@graphrefly/graphrefly 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +234 -0
- package/dist/chunk-5X3LAO3B.js +1571 -0
- package/dist/chunk-5X3LAO3B.js.map +1 -0
- package/dist/chunk-6W5SGIGB.js +1793 -0
- package/dist/chunk-6W5SGIGB.js.map +1 -0
- package/dist/chunk-CP6MNKAA.js +97 -0
- package/dist/chunk-CP6MNKAA.js.map +1 -0
- package/dist/chunk-HP7OKEOE.js +107 -0
- package/dist/chunk-HP7OKEOE.js.map +1 -0
- package/dist/chunk-KWXPDASV.js +781 -0
- package/dist/chunk-KWXPDASV.js.map +1 -0
- package/dist/chunk-O3PI7W45.js +68 -0
- package/dist/chunk-O3PI7W45.js.map +1 -0
- package/dist/chunk-QW7H3ICI.js +1372 -0
- package/dist/chunk-QW7H3ICI.js.map +1 -0
- package/dist/chunk-VPS7L64N.js +4785 -0
- package/dist/chunk-VPS7L64N.js.map +1 -0
- package/dist/chunk-Z4Y4FMQN.js +1097 -0
- package/dist/chunk-Z4Y4FMQN.js.map +1 -0
- package/dist/compat/nestjs/index.cjs +4883 -0
- package/dist/compat/nestjs/index.cjs.map +1 -0
- package/dist/compat/nestjs/index.d.cts +7 -0
- package/dist/compat/nestjs/index.d.ts +7 -0
- package/dist/compat/nestjs/index.js +84 -0
- package/dist/compat/nestjs/index.js.map +1 -0
- package/dist/core/index.cjs +1632 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +2 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +90 -0
- package/dist/core/index.js.map +1 -0
- package/dist/extra/index.cjs +6885 -0
- package/dist/extra/index.cjs.map +1 -0
- package/dist/extra/index.d.cts +5 -0
- package/dist/extra/index.d.ts +5 -0
- package/dist/extra/index.js +290 -0
- package/dist/extra/index.js.map +1 -0
- package/dist/graph/index.cjs +3225 -0
- package/dist/graph/index.cjs.map +1 -0
- package/dist/graph/index.d.cts +3 -0
- package/dist/graph/index.d.ts +3 -0
- package/dist/graph/index.js +25 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph-CL_ZDAj9.d.cts +605 -0
- package/dist/graph-D18qmsNm.d.ts +605 -0
- package/dist/index-B6SsZs2h.d.cts +3463 -0
- package/dist/index-B7eOdgEx.d.ts +449 -0
- package/dist/index-BHUvlQ3v.d.ts +3463 -0
- package/dist/index-BtK55IE2.d.ts +231 -0
- package/dist/index-BvhgZRHK.d.cts +231 -0
- package/dist/index-Bvy_6CaN.d.ts +452 -0
- package/dist/index-C3BMRmmp.d.cts +449 -0
- package/dist/index-C5mqLhMX.d.cts +452 -0
- package/dist/index-CP_QvbWu.d.ts +940 -0
- package/dist/index-D_geH2Bm.d.cts +940 -0
- package/dist/index.cjs +14843 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1517 -0
- package/dist/index.d.ts +1517 -0
- package/dist/index.js +3649 -0
- package/dist/index.js.map +1 -0
- package/dist/meta-BsF6Sag9.d.cts +607 -0
- package/dist/meta-BsF6Sag9.d.ts +607 -0
- package/dist/patterns/reactive-layout/index.cjs +4143 -0
- package/dist/patterns/reactive-layout/index.cjs.map +1 -0
- package/dist/patterns/reactive-layout/index.d.cts +3 -0
- package/dist/patterns/reactive-layout/index.d.ts +3 -0
- package/dist/patterns/reactive-layout/index.js +38 -0
- package/dist/patterns/reactive-layout/index.js.map +1 -0
- package/dist/reactive-log-BfvfNWQh.d.cts +137 -0
- package/dist/reactive-log-ohLmTXoZ.d.ts +137 -0
- package/package.json +256 -0
|
@@ -0,0 +1,1372 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createWatermarkController,
|
|
3
|
+
fromCron,
|
|
4
|
+
fromTimer,
|
|
5
|
+
observeGraph$,
|
|
6
|
+
observeNode$,
|
|
7
|
+
reactiveLog,
|
|
8
|
+
toMessages$,
|
|
9
|
+
toObservable
|
|
10
|
+
} from "./chunk-KWXPDASV.js";
|
|
11
|
+
import {
|
|
12
|
+
Graph
|
|
13
|
+
} from "./chunk-6W5SGIGB.js";
|
|
14
|
+
import {
|
|
15
|
+
COMPLETE,
|
|
16
|
+
DATA,
|
|
17
|
+
DEFAULT_ACTOR,
|
|
18
|
+
ERROR,
|
|
19
|
+
TEARDOWN,
|
|
20
|
+
__decorateElement,
|
|
21
|
+
__decoratorStart,
|
|
22
|
+
__export,
|
|
23
|
+
__runInitializers,
|
|
24
|
+
batch,
|
|
25
|
+
derived,
|
|
26
|
+
node,
|
|
27
|
+
normalizeActor,
|
|
28
|
+
policy,
|
|
29
|
+
state,
|
|
30
|
+
wallClockNs
|
|
31
|
+
} from "./chunk-5X3LAO3B.js";
|
|
32
|
+
|
|
33
|
+
// src/compat/nestjs/index.ts
|
|
34
|
+
var nestjs_exports = {};
|
|
35
|
+
__export(nestjs_exports, {
|
|
36
|
+
ACTOR_KEY: () => ACTOR_KEY,
|
|
37
|
+
COMMAND_HANDLERS: () => COMMAND_HANDLERS,
|
|
38
|
+
CQRS_EVENT_HANDLERS: () => CQRS_EVENT_HANDLERS,
|
|
39
|
+
CRON_HANDLERS: () => CRON_HANDLERS,
|
|
40
|
+
CommandHandler: () => CommandHandler,
|
|
41
|
+
EVENT_HANDLERS: () => EVENT_HANDLERS,
|
|
42
|
+
EventHandler: () => EventHandler,
|
|
43
|
+
GRAPHREFLY_REQUEST_GRAPH: () => GRAPHREFLY_REQUEST_GRAPH,
|
|
44
|
+
GRAPHREFLY_ROOT_GRAPH: () => GRAPHREFLY_ROOT_GRAPH,
|
|
45
|
+
GraphCron: () => GraphCron,
|
|
46
|
+
GraphInterval: () => GraphInterval,
|
|
47
|
+
GraphReflyEventExplorer: () => GraphReflyEventExplorer,
|
|
48
|
+
GraphReflyGuard: () => GraphReflyGuard,
|
|
49
|
+
GraphReflyGuardImpl: () => GraphReflyGuardImpl,
|
|
50
|
+
GraphReflyModule: () => GraphReflyModule,
|
|
51
|
+
INTERVAL_HANDLERS: () => INTERVAL_HANDLERS,
|
|
52
|
+
InjectCqrsGraph: () => InjectCqrsGraph,
|
|
53
|
+
InjectGraph: () => InjectGraph,
|
|
54
|
+
InjectNode: () => InjectNode,
|
|
55
|
+
ObserveGateway: () => ObserveGateway,
|
|
56
|
+
OnGraphEvent: () => OnGraphEvent,
|
|
57
|
+
QUERY_HANDLERS: () => QUERY_HANDLERS,
|
|
58
|
+
QueryHandler: () => QueryHandler,
|
|
59
|
+
SAGA_HANDLERS: () => SAGA_HANDLERS,
|
|
60
|
+
SagaHandler: () => SagaHandler,
|
|
61
|
+
fromHeader: () => fromHeader,
|
|
62
|
+
fromJwtPayload: () => fromJwtPayload,
|
|
63
|
+
getActor: () => getActor,
|
|
64
|
+
getGraphToken: () => getGraphToken,
|
|
65
|
+
getNodeToken: () => getNodeToken,
|
|
66
|
+
observeGraph$: () => observeGraph$,
|
|
67
|
+
observeNode$: () => observeNode$,
|
|
68
|
+
observeSSE: () => observeSSE,
|
|
69
|
+
observeSubscription: () => observeSubscription,
|
|
70
|
+
toMessages$: () => toMessages$,
|
|
71
|
+
toObservable: () => toObservable
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// src/compat/nestjs/decorators.ts
|
|
75
|
+
import { Inject } from "@nestjs/common";
|
|
76
|
+
|
|
77
|
+
// src/compat/nestjs/tokens.ts
|
|
78
|
+
var GRAPHREFLY_ROOT_GRAPH = /* @__PURE__ */ Symbol.for("graphrefly:root-graph");
|
|
79
|
+
var GRAPHREFLY_REQUEST_GRAPH = /* @__PURE__ */ Symbol.for("graphrefly:request-graph");
|
|
80
|
+
function getGraphToken(name) {
|
|
81
|
+
return /* @__PURE__ */ Symbol.for(`graphrefly:graph:${name}`);
|
|
82
|
+
}
|
|
83
|
+
function getNodeToken(path) {
|
|
84
|
+
return /* @__PURE__ */ Symbol.for(`graphrefly:node:${path}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/compat/nestjs/decorators.ts
|
|
88
|
+
var EVENT_HANDLERS = /* @__PURE__ */ new Map();
|
|
89
|
+
var INTERVAL_HANDLERS = /* @__PURE__ */ new Map();
|
|
90
|
+
var CRON_HANDLERS = /* @__PURE__ */ new Map();
|
|
91
|
+
var COMMAND_HANDLERS = /* @__PURE__ */ new Map();
|
|
92
|
+
var CQRS_EVENT_HANDLERS = /* @__PURE__ */ new Map();
|
|
93
|
+
var QUERY_HANDLERS = /* @__PURE__ */ new Map();
|
|
94
|
+
var SAGA_HANDLERS = /* @__PURE__ */ new Map();
|
|
95
|
+
function InjectGraph(name) {
|
|
96
|
+
if (name === "request") return Inject(GRAPHREFLY_REQUEST_GRAPH);
|
|
97
|
+
return Inject(name ? getGraphToken(name) : GRAPHREFLY_ROOT_GRAPH);
|
|
98
|
+
}
|
|
99
|
+
function InjectCqrsGraph(name) {
|
|
100
|
+
return Inject(getGraphToken(name));
|
|
101
|
+
}
|
|
102
|
+
function InjectNode(path) {
|
|
103
|
+
return Inject(getNodeToken(path));
|
|
104
|
+
}
|
|
105
|
+
function OnGraphEvent(nodeName) {
|
|
106
|
+
return (_value, context) => {
|
|
107
|
+
const methodKey = context.name;
|
|
108
|
+
context.addInitializer(function() {
|
|
109
|
+
const ctor = this.constructor;
|
|
110
|
+
const existing = EVENT_HANDLERS.get(ctor) ?? [];
|
|
111
|
+
existing.push({ nodeName, methodKey });
|
|
112
|
+
EVENT_HANDLERS.set(ctor, existing);
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function GraphInterval(ms) {
|
|
117
|
+
return (_value, context) => {
|
|
118
|
+
const methodKey = context.name;
|
|
119
|
+
context.addInitializer(function() {
|
|
120
|
+
const ctor = this.constructor;
|
|
121
|
+
const existing = INTERVAL_HANDLERS.get(ctor) ?? [];
|
|
122
|
+
existing.push({ ms, methodKey });
|
|
123
|
+
INTERVAL_HANDLERS.set(ctor, existing);
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function GraphCron(expr) {
|
|
128
|
+
return (_value, context) => {
|
|
129
|
+
const methodKey = context.name;
|
|
130
|
+
context.addInitializer(function() {
|
|
131
|
+
const ctor = this.constructor;
|
|
132
|
+
const existing = CRON_HANDLERS.get(ctor) ?? [];
|
|
133
|
+
existing.push({ expr, methodKey });
|
|
134
|
+
CRON_HANDLERS.set(ctor, existing);
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function CommandHandler(cqrsName, commandName) {
|
|
139
|
+
return (_value, context) => {
|
|
140
|
+
const methodKey = context.name;
|
|
141
|
+
context.addInitializer(function() {
|
|
142
|
+
const ctor = this.constructor;
|
|
143
|
+
const existing = COMMAND_HANDLERS.get(ctor) ?? [];
|
|
144
|
+
existing.push({ cqrsName, commandName, methodKey });
|
|
145
|
+
COMMAND_HANDLERS.set(ctor, existing);
|
|
146
|
+
});
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function EventHandler(cqrsName, eventName) {
|
|
150
|
+
return (_value, context) => {
|
|
151
|
+
const methodKey = context.name;
|
|
152
|
+
context.addInitializer(function() {
|
|
153
|
+
const ctor = this.constructor;
|
|
154
|
+
const existing = CQRS_EVENT_HANDLERS.get(ctor) ?? [];
|
|
155
|
+
existing.push({ cqrsName, eventName, methodKey });
|
|
156
|
+
CQRS_EVENT_HANDLERS.set(ctor, existing);
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function QueryHandler(cqrsName, projectionName) {
|
|
161
|
+
return (_value, context) => {
|
|
162
|
+
const methodKey = context.name;
|
|
163
|
+
context.addInitializer(function() {
|
|
164
|
+
const ctor = this.constructor;
|
|
165
|
+
const existing = QUERY_HANDLERS.get(ctor) ?? [];
|
|
166
|
+
existing.push({ cqrsName, projectionName, methodKey });
|
|
167
|
+
QUERY_HANDLERS.set(ctor, existing);
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function SagaHandler(cqrsName, sagaName, eventNames) {
|
|
172
|
+
return (_value, context) => {
|
|
173
|
+
const methodKey = context.name;
|
|
174
|
+
context.addInitializer(function() {
|
|
175
|
+
const ctor = this.constructor;
|
|
176
|
+
const existing = SAGA_HANDLERS.get(ctor) ?? [];
|
|
177
|
+
existing.push({ cqrsName, eventNames, sagaName, methodKey });
|
|
178
|
+
SAGA_HANDLERS.set(ctor, existing);
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/compat/nestjs/explorer.ts
|
|
184
|
+
var scheduleSeq = 0;
|
|
185
|
+
var GraphReflyEventExplorer = class {
|
|
186
|
+
constructor(graph, moduleRef) {
|
|
187
|
+
this.graph = graph;
|
|
188
|
+
this.moduleRef = moduleRef;
|
|
189
|
+
}
|
|
190
|
+
disposers = [];
|
|
191
|
+
scheduleNodeNames = [];
|
|
192
|
+
onModuleInit() {
|
|
193
|
+
this.wireEvents();
|
|
194
|
+
this.wireIntervals();
|
|
195
|
+
this.wireCrons();
|
|
196
|
+
this.wireCqrsCommands();
|
|
197
|
+
this.wireCqrsEvents();
|
|
198
|
+
this.wireCqrsQueries();
|
|
199
|
+
this.wireCqrsSagas();
|
|
200
|
+
}
|
|
201
|
+
onModuleDestroy() {
|
|
202
|
+
for (const dispose of this.disposers) dispose();
|
|
203
|
+
this.disposers.length = 0;
|
|
204
|
+
for (const name of this.scheduleNodeNames) {
|
|
205
|
+
try {
|
|
206
|
+
this.graph.remove(name);
|
|
207
|
+
} catch {
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
this.scheduleNodeNames.length = 0;
|
|
211
|
+
}
|
|
212
|
+
// -----------------------------------------------------------------------
|
|
213
|
+
// @OnGraphEvent — reactive subscription via graph.observe()
|
|
214
|
+
// -----------------------------------------------------------------------
|
|
215
|
+
wireEvents() {
|
|
216
|
+
for (const [ctor, metas] of EVENT_HANDLERS) {
|
|
217
|
+
const instance = this.resolveInstance(ctor);
|
|
218
|
+
if (!instance) continue;
|
|
219
|
+
for (const meta of metas) {
|
|
220
|
+
this.wireEventHandler(instance, meta);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
wireEventHandler(instance, meta) {
|
|
225
|
+
const method = instance[meta.methodKey];
|
|
226
|
+
if (typeof method !== "function") return;
|
|
227
|
+
const bound = method.bind(instance);
|
|
228
|
+
const handle = this.observeNode(meta.nodeName);
|
|
229
|
+
const unsub = handle.subscribe((msgs) => {
|
|
230
|
+
for (const m of msgs) {
|
|
231
|
+
if (m[0] === DATA) {
|
|
232
|
+
bound(m[1]);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
this.disposers.push(unsub);
|
|
237
|
+
}
|
|
238
|
+
// -----------------------------------------------------------------------
|
|
239
|
+
// @GraphInterval — reactive via fromTimer central timer primitive
|
|
240
|
+
// -----------------------------------------------------------------------
|
|
241
|
+
wireIntervals() {
|
|
242
|
+
for (const [ctor, metas] of INTERVAL_HANDLERS) {
|
|
243
|
+
const instance = this.resolveInstance(ctor);
|
|
244
|
+
if (!instance) continue;
|
|
245
|
+
for (const meta of metas) {
|
|
246
|
+
this.wireIntervalHandler(instance, ctor, meta);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
wireIntervalHandler(instance, ctor, meta) {
|
|
251
|
+
const method = instance[meta.methodKey];
|
|
252
|
+
if (typeof method !== "function") return;
|
|
253
|
+
const bound = method.bind(instance);
|
|
254
|
+
const className = ctor.name ?? "anonymous";
|
|
255
|
+
const nodeName = `__schedule__.${className}.${String(meta.methodKey)}.${scheduleSeq++}`;
|
|
256
|
+
const timerNode = fromTimer(meta.ms, { period: meta.ms, name: nodeName });
|
|
257
|
+
this.graph.add(nodeName, timerNode);
|
|
258
|
+
this.scheduleNodeNames.push(nodeName);
|
|
259
|
+
const handle = this.observeNode(nodeName);
|
|
260
|
+
const unsub = handle.subscribe((msgs) => {
|
|
261
|
+
for (const m of msgs) {
|
|
262
|
+
if (m[0] === DATA) bound(m[1]);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
this.disposers.push(unsub);
|
|
266
|
+
}
|
|
267
|
+
// -----------------------------------------------------------------------
|
|
268
|
+
// @GraphCron — reactive via fromCron central timer primitive
|
|
269
|
+
// -----------------------------------------------------------------------
|
|
270
|
+
wireCrons() {
|
|
271
|
+
for (const [ctor, metas] of CRON_HANDLERS) {
|
|
272
|
+
const instance = this.resolveInstance(ctor);
|
|
273
|
+
if (!instance) continue;
|
|
274
|
+
for (const meta of metas) {
|
|
275
|
+
this.wireCronHandler(instance, ctor, meta);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
wireCronHandler(instance, ctor, meta) {
|
|
280
|
+
const method = instance[meta.methodKey];
|
|
281
|
+
if (typeof method !== "function") return;
|
|
282
|
+
const bound = method.bind(instance);
|
|
283
|
+
const className = ctor.name ?? "anonymous";
|
|
284
|
+
const nodeName = `__schedule__.${className}.${String(meta.methodKey)}.${scheduleSeq++}`;
|
|
285
|
+
const cronNode = fromCron(meta.expr, { name: nodeName });
|
|
286
|
+
this.graph.add(nodeName, cronNode);
|
|
287
|
+
this.scheduleNodeNames.push(nodeName);
|
|
288
|
+
const handle = this.observeNode(nodeName);
|
|
289
|
+
const unsub = handle.subscribe((msgs) => {
|
|
290
|
+
for (const m of msgs) {
|
|
291
|
+
if (m[0] === DATA) bound(m[1]);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
this.disposers.push(unsub);
|
|
295
|
+
}
|
|
296
|
+
// -----------------------------------------------------------------------
|
|
297
|
+
// @CommandHandler — register method as CqrsGraph command handler
|
|
298
|
+
// -----------------------------------------------------------------------
|
|
299
|
+
wireCqrsCommands() {
|
|
300
|
+
for (const [ctor, metas] of COMMAND_HANDLERS) {
|
|
301
|
+
const instance = this.resolveInstance(ctor);
|
|
302
|
+
if (!instance) continue;
|
|
303
|
+
for (const meta of metas) {
|
|
304
|
+
this.wireCqrsCommand(instance, meta);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
wireCqrsCommand(instance, meta) {
|
|
309
|
+
const method = instance[meta.methodKey];
|
|
310
|
+
if (typeof method !== "function") return;
|
|
311
|
+
const bound = method.bind(instance);
|
|
312
|
+
const cqrsGraph = this.resolveCqrsGraph(meta.cqrsName);
|
|
313
|
+
if (!cqrsGraph) return;
|
|
314
|
+
cqrsGraph.command(meta.commandName, bound);
|
|
315
|
+
}
|
|
316
|
+
// -----------------------------------------------------------------------
|
|
317
|
+
// @EventHandler — subscribe method to CQRS event stream
|
|
318
|
+
// -----------------------------------------------------------------------
|
|
319
|
+
wireCqrsEvents() {
|
|
320
|
+
for (const [ctor, metas] of CQRS_EVENT_HANDLERS) {
|
|
321
|
+
const instance = this.resolveInstance(ctor);
|
|
322
|
+
if (!instance) continue;
|
|
323
|
+
for (const meta of metas) {
|
|
324
|
+
this.wireCqrsEventHandler(instance, meta);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
wireCqrsEventHandler(instance, meta) {
|
|
329
|
+
const method = instance[meta.methodKey];
|
|
330
|
+
if (typeof method !== "function") return;
|
|
331
|
+
const bound = method.bind(instance);
|
|
332
|
+
const cqrsGraph = this.resolveCqrsGraph(meta.cqrsName);
|
|
333
|
+
if (!cqrsGraph) return;
|
|
334
|
+
cqrsGraph.event(meta.eventName);
|
|
335
|
+
const eventNode = cqrsGraph.resolve(meta.eventName);
|
|
336
|
+
const currentSnap = eventNode.get();
|
|
337
|
+
const existingEntries = currentSnap?.value?.entries;
|
|
338
|
+
let lastSeq = existingEntries && existingEntries.length > 0 ? existingEntries[existingEntries.length - 1].seq : 0;
|
|
339
|
+
const handle = this.observeNodeOn(cqrsGraph, meta.eventName);
|
|
340
|
+
const unsub = handle.subscribe((msgs) => {
|
|
341
|
+
for (const m of msgs) {
|
|
342
|
+
if (m[0] === DATA) {
|
|
343
|
+
const snap = m[1];
|
|
344
|
+
for (const entry of snap.value.entries) {
|
|
345
|
+
if (entry.seq > lastSeq) {
|
|
346
|
+
bound(entry);
|
|
347
|
+
lastSeq = entry.seq;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
this.disposers.push(unsub);
|
|
354
|
+
}
|
|
355
|
+
// -----------------------------------------------------------------------
|
|
356
|
+
// @QueryHandler — subscribe method to CQRS projection changes
|
|
357
|
+
// -----------------------------------------------------------------------
|
|
358
|
+
wireCqrsQueries() {
|
|
359
|
+
for (const [ctor, metas] of QUERY_HANDLERS) {
|
|
360
|
+
const instance = this.resolveInstance(ctor);
|
|
361
|
+
if (!instance) continue;
|
|
362
|
+
for (const meta of metas) {
|
|
363
|
+
this.wireCqrsQuery(instance, meta);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
wireCqrsQuery(instance, meta) {
|
|
368
|
+
const method = instance[meta.methodKey];
|
|
369
|
+
if (typeof method !== "function") return;
|
|
370
|
+
const bound = method.bind(instance);
|
|
371
|
+
const cqrsGraph = this.resolveCqrsGraph(meta.cqrsName);
|
|
372
|
+
if (!cqrsGraph) return;
|
|
373
|
+
const handle = this.observeNodeOn(cqrsGraph, meta.projectionName);
|
|
374
|
+
const unsub = handle.subscribe((msgs) => {
|
|
375
|
+
for (const m of msgs) {
|
|
376
|
+
if (m[0] === DATA) {
|
|
377
|
+
bound(m[1]);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
this.disposers.push(unsub);
|
|
382
|
+
}
|
|
383
|
+
// -----------------------------------------------------------------------
|
|
384
|
+
// @SagaHandler — register method as CqrsGraph saga (subgraph side effect)
|
|
385
|
+
// -----------------------------------------------------------------------
|
|
386
|
+
wireCqrsSagas() {
|
|
387
|
+
for (const [ctor, metas] of SAGA_HANDLERS) {
|
|
388
|
+
const instance = this.resolveInstance(ctor);
|
|
389
|
+
if (!instance) continue;
|
|
390
|
+
for (const meta of metas) {
|
|
391
|
+
this.wireCqrsSaga(instance, meta);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
wireCqrsSaga(instance, meta) {
|
|
396
|
+
const method = instance[meta.methodKey];
|
|
397
|
+
if (typeof method !== "function") return;
|
|
398
|
+
const bound = method.bind(instance);
|
|
399
|
+
const cqrsGraph = this.resolveCqrsGraph(meta.cqrsName);
|
|
400
|
+
if (!cqrsGraph) return;
|
|
401
|
+
cqrsGraph.saga(meta.sagaName, meta.eventNames, bound);
|
|
402
|
+
}
|
|
403
|
+
// -----------------------------------------------------------------------
|
|
404
|
+
// Helpers
|
|
405
|
+
// -----------------------------------------------------------------------
|
|
406
|
+
observeNode(name) {
|
|
407
|
+
return this.graph.observe(name);
|
|
408
|
+
}
|
|
409
|
+
observeNodeOn(graph, name) {
|
|
410
|
+
return graph.observe(name);
|
|
411
|
+
}
|
|
412
|
+
resolveCqrsGraph(name) {
|
|
413
|
+
try {
|
|
414
|
+
return this.moduleRef.get(getGraphToken(name), { strict: false });
|
|
415
|
+
} catch {
|
|
416
|
+
console.warn(
|
|
417
|
+
`[GraphReFly] CqrsGraph "${name}" not found in DI \u2014 did you import GraphReflyModule.forCqrs({ name: "${name}" })?`
|
|
418
|
+
);
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
resolveInstance(ctor) {
|
|
423
|
+
try {
|
|
424
|
+
return this.moduleRef.get(ctor, { strict: false });
|
|
425
|
+
} catch {
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
// src/compat/nestjs/gateway.ts
|
|
432
|
+
function observeSSE(graph, path, opts) {
|
|
433
|
+
const { actor, serialize = defaultSerialize, keepAliveMs, signal } = opts ?? {};
|
|
434
|
+
const encoder = new TextEncoder();
|
|
435
|
+
let stop;
|
|
436
|
+
const useBackpressure = opts?.highWaterMark != null;
|
|
437
|
+
let wm;
|
|
438
|
+
let pullResolve;
|
|
439
|
+
const taggedBuf = [];
|
|
440
|
+
let closed = false;
|
|
441
|
+
return new ReadableStream({
|
|
442
|
+
start(controller) {
|
|
443
|
+
let keepAlive;
|
|
444
|
+
let unsub = () => {
|
|
445
|
+
};
|
|
446
|
+
const close = () => {
|
|
447
|
+
if (closed) return;
|
|
448
|
+
closed = true;
|
|
449
|
+
if (keepAlive !== void 0) clearInterval(keepAlive);
|
|
450
|
+
signal?.removeEventListener("abort", onAbort);
|
|
451
|
+
unsub();
|
|
452
|
+
wm?.dispose();
|
|
453
|
+
pullResolve?.();
|
|
454
|
+
pullResolve = void 0;
|
|
455
|
+
for (const entry of taggedBuf) controller.enqueue(entry.frame);
|
|
456
|
+
taggedBuf.length = 0;
|
|
457
|
+
controller.close();
|
|
458
|
+
};
|
|
459
|
+
stop = close;
|
|
460
|
+
const onAbort = () => close();
|
|
461
|
+
const handle = graph.observe(path, { actor });
|
|
462
|
+
if (useBackpressure) {
|
|
463
|
+
wm = createWatermarkController((msgs) => handle.up(msgs), {
|
|
464
|
+
highWaterMark: opts.highWaterMark,
|
|
465
|
+
lowWaterMark: opts.lowWaterMark ?? Math.floor(opts.highWaterMark / 2)
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
unsub = handle.subscribe((msgs) => {
|
|
469
|
+
for (const msg of msgs) {
|
|
470
|
+
if (closed) return;
|
|
471
|
+
const t = msg[0];
|
|
472
|
+
if (t === DATA) {
|
|
473
|
+
const frame = encoder.encode(sseFrame("data", serialize(msg[1])));
|
|
474
|
+
if (useBackpressure) {
|
|
475
|
+
taggedBuf.push({ frame, counted: true });
|
|
476
|
+
wm.onEnqueue();
|
|
477
|
+
pullResolve?.();
|
|
478
|
+
pullResolve = void 0;
|
|
479
|
+
} else {
|
|
480
|
+
controller.enqueue(frame);
|
|
481
|
+
}
|
|
482
|
+
} else if (t === ERROR) {
|
|
483
|
+
const frame = encoder.encode(sseFrame("error", serialize(msg[1])));
|
|
484
|
+
if (useBackpressure) {
|
|
485
|
+
taggedBuf.push({ frame, counted: false });
|
|
486
|
+
pullResolve?.();
|
|
487
|
+
pullResolve = void 0;
|
|
488
|
+
} else {
|
|
489
|
+
controller.enqueue(frame);
|
|
490
|
+
}
|
|
491
|
+
close();
|
|
492
|
+
return;
|
|
493
|
+
} else if (t === COMPLETE || t === TEARDOWN) {
|
|
494
|
+
if (t === COMPLETE) {
|
|
495
|
+
const frame = encoder.encode(sseFrame("complete"));
|
|
496
|
+
if (useBackpressure) {
|
|
497
|
+
taggedBuf.push({ frame, counted: false });
|
|
498
|
+
pullResolve?.();
|
|
499
|
+
pullResolve = void 0;
|
|
500
|
+
} else {
|
|
501
|
+
controller.enqueue(frame);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
close();
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
if (keepAliveMs !== void 0 && keepAliveMs > 0) {
|
|
510
|
+
keepAlive = setInterval(() => {
|
|
511
|
+
if (closed) return;
|
|
512
|
+
if (useBackpressure) {
|
|
513
|
+
taggedBuf.push({ frame: encoder.encode(": keepalive\n\n"), counted: false });
|
|
514
|
+
pullResolve?.();
|
|
515
|
+
pullResolve = void 0;
|
|
516
|
+
} else {
|
|
517
|
+
controller.enqueue(encoder.encode(": keepalive\n\n"));
|
|
518
|
+
}
|
|
519
|
+
}, keepAliveMs);
|
|
520
|
+
}
|
|
521
|
+
if (signal?.aborted) onAbort();
|
|
522
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
523
|
+
},
|
|
524
|
+
pull(controller) {
|
|
525
|
+
if (!useBackpressure) return;
|
|
526
|
+
if (closed) return;
|
|
527
|
+
if (taggedBuf.length > 0) {
|
|
528
|
+
const entry = taggedBuf.shift();
|
|
529
|
+
controller.enqueue(entry.frame);
|
|
530
|
+
if (entry.counted) wm.onDequeue();
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
return new Promise((resolve) => {
|
|
534
|
+
pullResolve = resolve;
|
|
535
|
+
});
|
|
536
|
+
},
|
|
537
|
+
cancel() {
|
|
538
|
+
try {
|
|
539
|
+
stop?.();
|
|
540
|
+
} catch {
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
function observeSubscription(graph, path, opts) {
|
|
546
|
+
const { actor, filter } = opts ?? {};
|
|
547
|
+
const queue = [];
|
|
548
|
+
const waiters = [];
|
|
549
|
+
let disposed = false;
|
|
550
|
+
const handle = graph.observe(path, { actor });
|
|
551
|
+
const wm = opts?.highWaterMark != null ? createWatermarkController((msgs) => handle.up(msgs), {
|
|
552
|
+
highWaterMark: opts.highWaterMark,
|
|
553
|
+
lowWaterMark: opts.lowWaterMark ?? Math.floor(opts.highWaterMark / 2)
|
|
554
|
+
}) : void 0;
|
|
555
|
+
const dispose = () => {
|
|
556
|
+
if (disposed) return;
|
|
557
|
+
disposed = true;
|
|
558
|
+
wm?.dispose();
|
|
559
|
+
unsub();
|
|
560
|
+
};
|
|
561
|
+
const push = (item) => {
|
|
562
|
+
if (disposed) return;
|
|
563
|
+
if (waiters.length > 0) {
|
|
564
|
+
const w = waiters.shift();
|
|
565
|
+
if (item.done && item.error) w.reject(item.error);
|
|
566
|
+
else if (item.done) w.resolve({ done: true, value: void 0 });
|
|
567
|
+
else w.resolve({ done: false, value: item.value });
|
|
568
|
+
} else {
|
|
569
|
+
queue.push(item);
|
|
570
|
+
if (!item.done) wm?.onEnqueue();
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
const unsub = handle.subscribe((msgs) => {
|
|
574
|
+
for (const msg of msgs) {
|
|
575
|
+
const t = msg[0];
|
|
576
|
+
if (t === DATA) {
|
|
577
|
+
const value = msg[1];
|
|
578
|
+
if (filter && !filter(value)) continue;
|
|
579
|
+
push({ done: false, value });
|
|
580
|
+
} else if (t === ERROR) {
|
|
581
|
+
const err = msg[1] instanceof Error ? msg[1] : new Error(String(msg[1]));
|
|
582
|
+
push({ done: true, error: err });
|
|
583
|
+
dispose();
|
|
584
|
+
return;
|
|
585
|
+
} else if (t === COMPLETE || t === TEARDOWN) {
|
|
586
|
+
push({ done: true });
|
|
587
|
+
dispose();
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
const iterator = {
|
|
593
|
+
next() {
|
|
594
|
+
if (queue.length > 0) {
|
|
595
|
+
const item = queue.shift();
|
|
596
|
+
if (!item.done) wm?.onDequeue();
|
|
597
|
+
if (item.done && item.error) return Promise.reject(item.error);
|
|
598
|
+
return Promise.resolve(
|
|
599
|
+
item.done ? { done: true, value: void 0 } : { done: false, value: item.value }
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
if (disposed) return Promise.resolve({ done: true, value: void 0 });
|
|
603
|
+
return new Promise((resolve, reject) => {
|
|
604
|
+
waiters.push({ resolve, reject });
|
|
605
|
+
});
|
|
606
|
+
},
|
|
607
|
+
return() {
|
|
608
|
+
dispose();
|
|
609
|
+
for (const w of waiters) w.resolve({ done: true, value: void 0 });
|
|
610
|
+
waiters.length = 0;
|
|
611
|
+
return Promise.resolve({ done: true, value: void 0 });
|
|
612
|
+
},
|
|
613
|
+
throw(err) {
|
|
614
|
+
dispose();
|
|
615
|
+
return Promise.reject(err);
|
|
616
|
+
},
|
|
617
|
+
[Symbol.asyncIterator]() {
|
|
618
|
+
return this;
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
return iterator;
|
|
622
|
+
}
|
|
623
|
+
var ObserveGateway = class {
|
|
624
|
+
constructor(graph, opts) {
|
|
625
|
+
this.graph = graph;
|
|
626
|
+
this.extractActor = opts?.extractActor ?? (() => void 0);
|
|
627
|
+
this.parse = opts?.parse ?? defaultParseCommand;
|
|
628
|
+
this.highWaterMark = opts?.highWaterMark;
|
|
629
|
+
this.lowWaterMark = opts?.lowWaterMark;
|
|
630
|
+
}
|
|
631
|
+
clients = /* @__PURE__ */ new Map();
|
|
632
|
+
extractActor;
|
|
633
|
+
parse;
|
|
634
|
+
highWaterMark;
|
|
635
|
+
lowWaterMark;
|
|
636
|
+
/**
|
|
637
|
+
* Register a new client. Call from `handleConnection`.
|
|
638
|
+
*/
|
|
639
|
+
handleConnection(client) {
|
|
640
|
+
if (!this.clients.has(client)) {
|
|
641
|
+
this.clients.set(client, /* @__PURE__ */ new Map());
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Unregister a client and dispose all its subscriptions. Call from `handleDisconnect`.
|
|
646
|
+
*/
|
|
647
|
+
handleDisconnect(client) {
|
|
648
|
+
const subs = this.clients.get(client);
|
|
649
|
+
if (!subs) return;
|
|
650
|
+
for (const entry of subs.values()) {
|
|
651
|
+
entry.wm?.dispose();
|
|
652
|
+
entry.unsub();
|
|
653
|
+
}
|
|
654
|
+
this.clients.delete(client);
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Handle an incoming client message (subscribe/unsubscribe/ack command).
|
|
658
|
+
*
|
|
659
|
+
* @param client - The WebSocket client reference.
|
|
660
|
+
* @param raw - Raw message data (string or parsed object).
|
|
661
|
+
* @param send - Function to send a message back to the client.
|
|
662
|
+
* Defaults to `client.send(JSON.stringify(msg))`.
|
|
663
|
+
*/
|
|
664
|
+
handleMessage(client, raw, send) {
|
|
665
|
+
const sender = send ?? defaultSend.bind(null, client);
|
|
666
|
+
let cmd;
|
|
667
|
+
try {
|
|
668
|
+
cmd = typeof raw === "string" ? this.parse(raw) : raw;
|
|
669
|
+
} catch {
|
|
670
|
+
sender({ type: "err", message: "invalid command" });
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
if (cmd.type === "subscribe") {
|
|
674
|
+
this.subscribe(client, cmd.path, sender);
|
|
675
|
+
} else if (cmd.type === "unsubscribe") {
|
|
676
|
+
this.unsubscribe(client, cmd.path, sender);
|
|
677
|
+
} else if (cmd.type === "ack") {
|
|
678
|
+
this.ack(client, cmd.path, cmd.count ?? 1);
|
|
679
|
+
} else {
|
|
680
|
+
sender({ type: "err", message: `unknown command type: ${cmd.type}` });
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Number of active subscriptions for a client. Useful for tests.
|
|
685
|
+
*/
|
|
686
|
+
subscriptionCount(client) {
|
|
687
|
+
return this.clients.get(client)?.size ?? 0;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Dispose all clients and subscriptions.
|
|
691
|
+
*/
|
|
692
|
+
destroy() {
|
|
693
|
+
for (const [client] of this.clients) {
|
|
694
|
+
this.handleDisconnect(client);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
// -----------------------------------------------------------------------
|
|
698
|
+
// Internal
|
|
699
|
+
// -----------------------------------------------------------------------
|
|
700
|
+
subscribe(client, path, send) {
|
|
701
|
+
let subs = this.clients.get(client);
|
|
702
|
+
if (!subs) {
|
|
703
|
+
subs = /* @__PURE__ */ new Map();
|
|
704
|
+
this.clients.set(client, subs);
|
|
705
|
+
}
|
|
706
|
+
if (subs.has(path)) {
|
|
707
|
+
send({ type: "subscribed", path });
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
const actor = this.extractActor(client);
|
|
711
|
+
let handle;
|
|
712
|
+
try {
|
|
713
|
+
handle = this.graph.observe(path, { actor });
|
|
714
|
+
} catch (err) {
|
|
715
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
716
|
+
send({ type: "err", message });
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
const wm = this.highWaterMark != null ? createWatermarkController((msgs) => handle.up(msgs), {
|
|
720
|
+
highWaterMark: this.highWaterMark,
|
|
721
|
+
lowWaterMark: this.lowWaterMark ?? Math.floor(this.highWaterMark / 2)
|
|
722
|
+
}) : void 0;
|
|
723
|
+
const cleanup = () => {
|
|
724
|
+
wm?.dispose();
|
|
725
|
+
unsub();
|
|
726
|
+
subs.delete(path);
|
|
727
|
+
};
|
|
728
|
+
const unsub = handle.subscribe((msgs) => {
|
|
729
|
+
for (const msg of msgs) {
|
|
730
|
+
const t = msg[0];
|
|
731
|
+
if (t === DATA) {
|
|
732
|
+
wm?.onEnqueue();
|
|
733
|
+
trySend(send, { type: "data", path, value: msg[1] });
|
|
734
|
+
} else if (t === ERROR) {
|
|
735
|
+
const errMsg = msg[1] instanceof Error ? msg[1].message : String(msg[1]);
|
|
736
|
+
trySend(send, { type: "error", path, error: errMsg });
|
|
737
|
+
cleanup();
|
|
738
|
+
return;
|
|
739
|
+
} else if (t === COMPLETE || t === TEARDOWN) {
|
|
740
|
+
trySend(send, { type: "complete", path });
|
|
741
|
+
cleanup();
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
subs.set(path, { unsub, wm });
|
|
747
|
+
send({ type: "subscribed", path });
|
|
748
|
+
}
|
|
749
|
+
unsubscribe(client, path, send) {
|
|
750
|
+
const subs = this.clients.get(client);
|
|
751
|
+
const entry = subs?.get(path);
|
|
752
|
+
if (entry) {
|
|
753
|
+
entry.wm?.dispose();
|
|
754
|
+
entry.unsub();
|
|
755
|
+
subs.delete(path);
|
|
756
|
+
}
|
|
757
|
+
send({ type: "unsubscribed", path });
|
|
758
|
+
}
|
|
759
|
+
ack(client, path, count) {
|
|
760
|
+
const entry = this.clients.get(client)?.get(path);
|
|
761
|
+
if (!entry?.wm) return;
|
|
762
|
+
const n = Math.min(Math.max(0, Math.floor(count)), 1024);
|
|
763
|
+
for (let i = 0; i < n; i++) entry.wm.onDequeue();
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
function defaultSerialize(value) {
|
|
767
|
+
if (value instanceof Error) return value.message;
|
|
768
|
+
try {
|
|
769
|
+
return JSON.stringify(value);
|
|
770
|
+
} catch {
|
|
771
|
+
return String(value);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
function sseFrame(event, data) {
|
|
775
|
+
let frame = `event: ${event}
|
|
776
|
+
`;
|
|
777
|
+
if (data !== void 0) {
|
|
778
|
+
for (const line of data.split("\n")) {
|
|
779
|
+
frame += `data: ${line}
|
|
780
|
+
`;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
frame += "\n";
|
|
784
|
+
return frame;
|
|
785
|
+
}
|
|
786
|
+
function defaultParseCommand(data) {
|
|
787
|
+
return JSON.parse(data);
|
|
788
|
+
}
|
|
789
|
+
function defaultSend(client, msg) {
|
|
790
|
+
try {
|
|
791
|
+
client.send(JSON.stringify(msg));
|
|
792
|
+
} catch {
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
function trySend(send, msg) {
|
|
796
|
+
try {
|
|
797
|
+
send(msg);
|
|
798
|
+
} catch {
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// src/compat/nestjs/guard.ts
|
|
803
|
+
var ACTOR_KEY = "graphReflyActor";
|
|
804
|
+
function fromJwtPayload(mapping) {
|
|
805
|
+
return (context) => {
|
|
806
|
+
const req = context.switchToHttp().getRequest();
|
|
807
|
+
const user = req?.user;
|
|
808
|
+
if (user == null) return void 0;
|
|
809
|
+
if (mapping) return mapping(user);
|
|
810
|
+
return user;
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
function fromHeader(headerName = "x-graphrefly-actor") {
|
|
814
|
+
return (context) => {
|
|
815
|
+
const req = context.switchToHttp().getRequest();
|
|
816
|
+
const raw = req?.headers?.[headerName.toLowerCase()];
|
|
817
|
+
if (typeof raw !== "string" || raw.length === 0) return void 0;
|
|
818
|
+
try {
|
|
819
|
+
return JSON.parse(raw);
|
|
820
|
+
} catch {
|
|
821
|
+
return void 0;
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
function getActor(req) {
|
|
826
|
+
const actor = req?.[ACTOR_KEY];
|
|
827
|
+
return actor != null ? normalizeActor(actor) : DEFAULT_ACTOR;
|
|
828
|
+
}
|
|
829
|
+
var GraphReflyGuardImpl = class {
|
|
830
|
+
constructor(extractor) {
|
|
831
|
+
this.extractor = extractor;
|
|
832
|
+
}
|
|
833
|
+
canActivate(context) {
|
|
834
|
+
const actor = normalizeActor(this.extractor(context));
|
|
835
|
+
const req = context.switchToHttp().getRequest();
|
|
836
|
+
if (req != null) {
|
|
837
|
+
req[ACTOR_KEY] = actor;
|
|
838
|
+
}
|
|
839
|
+
return true;
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
function GraphReflyGuard(extractor) {
|
|
843
|
+
return new GraphReflyGuardImpl(extractor ?? fromJwtPayload());
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// src/compat/nestjs/module.ts
|
|
847
|
+
import {
|
|
848
|
+
Module,
|
|
849
|
+
Scope
|
|
850
|
+
} from "@nestjs/common";
|
|
851
|
+
import { ModuleRef } from "@nestjs/core";
|
|
852
|
+
|
|
853
|
+
// src/patterns/cqrs.ts
|
|
854
|
+
var cqrs_exports = {};
|
|
855
|
+
__export(cqrs_exports, {
|
|
856
|
+
CqrsGraph: () => CqrsGraph,
|
|
857
|
+
MemoryEventStore: () => MemoryEventStore,
|
|
858
|
+
cqrs: () => cqrs
|
|
859
|
+
});
|
|
860
|
+
var COMMAND_GUARD = policy((allow, deny) => {
|
|
861
|
+
allow("write");
|
|
862
|
+
allow("signal");
|
|
863
|
+
deny("observe");
|
|
864
|
+
});
|
|
865
|
+
var PROJECTION_GUARD = policy((allow, deny) => {
|
|
866
|
+
allow("observe");
|
|
867
|
+
allow("signal");
|
|
868
|
+
deny("write");
|
|
869
|
+
});
|
|
870
|
+
var EVENT_GUARD = policy((allow, deny) => {
|
|
871
|
+
allow("observe");
|
|
872
|
+
allow("signal");
|
|
873
|
+
deny("write");
|
|
874
|
+
});
|
|
875
|
+
function cqrsMeta(kind, extra) {
|
|
876
|
+
return { cqrs: true, cqrs_type: kind, ...extra ?? {} };
|
|
877
|
+
}
|
|
878
|
+
function keepalive(n) {
|
|
879
|
+
return n.subscribe(() => {
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
var MemoryEventStore = class {
|
|
883
|
+
_store = /* @__PURE__ */ new Map();
|
|
884
|
+
persist(event) {
|
|
885
|
+
let list = this._store.get(event.type);
|
|
886
|
+
if (!list) {
|
|
887
|
+
list = [];
|
|
888
|
+
this._store.set(event.type, list);
|
|
889
|
+
}
|
|
890
|
+
list.push(event);
|
|
891
|
+
}
|
|
892
|
+
loadEvents(eventType, cursor) {
|
|
893
|
+
const list = this._store.get(eventType) ?? [];
|
|
894
|
+
const sinceTs = cursor?.timestampNs;
|
|
895
|
+
const sinceSeq = cursor?.seq;
|
|
896
|
+
const events = sinceTs == null ? [...list] : list.filter(
|
|
897
|
+
(e) => e.timestampNs > sinceTs || e.timestampNs === sinceTs && e.seq > (sinceSeq ?? -1)
|
|
898
|
+
);
|
|
899
|
+
const lastEvent = events.length > 0 ? events[events.length - 1] : void 0;
|
|
900
|
+
return {
|
|
901
|
+
events,
|
|
902
|
+
cursor: lastEvent ? { timestampNs: lastEvent.timestampNs, seq: lastEvent.seq } : cursor
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
clear() {
|
|
906
|
+
this._store.clear();
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
var CqrsGraph = class extends Graph {
|
|
910
|
+
_eventLogs = /* @__PURE__ */ new Map();
|
|
911
|
+
_commandHandlers = /* @__PURE__ */ new Map();
|
|
912
|
+
_projections = /* @__PURE__ */ new Set();
|
|
913
|
+
_sagas = /* @__PURE__ */ new Set();
|
|
914
|
+
_keepaliveDisposers = [];
|
|
915
|
+
_eventStore;
|
|
916
|
+
_seq = 0;
|
|
917
|
+
constructor(name, opts = {}) {
|
|
918
|
+
super(name, opts.graph);
|
|
919
|
+
}
|
|
920
|
+
destroy() {
|
|
921
|
+
for (const dispose of this._keepaliveDisposers) dispose();
|
|
922
|
+
this._keepaliveDisposers.length = 0;
|
|
923
|
+
super.destroy();
|
|
924
|
+
}
|
|
925
|
+
// -- Events ---------------------------------------------------------------
|
|
926
|
+
/**
|
|
927
|
+
* Register a named event stream backed by `reactiveLog`.
|
|
928
|
+
* Guard denies external `write` — only commands append internally.
|
|
929
|
+
*/
|
|
930
|
+
event(name) {
|
|
931
|
+
const existing = this._eventLogs.get(name);
|
|
932
|
+
if (existing) return existing.node;
|
|
933
|
+
const log = reactiveLog([], { name });
|
|
934
|
+
const entries = log.entries;
|
|
935
|
+
const guarded = derived(
|
|
936
|
+
[entries],
|
|
937
|
+
([snapshot]) => snapshot,
|
|
938
|
+
{
|
|
939
|
+
name,
|
|
940
|
+
describeKind: "state",
|
|
941
|
+
meta: cqrsMeta("event", { event_name: name }),
|
|
942
|
+
guard: EVENT_GUARD,
|
|
943
|
+
initial: entries.get()
|
|
944
|
+
}
|
|
945
|
+
);
|
|
946
|
+
this.add(name, guarded);
|
|
947
|
+
this._keepaliveDisposers.push(keepalive(guarded));
|
|
948
|
+
this._eventLogs.set(name, { log, node: guarded });
|
|
949
|
+
return guarded;
|
|
950
|
+
}
|
|
951
|
+
/** Internal: append to an event log, auto-registering if needed. */
|
|
952
|
+
_appendEvent(eventName, payload) {
|
|
953
|
+
let entry = this._eventLogs.get(eventName);
|
|
954
|
+
if (!entry) {
|
|
955
|
+
this.event(eventName);
|
|
956
|
+
entry = this._eventLogs.get(eventName);
|
|
957
|
+
}
|
|
958
|
+
if (entry.node.status === "completed" || entry.node.status === "errored") {
|
|
959
|
+
throw new Error(
|
|
960
|
+
`Cannot dispatch to terminated event stream "${eventName}" (status: ${entry.node.status}).`
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
const nv = entry.log.entries.v;
|
|
964
|
+
const evt = {
|
|
965
|
+
type: eventName,
|
|
966
|
+
payload,
|
|
967
|
+
timestampNs: wallClockNs(),
|
|
968
|
+
seq: ++this._seq,
|
|
969
|
+
...nv != null ? { v0: { id: nv.id, version: nv.version } } : {}
|
|
970
|
+
};
|
|
971
|
+
entry.log.append(evt);
|
|
972
|
+
if (this._eventStore) {
|
|
973
|
+
this._eventStore.persist(evt);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
// -- Commands -------------------------------------------------------------
|
|
977
|
+
/**
|
|
978
|
+
* Register a command with its handler. Guard denies `observe` (write-only).
|
|
979
|
+
* Use `dispatch(name, payload)` to execute.
|
|
980
|
+
*
|
|
981
|
+
* The command node carries dynamic `meta.error` — a reactive companion
|
|
982
|
+
* that holds the last handler error (or `null` on success).
|
|
983
|
+
*/
|
|
984
|
+
command(name, handler) {
|
|
985
|
+
const cmdNode = state(void 0, {
|
|
986
|
+
name,
|
|
987
|
+
describeKind: "state",
|
|
988
|
+
meta: {
|
|
989
|
+
...cqrsMeta("command", { command_name: name }),
|
|
990
|
+
error: null
|
|
991
|
+
},
|
|
992
|
+
guard: COMMAND_GUARD
|
|
993
|
+
});
|
|
994
|
+
this.add(name, cmdNode);
|
|
995
|
+
this._commandHandlers.set(name, handler);
|
|
996
|
+
return cmdNode;
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Execute a registered command. Wraps the entire dispatch in `batch()` so
|
|
1000
|
+
* the command node DATA and all emitted events settle atomically.
|
|
1001
|
+
*
|
|
1002
|
+
* If the handler throws, `meta.error` on the command node is set to the
|
|
1003
|
+
* error and the exception is re-thrown.
|
|
1004
|
+
*/
|
|
1005
|
+
dispatch(commandName, payload) {
|
|
1006
|
+
const handler = this._commandHandlers.get(commandName);
|
|
1007
|
+
if (!handler) {
|
|
1008
|
+
throw new Error(`Unknown command: "${commandName}". Register with .command() first.`);
|
|
1009
|
+
}
|
|
1010
|
+
const cmdNode = this.resolve(commandName);
|
|
1011
|
+
batch(() => {
|
|
1012
|
+
cmdNode.down([[DATA, payload]], { internal: true });
|
|
1013
|
+
try {
|
|
1014
|
+
handler(payload, { emit: (eName, data) => this._appendEvent(eName, data) });
|
|
1015
|
+
cmdNode.meta.error.down([[DATA, null]], { internal: true });
|
|
1016
|
+
} catch (err) {
|
|
1017
|
+
cmdNode.meta.error.down([[DATA, err]], { internal: true });
|
|
1018
|
+
throw err;
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
// -- Projections ----------------------------------------------------------
|
|
1023
|
+
/**
|
|
1024
|
+
* Register a read-only projection derived from event streams.
|
|
1025
|
+
* Guard denies `write` — value is computed from events only.
|
|
1026
|
+
*
|
|
1027
|
+
* **Purity contract:** The `reducer` must be a pure function — it receives
|
|
1028
|
+
* the original `initial` on every invocation (full event-sourcing replay).
|
|
1029
|
+
* Never mutate `initial`; always return a new value.
|
|
1030
|
+
*/
|
|
1031
|
+
projection(name, eventNames, reducer, initial) {
|
|
1032
|
+
const eventNodes = eventNames.map((eName) => {
|
|
1033
|
+
if (!this._eventLogs.has(eName)) this.event(eName);
|
|
1034
|
+
return this._eventLogs.get(eName).node;
|
|
1035
|
+
});
|
|
1036
|
+
const projNode = derived(
|
|
1037
|
+
eventNodes,
|
|
1038
|
+
(snapshots) => {
|
|
1039
|
+
const allEvents = [];
|
|
1040
|
+
for (const snapshot of snapshots) {
|
|
1041
|
+
const snap = snapshot;
|
|
1042
|
+
allEvents.push(...snap.value.entries);
|
|
1043
|
+
}
|
|
1044
|
+
allEvents.sort((a, b) => a.timestampNs - b.timestampNs || a.seq - b.seq);
|
|
1045
|
+
return reducer(initial, allEvents);
|
|
1046
|
+
},
|
|
1047
|
+
{
|
|
1048
|
+
name,
|
|
1049
|
+
describeKind: "derived",
|
|
1050
|
+
meta: cqrsMeta("projection", { projection_name: name, source_events: eventNames }),
|
|
1051
|
+
guard: PROJECTION_GUARD,
|
|
1052
|
+
initial
|
|
1053
|
+
}
|
|
1054
|
+
);
|
|
1055
|
+
this.add(name, projNode);
|
|
1056
|
+
for (const eName of eventNames) this.connect(eName, name);
|
|
1057
|
+
this._keepaliveDisposers.push(keepalive(projNode));
|
|
1058
|
+
this._projections.add(name);
|
|
1059
|
+
return projNode;
|
|
1060
|
+
}
|
|
1061
|
+
// -- Sagas ----------------------------------------------------------------
|
|
1062
|
+
/**
|
|
1063
|
+
* Register an event-driven side effect. Runs handler for each **new** event
|
|
1064
|
+
* from the specified streams (tracks last-processed entry count per stream).
|
|
1065
|
+
*
|
|
1066
|
+
* The saga node carries dynamic `meta.error` — a reactive companion that
|
|
1067
|
+
* holds the last handler error (or `null` on success). Handler errors do
|
|
1068
|
+
* not propagate out of the saga run (the event cursor still advances so
|
|
1069
|
+
* the same entry is not delivered twice).
|
|
1070
|
+
*/
|
|
1071
|
+
saga(name, eventNames, handler) {
|
|
1072
|
+
const eventNodes = eventNames.map((eName) => {
|
|
1073
|
+
if (!this._eventLogs.has(eName)) this.event(eName);
|
|
1074
|
+
return this._eventLogs.get(eName).node;
|
|
1075
|
+
});
|
|
1076
|
+
const lastCounts = /* @__PURE__ */ new Map();
|
|
1077
|
+
const sagaRef = {};
|
|
1078
|
+
const sagaNode = node(
|
|
1079
|
+
eventNodes,
|
|
1080
|
+
(snapshots) => {
|
|
1081
|
+
const errNode = sagaRef.n.meta.error;
|
|
1082
|
+
for (let i = 0; i < snapshots.length; i++) {
|
|
1083
|
+
const snap = snapshots[i];
|
|
1084
|
+
const eName = eventNames[i];
|
|
1085
|
+
const entries = snap.value.entries;
|
|
1086
|
+
const lastCount = lastCounts.get(eName) ?? 0;
|
|
1087
|
+
if (entries.length > lastCount) {
|
|
1088
|
+
const newEntries = entries.slice(lastCount);
|
|
1089
|
+
for (const entry of newEntries) {
|
|
1090
|
+
try {
|
|
1091
|
+
handler(entry);
|
|
1092
|
+
errNode.down([[DATA, null]], { internal: true });
|
|
1093
|
+
} catch (err) {
|
|
1094
|
+
errNode.down([[DATA, err]], { internal: true });
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
lastCounts.set(eName, entries.length);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
},
|
|
1101
|
+
{
|
|
1102
|
+
name,
|
|
1103
|
+
describeKind: "effect",
|
|
1104
|
+
meta: {
|
|
1105
|
+
...cqrsMeta("saga", { saga_name: name, source_events: eventNames }),
|
|
1106
|
+
error: null
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
);
|
|
1110
|
+
sagaRef.n = sagaNode;
|
|
1111
|
+
this.add(name, sagaNode);
|
|
1112
|
+
for (const eName of eventNames) this.connect(eName, name);
|
|
1113
|
+
this._keepaliveDisposers.push(keepalive(sagaNode));
|
|
1114
|
+
this._sagas.add(name);
|
|
1115
|
+
return sagaNode;
|
|
1116
|
+
}
|
|
1117
|
+
// -- Event store ----------------------------------------------------------
|
|
1118
|
+
useEventStore(adapter) {
|
|
1119
|
+
this._eventStore = adapter;
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* Replay persisted events through a reducer to rebuild a read model.
|
|
1123
|
+
* Requires an event store adapter wired via `useEventStore()`.
|
|
1124
|
+
*/
|
|
1125
|
+
async rebuildProjection(eventNames, reducer, initial) {
|
|
1126
|
+
if (!this._eventStore) {
|
|
1127
|
+
throw new Error("No event store wired. Call useEventStore() first.");
|
|
1128
|
+
}
|
|
1129
|
+
const allEvents = [];
|
|
1130
|
+
for (const eName of eventNames) {
|
|
1131
|
+
const result = await this._eventStore.loadEvents(eName);
|
|
1132
|
+
allEvents.push(...result.events);
|
|
1133
|
+
}
|
|
1134
|
+
allEvents.sort((a, b) => a.timestampNs - b.timestampNs || a.seq - b.seq);
|
|
1135
|
+
return reducer(initial, allEvents);
|
|
1136
|
+
}
|
|
1137
|
+
};
|
|
1138
|
+
function cqrs(name, opts) {
|
|
1139
|
+
return new CqrsGraph(name, opts);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// src/compat/nestjs/module.ts
|
|
1143
|
+
var GraphReflyRootLifecycle = class {
|
|
1144
|
+
constructor(graph) {
|
|
1145
|
+
this.graph = graph;
|
|
1146
|
+
}
|
|
1147
|
+
onModuleDestroy() {
|
|
1148
|
+
this.graph.destroy();
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
1151
|
+
var GraphReflyRequestLifecycle = class {
|
|
1152
|
+
graph = new Graph("request");
|
|
1153
|
+
onModuleDestroy() {
|
|
1154
|
+
this.graph.destroy();
|
|
1155
|
+
}
|
|
1156
|
+
};
|
|
1157
|
+
var _GraphReflyModule_decorators, _init;
|
|
1158
|
+
_GraphReflyModule_decorators = [Module({})];
|
|
1159
|
+
var _GraphReflyModule = class _GraphReflyModule {
|
|
1160
|
+
/**
|
|
1161
|
+
* Register the root `Graph` singleton in the NestJS DI container.
|
|
1162
|
+
*
|
|
1163
|
+
* The root graph is `@Global()` — injectable everywhere without importing
|
|
1164
|
+
* the module again. Use `@InjectGraph()` to inject it.
|
|
1165
|
+
*
|
|
1166
|
+
* Lifecycle:
|
|
1167
|
+
* - **init:** Graph created in factory. If `build` is provided, it runs
|
|
1168
|
+
* first (registers nodes/mounts). If `snapshot` is provided, values
|
|
1169
|
+
* are restored via `graph.restore()`.
|
|
1170
|
+
* - **destroy:** Calls `graph.destroy()` — sends `[[TEARDOWN]]` to all
|
|
1171
|
+
* nodes, including mounted feature subgraphs (cascading teardown).
|
|
1172
|
+
*/
|
|
1173
|
+
static forRoot(opts) {
|
|
1174
|
+
const options = opts ?? {};
|
|
1175
|
+
const graphName = options.name ?? "root";
|
|
1176
|
+
const providers = [
|
|
1177
|
+
{
|
|
1178
|
+
provide: GRAPHREFLY_ROOT_GRAPH,
|
|
1179
|
+
useFactory: () => {
|
|
1180
|
+
const g = new Graph(graphName);
|
|
1181
|
+
if (options.build) options.build(g);
|
|
1182
|
+
if (options.snapshot) g.restore(options.snapshot);
|
|
1183
|
+
return g;
|
|
1184
|
+
}
|
|
1185
|
+
},
|
|
1186
|
+
{
|
|
1187
|
+
provide: /* @__PURE__ */ Symbol.for("graphrefly:root-lifecycle"),
|
|
1188
|
+
useFactory: (graph) => new GraphReflyRootLifecycle(graph),
|
|
1189
|
+
inject: [GRAPHREFLY_ROOT_GRAPH]
|
|
1190
|
+
},
|
|
1191
|
+
{
|
|
1192
|
+
provide: GraphReflyEventExplorer,
|
|
1193
|
+
useFactory: (graph, moduleRef) => new GraphReflyEventExplorer(graph, moduleRef),
|
|
1194
|
+
inject: [GRAPHREFLY_ROOT_GRAPH, ModuleRef]
|
|
1195
|
+
}
|
|
1196
|
+
];
|
|
1197
|
+
if (options.nodes) {
|
|
1198
|
+
for (const path of options.nodes) {
|
|
1199
|
+
providers.push({
|
|
1200
|
+
provide: getNodeToken(path),
|
|
1201
|
+
useFactory: (graph) => graph.resolve(path),
|
|
1202
|
+
inject: [GRAPHREFLY_ROOT_GRAPH]
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
if (options.requestScope) {
|
|
1207
|
+
providers.push(
|
|
1208
|
+
{
|
|
1209
|
+
provide: /* @__PURE__ */ Symbol.for("graphrefly:request-lifecycle"),
|
|
1210
|
+
useFactory: () => new GraphReflyRequestLifecycle(),
|
|
1211
|
+
scope: Scope.REQUEST
|
|
1212
|
+
},
|
|
1213
|
+
{
|
|
1214
|
+
provide: GRAPHREFLY_REQUEST_GRAPH,
|
|
1215
|
+
useFactory: (lifecycle) => lifecycle.graph,
|
|
1216
|
+
inject: [/* @__PURE__ */ Symbol.for("graphrefly:request-lifecycle")],
|
|
1217
|
+
scope: Scope.REQUEST
|
|
1218
|
+
}
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
return {
|
|
1222
|
+
module: _GraphReflyModule,
|
|
1223
|
+
global: true,
|
|
1224
|
+
providers,
|
|
1225
|
+
exports: [
|
|
1226
|
+
GRAPHREFLY_ROOT_GRAPH,
|
|
1227
|
+
...(options.nodes ?? []).map(getNodeToken),
|
|
1228
|
+
...options.requestScope ? [GRAPHREFLY_REQUEST_GRAPH] : []
|
|
1229
|
+
]
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Register a feature subgraph that auto-mounts into the root graph.
|
|
1234
|
+
*
|
|
1235
|
+
* The feature graph is created in the factory, built/restored, then
|
|
1236
|
+
* mounted into root via `root.mount(name, featureGraph)`. On app
|
|
1237
|
+
* shutdown, root's `graph.destroy()` cascades TEARDOWN through all
|
|
1238
|
+
* mounted subgraphs (no explicit remove needed).
|
|
1239
|
+
*
|
|
1240
|
+
* Node tokens are auto-qualified as `featureName::path` to prevent
|
|
1241
|
+
* collisions between features declaring nodes with the same local name.
|
|
1242
|
+
*
|
|
1243
|
+
* Injectable via `@InjectGraph(name)`.
|
|
1244
|
+
*/
|
|
1245
|
+
static forFeature(opts) {
|
|
1246
|
+
const providers = [
|
|
1247
|
+
{
|
|
1248
|
+
provide: getGraphToken(opts.name),
|
|
1249
|
+
useFactory: (rootGraph) => {
|
|
1250
|
+
const g = new Graph(opts.name);
|
|
1251
|
+
if (opts.build) opts.build(g);
|
|
1252
|
+
if (opts.snapshot) g.restore(opts.snapshot);
|
|
1253
|
+
rootGraph.mount(opts.name, g);
|
|
1254
|
+
return g;
|
|
1255
|
+
},
|
|
1256
|
+
inject: [GRAPHREFLY_ROOT_GRAPH]
|
|
1257
|
+
}
|
|
1258
|
+
];
|
|
1259
|
+
if (opts.nodes) {
|
|
1260
|
+
for (const path of opts.nodes) {
|
|
1261
|
+
providers.push({
|
|
1262
|
+
provide: getNodeToken(`${opts.name}::${path}`),
|
|
1263
|
+
useFactory: (graph) => graph.resolve(path),
|
|
1264
|
+
inject: [getGraphToken(opts.name)]
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
return {
|
|
1269
|
+
module: _GraphReflyModule,
|
|
1270
|
+
providers,
|
|
1271
|
+
exports: [
|
|
1272
|
+
getGraphToken(opts.name),
|
|
1273
|
+
...(opts.nodes ?? []).map((p) => getNodeToken(`${opts.name}::${p}`))
|
|
1274
|
+
]
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Register a CQRS subgraph that auto-mounts into the root graph.
|
|
1279
|
+
*
|
|
1280
|
+
* Creates a `CqrsGraph` via the `cqrs()` factory (roadmap §4.5), mounts it
|
|
1281
|
+
* into the root graph, and exposes it for DI via `@InjectGraph(name)`.
|
|
1282
|
+
*
|
|
1283
|
+
* CQRS decorators (`@CommandHandler`, `@EventHandler`, `@QueryHandler`,
|
|
1284
|
+
* `@SagaHandler`) are discovered by the explorer and wired to this graph
|
|
1285
|
+
* on module init.
|
|
1286
|
+
*
|
|
1287
|
+
* @example
|
|
1288
|
+
* ```ts
|
|
1289
|
+
* GraphReflyModule.forCqrs({
|
|
1290
|
+
* name: "orders",
|
|
1291
|
+
* build: (g) => {
|
|
1292
|
+
* g.event("orderPlaced");
|
|
1293
|
+
* g.projection("orderCount", ["orderPlaced"], (_s, evts) => evts.length, 0);
|
|
1294
|
+
* },
|
|
1295
|
+
* })
|
|
1296
|
+
* ```
|
|
1297
|
+
*/
|
|
1298
|
+
static forCqrs(opts) {
|
|
1299
|
+
const providers = [
|
|
1300
|
+
{
|
|
1301
|
+
provide: getGraphToken(opts.name),
|
|
1302
|
+
useFactory: (rootGraph) => {
|
|
1303
|
+
const g = cqrs(opts.name, opts.cqrs);
|
|
1304
|
+
if (opts.eventStore) g.useEventStore(opts.eventStore);
|
|
1305
|
+
if (opts.build) opts.build(g);
|
|
1306
|
+
rootGraph.mount(opts.name, g);
|
|
1307
|
+
return g;
|
|
1308
|
+
},
|
|
1309
|
+
inject: [GRAPHREFLY_ROOT_GRAPH]
|
|
1310
|
+
}
|
|
1311
|
+
];
|
|
1312
|
+
if (opts.nodes) {
|
|
1313
|
+
for (const path of opts.nodes) {
|
|
1314
|
+
providers.push({
|
|
1315
|
+
provide: getNodeToken(`${opts.name}::${path}`),
|
|
1316
|
+
useFactory: (graph) => graph.resolve(path),
|
|
1317
|
+
inject: [getGraphToken(opts.name)]
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
return {
|
|
1322
|
+
module: _GraphReflyModule,
|
|
1323
|
+
providers,
|
|
1324
|
+
exports: [
|
|
1325
|
+
getGraphToken(opts.name),
|
|
1326
|
+
...(opts.nodes ?? []).map((p) => getNodeToken(`${opts.name}::${p}`))
|
|
1327
|
+
]
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
1331
|
+
_init = __decoratorStart(null);
|
|
1332
|
+
_GraphReflyModule = __decorateElement(_init, 0, "GraphReflyModule", _GraphReflyModule_decorators, _GraphReflyModule);
|
|
1333
|
+
__runInitializers(_init, 1, _GraphReflyModule);
|
|
1334
|
+
var GraphReflyModule = _GraphReflyModule;
|
|
1335
|
+
|
|
1336
|
+
export {
|
|
1337
|
+
GRAPHREFLY_ROOT_GRAPH,
|
|
1338
|
+
GRAPHREFLY_REQUEST_GRAPH,
|
|
1339
|
+
getGraphToken,
|
|
1340
|
+
getNodeToken,
|
|
1341
|
+
EVENT_HANDLERS,
|
|
1342
|
+
INTERVAL_HANDLERS,
|
|
1343
|
+
CRON_HANDLERS,
|
|
1344
|
+
COMMAND_HANDLERS,
|
|
1345
|
+
CQRS_EVENT_HANDLERS,
|
|
1346
|
+
QUERY_HANDLERS,
|
|
1347
|
+
SAGA_HANDLERS,
|
|
1348
|
+
InjectGraph,
|
|
1349
|
+
InjectCqrsGraph,
|
|
1350
|
+
InjectNode,
|
|
1351
|
+
OnGraphEvent,
|
|
1352
|
+
GraphInterval,
|
|
1353
|
+
GraphCron,
|
|
1354
|
+
CommandHandler,
|
|
1355
|
+
EventHandler,
|
|
1356
|
+
QueryHandler,
|
|
1357
|
+
SagaHandler,
|
|
1358
|
+
GraphReflyEventExplorer,
|
|
1359
|
+
observeSSE,
|
|
1360
|
+
observeSubscription,
|
|
1361
|
+
ObserveGateway,
|
|
1362
|
+
ACTOR_KEY,
|
|
1363
|
+
fromJwtPayload,
|
|
1364
|
+
fromHeader,
|
|
1365
|
+
getActor,
|
|
1366
|
+
GraphReflyGuardImpl,
|
|
1367
|
+
GraphReflyGuard,
|
|
1368
|
+
cqrs_exports,
|
|
1369
|
+
GraphReflyModule,
|
|
1370
|
+
nestjs_exports
|
|
1371
|
+
};
|
|
1372
|
+
//# sourceMappingURL=chunk-QW7H3ICI.js.map
|