@rotorsoft/act 0.32.3 → 0.32.5
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/dist/.tsbuildinfo +1 -1
- package/dist/@types/act.d.ts +71 -47
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/InMemoryCache.d.ts +1 -2
- package/dist/@types/adapters/InMemoryCache.d.ts.map +1 -1
- package/dist/@types/{act-builder.d.ts → builders/act-builder.d.ts} +5 -5
- package/dist/@types/builders/act-builder.d.ts.map +1 -0
- package/dist/@types/builders/index.d.ts +13 -0
- package/dist/@types/builders/index.d.ts.map +1 -0
- package/dist/@types/{projection-builder.d.ts → builders/projection-builder.d.ts} +3 -3
- package/dist/@types/builders/projection-builder.d.ts.map +1 -0
- package/dist/@types/{slice-builder.d.ts → builders/slice-builder.d.ts} +2 -2
- package/dist/@types/builders/slice-builder.d.ts.map +1 -0
- package/dist/@types/{state-builder.d.ts → builders/state-builder.d.ts} +1 -1
- package/dist/@types/builders/state-builder.d.ts.map +1 -0
- package/dist/@types/config.d.ts.map +1 -1
- package/dist/@types/index.d.ts +1 -4
- package/dist/@types/index.d.ts.map +1 -1
- package/dist/@types/internal/close-cycle.d.ts +38 -0
- package/dist/@types/internal/close-cycle.d.ts.map +1 -0
- package/dist/@types/internal/drain-cycle.d.ts +61 -0
- package/dist/@types/internal/drain-cycle.d.ts.map +1 -0
- package/dist/@types/internal/drain-ratio.d.ts +26 -0
- package/dist/@types/internal/drain-ratio.d.ts.map +1 -0
- package/dist/@types/internal/event-sourcing.d.ts +14 -0
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/@types/internal/index.d.ts +5 -1
- package/dist/@types/internal/index.d.ts.map +1 -1
- package/dist/@types/internal/lru-map.d.ts +50 -0
- package/dist/@types/internal/lru-map.d.ts.map +1 -0
- package/dist/@types/internal/merge.d.ts +13 -1
- package/dist/@types/internal/merge.d.ts.map +1 -1
- package/dist/@types/internal/tracing.d.ts +9 -2
- package/dist/@types/internal/tracing.d.ts.map +1 -1
- package/dist/@types/ports.d.ts.map +1 -1
- package/dist/@types/types/reaction.d.ts +7 -1
- package/dist/@types/types/reaction.d.ts.map +1 -1
- package/dist/index.cjs +581 -416
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +580 -416
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/@types/act-builder.d.ts.map +0 -1
- package/dist/@types/projection-builder.d.ts.map +0 -1
- package/dist/@types/slice-builder.d.ts.map +0 -1
- package/dist/@types/state-builder.d.ts.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -36,6 +36,7 @@ __export(index_exports, {
|
|
|
36
36
|
CommittedMetaSchema: () => CommittedMetaSchema,
|
|
37
37
|
ConcurrencyError: () => ConcurrencyError,
|
|
38
38
|
ConsoleLogger: () => ConsoleLogger,
|
|
39
|
+
DEFAULT_MAX_SUBSCRIBED_STREAMS: () => DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
39
40
|
Environments: () => Environments,
|
|
40
41
|
Errors: () => Errors,
|
|
41
42
|
EventMetaSchema: () => EventMetaSchema,
|
|
@@ -171,26 +172,75 @@ var ConsoleLogger = class _ConsoleLogger {
|
|
|
171
172
|
}
|
|
172
173
|
};
|
|
173
174
|
|
|
175
|
+
// src/internal/lru-map.ts
|
|
176
|
+
var LruMap = class {
|
|
177
|
+
constructor(_maxSize) {
|
|
178
|
+
this._maxSize = _maxSize;
|
|
179
|
+
}
|
|
180
|
+
_entries = /* @__PURE__ */ new Map();
|
|
181
|
+
get(key) {
|
|
182
|
+
const v = this._entries.get(key);
|
|
183
|
+
if (v === void 0) return void 0;
|
|
184
|
+
this._entries.delete(key);
|
|
185
|
+
this._entries.set(key, v);
|
|
186
|
+
return v;
|
|
187
|
+
}
|
|
188
|
+
has(key) {
|
|
189
|
+
return this._entries.has(key);
|
|
190
|
+
}
|
|
191
|
+
set(key, value) {
|
|
192
|
+
this._entries.delete(key);
|
|
193
|
+
if (this._entries.size >= this._maxSize) {
|
|
194
|
+
const oldest = this._entries.keys().next().value;
|
|
195
|
+
if (oldest !== void 0) this._entries.delete(oldest);
|
|
196
|
+
}
|
|
197
|
+
this._entries.set(key, value);
|
|
198
|
+
}
|
|
199
|
+
delete(key) {
|
|
200
|
+
return this._entries.delete(key);
|
|
201
|
+
}
|
|
202
|
+
clear() {
|
|
203
|
+
this._entries.clear();
|
|
204
|
+
}
|
|
205
|
+
get size() {
|
|
206
|
+
return this._entries.size;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
var LruSet = class {
|
|
210
|
+
_map;
|
|
211
|
+
constructor(maxSize) {
|
|
212
|
+
this._map = new LruMap(maxSize);
|
|
213
|
+
}
|
|
214
|
+
has(value) {
|
|
215
|
+
return this._map.has(value);
|
|
216
|
+
}
|
|
217
|
+
add(value) {
|
|
218
|
+
this._map.set(value, true);
|
|
219
|
+
}
|
|
220
|
+
delete(value) {
|
|
221
|
+
return this._map.delete(value);
|
|
222
|
+
}
|
|
223
|
+
clear() {
|
|
224
|
+
this._map.clear();
|
|
225
|
+
}
|
|
226
|
+
get size() {
|
|
227
|
+
return this._map.size;
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
174
231
|
// src/adapters/InMemoryCache.ts
|
|
175
232
|
var InMemoryCache = class {
|
|
176
|
-
|
|
177
|
-
|
|
233
|
+
// CacheEntry<any> lets `get<TState>` and `set<TState>` flow without casts:
|
|
234
|
+
// any is bidirectionally compatible with the per-call TState binding, while
|
|
235
|
+
// the public Cache interface still presents a typed surface to callers.
|
|
236
|
+
_entries;
|
|
178
237
|
constructor(options) {
|
|
179
|
-
this.
|
|
238
|
+
this._entries = new LruMap(options?.maxSize ?? 1e3);
|
|
180
239
|
}
|
|
181
240
|
async get(stream) {
|
|
182
|
-
|
|
183
|
-
if (!entry) return void 0;
|
|
184
|
-
this._entries.delete(stream);
|
|
185
|
-
this._entries.set(stream, entry);
|
|
186
|
-
return entry;
|
|
241
|
+
return this._entries.get(stream);
|
|
187
242
|
}
|
|
188
243
|
async set(stream, entry) {
|
|
189
|
-
this._entries.delete(stream);
|
|
190
|
-
if (this._entries.size >= this._maxSize) {
|
|
191
|
-
const first = this._entries.keys().next().value;
|
|
192
|
-
this._entries.delete(first);
|
|
193
|
-
}
|
|
194
244
|
this._entries.set(stream, entry);
|
|
195
245
|
}
|
|
196
246
|
async invalidate(stream) {
|
|
@@ -345,12 +395,19 @@ var BaseSchema = PackageSchema.extend({
|
|
|
345
395
|
});
|
|
346
396
|
var { NODE_ENV, LOG_LEVEL, LOG_SINGLE_LINE, SLEEP_MS } = process.env;
|
|
347
397
|
var env = NODE_ENV || "development";
|
|
348
|
-
var logLevel = LOG_LEVEL || (NODE_ENV === "test" ? "
|
|
398
|
+
var logLevel = LOG_LEVEL || (NODE_ENV === "test" ? "fatal" : NODE_ENV === "production" ? "info" : "trace");
|
|
349
399
|
var logSingleLine = (LOG_SINGLE_LINE || "true") === "true";
|
|
350
400
|
var sleepMs = parseInt(NODE_ENV === "test" ? "0" : SLEEP_MS ?? "100", 10);
|
|
351
401
|
var pkg = getPackage();
|
|
402
|
+
var _validated;
|
|
352
403
|
var config = () => {
|
|
353
|
-
|
|
404
|
+
if (!_validated) {
|
|
405
|
+
_validated = extend(
|
|
406
|
+
{ ...pkg, env, logLevel, logSingleLine, sleepMs },
|
|
407
|
+
BaseSchema
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
return _validated;
|
|
354
411
|
};
|
|
355
412
|
|
|
356
413
|
// src/utils.ts
|
|
@@ -773,7 +830,7 @@ function port(injector) {
|
|
|
773
830
|
if (!adapters.has(injector.name)) {
|
|
774
831
|
const injected = injector(adapter);
|
|
775
832
|
adapters.set(injector.name, injected);
|
|
776
|
-
|
|
833
|
+
log().info(`[act] + ${injector.name}:${injected.constructor.name}`);
|
|
777
834
|
}
|
|
778
835
|
return adapters.get(injector.name);
|
|
779
836
|
};
|
|
@@ -799,7 +856,7 @@ async function disposeAndExit(code = "EXIT") {
|
|
|
799
856
|
}
|
|
800
857
|
for (const adapter of [...adapters.values()].reverse()) {
|
|
801
858
|
await adapter.dispose();
|
|
802
|
-
|
|
859
|
+
log().info(`[act] - ${adapter.constructor.name}`);
|
|
803
860
|
}
|
|
804
861
|
adapters.clear();
|
|
805
862
|
config().env !== "test" && process.exit(code === "ERROR" ? 1 : 0);
|
|
@@ -830,9 +887,233 @@ process.once("unhandledRejection", async (arg) => {
|
|
|
830
887
|
});
|
|
831
888
|
|
|
832
889
|
// src/act.ts
|
|
833
|
-
var import_crypto2 = require("crypto");
|
|
834
890
|
var import_events = __toESM(require("events"), 1);
|
|
835
891
|
|
|
892
|
+
// src/internal/close-cycle.ts
|
|
893
|
+
var import_crypto = require("crypto");
|
|
894
|
+
async function runCloseCycle(targets, deps) {
|
|
895
|
+
if (!targets.length) return { truncated: /* @__PURE__ */ new Map(), skipped: [] };
|
|
896
|
+
const targetMap = new Map(targets.map((t) => [t.stream, t]));
|
|
897
|
+
const streams = [...targetMap.keys()];
|
|
898
|
+
const skipped = [];
|
|
899
|
+
const streamInfo = await scanStreamHeads(streams);
|
|
900
|
+
const safe = await partitionBySafety(
|
|
901
|
+
streamInfo,
|
|
902
|
+
deps.reactiveEventsSize,
|
|
903
|
+
skipped
|
|
904
|
+
);
|
|
905
|
+
if (!safe.length) return { truncated: /* @__PURE__ */ new Map(), skipped };
|
|
906
|
+
const correlation = (0, import_crypto.randomUUID)();
|
|
907
|
+
const { guarded, guardEvents } = await guardWithTombstones(
|
|
908
|
+
safe,
|
|
909
|
+
streamInfo,
|
|
910
|
+
correlation,
|
|
911
|
+
deps.tombstone,
|
|
912
|
+
skipped
|
|
913
|
+
);
|
|
914
|
+
if (!guarded.length) return { truncated: /* @__PURE__ */ new Map(), skipped };
|
|
915
|
+
const seedStates = await loadRestartSeeds(
|
|
916
|
+
guarded,
|
|
917
|
+
targetMap,
|
|
918
|
+
streamInfo,
|
|
919
|
+
deps.eventToState,
|
|
920
|
+
deps.load,
|
|
921
|
+
deps.logger
|
|
922
|
+
);
|
|
923
|
+
await runArchiveCallbacks(guarded, targetMap);
|
|
924
|
+
const truncated = await truncateAndWarmCache(
|
|
925
|
+
guarded,
|
|
926
|
+
seedStates,
|
|
927
|
+
guardEvents,
|
|
928
|
+
correlation
|
|
929
|
+
);
|
|
930
|
+
return { truncated, skipped };
|
|
931
|
+
}
|
|
932
|
+
async function scanStreamHeads(streams) {
|
|
933
|
+
const out = /* @__PURE__ */ new Map();
|
|
934
|
+
await Promise.all(
|
|
935
|
+
streams.map(async (s) => {
|
|
936
|
+
let maxId = -1;
|
|
937
|
+
let version = -1;
|
|
938
|
+
let lastEventName;
|
|
939
|
+
await store().query(
|
|
940
|
+
(e) => {
|
|
941
|
+
if (e.name === TOMBSTONE_EVENT) return;
|
|
942
|
+
if (maxId === -1) {
|
|
943
|
+
maxId = e.id;
|
|
944
|
+
version = e.version;
|
|
945
|
+
}
|
|
946
|
+
if (e.name !== SNAP_EVENT && lastEventName === void 0) {
|
|
947
|
+
lastEventName = e.name;
|
|
948
|
+
}
|
|
949
|
+
},
|
|
950
|
+
// limit: 2 covers the typical snapshot-at-head case (snapshot is
|
|
951
|
+
// always preceded by the domain event it captured). Streams with
|
|
952
|
+
// unusual layouts fall back to no-seed via the lookup miss path.
|
|
953
|
+
{ stream: s, stream_exact: true, backward: true, limit: 2 }
|
|
954
|
+
);
|
|
955
|
+
if (maxId >= 0) out.set(s, { maxId, version, lastEventName });
|
|
956
|
+
})
|
|
957
|
+
);
|
|
958
|
+
return out;
|
|
959
|
+
}
|
|
960
|
+
async function partitionBySafety(streamInfo, reactiveEventsSize, skipped) {
|
|
961
|
+
if (reactiveEventsSize === 0) return [...streamInfo.keys()];
|
|
962
|
+
const pendingSet = /* @__PURE__ */ new Set();
|
|
963
|
+
await store().query_streams((position) => {
|
|
964
|
+
const sourceRe = position.source ? RegExp(position.source) : void 0;
|
|
965
|
+
for (const [stream, info] of streamInfo) {
|
|
966
|
+
if ((!sourceRe || sourceRe.test(stream)) && position.at < info.maxId) {
|
|
967
|
+
pendingSet.add(stream);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
const safe = [];
|
|
972
|
+
for (const [stream] of streamInfo) {
|
|
973
|
+
if (pendingSet.has(stream)) skipped.push(stream);
|
|
974
|
+
else safe.push(stream);
|
|
975
|
+
}
|
|
976
|
+
return safe;
|
|
977
|
+
}
|
|
978
|
+
async function guardWithTombstones(safe, streamInfo, correlation, tombstone2, skipped) {
|
|
979
|
+
const guarded = [];
|
|
980
|
+
const guardEvents = /* @__PURE__ */ new Map();
|
|
981
|
+
await Promise.all(
|
|
982
|
+
safe.map(async (stream) => {
|
|
983
|
+
const info = streamInfo.get(stream);
|
|
984
|
+
const committed = await tombstone2(stream, info.version, correlation);
|
|
985
|
+
if (committed) {
|
|
986
|
+
guarded.push(stream);
|
|
987
|
+
guardEvents.set(stream, { id: committed.id, stream });
|
|
988
|
+
} else {
|
|
989
|
+
skipped.push(stream);
|
|
990
|
+
}
|
|
991
|
+
})
|
|
992
|
+
);
|
|
993
|
+
return { guarded, guardEvents };
|
|
994
|
+
}
|
|
995
|
+
async function loadRestartSeeds(guarded, targetMap, streamInfo, eventToState, load2, logger) {
|
|
996
|
+
const seedStates = /* @__PURE__ */ new Map();
|
|
997
|
+
await Promise.all(
|
|
998
|
+
guarded.filter((s) => targetMap.get(s)?.restart).map(async (stream) => {
|
|
999
|
+
const lastEventName = streamInfo.get(stream)?.lastEventName;
|
|
1000
|
+
const ownerState = lastEventName ? eventToState.get(lastEventName) : void 0;
|
|
1001
|
+
if (!ownerState) {
|
|
1002
|
+
logger.error(
|
|
1003
|
+
`Cannot seed restart for "${stream}": no registered state owns event "${lastEventName ?? "<none>"}". Stream will be tombstoned instead.`
|
|
1004
|
+
);
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
const snap2 = await load2(ownerState, stream);
|
|
1008
|
+
seedStates.set(stream, snap2.state);
|
|
1009
|
+
})
|
|
1010
|
+
);
|
|
1011
|
+
return seedStates;
|
|
1012
|
+
}
|
|
1013
|
+
async function runArchiveCallbacks(guarded, targetMap) {
|
|
1014
|
+
for (const stream of guarded) {
|
|
1015
|
+
const archiveFn = targetMap.get(stream)?.archive;
|
|
1016
|
+
if (archiveFn) await archiveFn();
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
async function truncateAndWarmCache(guarded, seedStates, guardEvents, correlation) {
|
|
1020
|
+
const truncTargets = guarded.map((stream) => {
|
|
1021
|
+
const snapshot = seedStates.get(stream);
|
|
1022
|
+
const guard = guardEvents.get(stream);
|
|
1023
|
+
return {
|
|
1024
|
+
stream,
|
|
1025
|
+
snapshot,
|
|
1026
|
+
meta: {
|
|
1027
|
+
correlation,
|
|
1028
|
+
causation: {
|
|
1029
|
+
event: { id: guard.id, name: TOMBSTONE_EVENT, stream: guard.stream }
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
});
|
|
1034
|
+
const truncated = await store().truncate(truncTargets);
|
|
1035
|
+
await Promise.all(
|
|
1036
|
+
guarded.map(async (stream) => {
|
|
1037
|
+
const entry = truncated.get(stream);
|
|
1038
|
+
const state2 = seedStates.get(stream);
|
|
1039
|
+
if (state2 && entry) {
|
|
1040
|
+
await cache().set(stream, {
|
|
1041
|
+
state: state2,
|
|
1042
|
+
version: entry.committed.version,
|
|
1043
|
+
event_id: entry.committed.id,
|
|
1044
|
+
patches: 0,
|
|
1045
|
+
snaps: 1
|
|
1046
|
+
});
|
|
1047
|
+
} else {
|
|
1048
|
+
await cache().invalidate(stream);
|
|
1049
|
+
}
|
|
1050
|
+
})
|
|
1051
|
+
);
|
|
1052
|
+
return truncated;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// src/internal/drain-cycle.ts
|
|
1056
|
+
var import_crypto2 = require("crypto");
|
|
1057
|
+
async function runDrainCycle(ops, registry, batchHandlers, handle, handleBatch, lagging, leading, eventLimit, leaseMillis) {
|
|
1058
|
+
const leased = await ops.claim(lagging, leading, (0, import_crypto2.randomUUID)(), leaseMillis);
|
|
1059
|
+
if (!leased.length) return void 0;
|
|
1060
|
+
const fetched = await ops.fetch(leased, eventLimit);
|
|
1061
|
+
const fetchMap = /* @__PURE__ */ new Map();
|
|
1062
|
+
const fetch_window_at = fetched.reduce(
|
|
1063
|
+
(max, { at, events }) => Math.max(max, events.at(-1)?.id || at),
|
|
1064
|
+
0
|
|
1065
|
+
);
|
|
1066
|
+
for (const f of fetched) {
|
|
1067
|
+
const { stream, events } = f;
|
|
1068
|
+
const payloads = events.flatMap((event) => {
|
|
1069
|
+
const register = registry.events[event.name];
|
|
1070
|
+
if (!register) return [];
|
|
1071
|
+
return [...register.reactions.values()].filter((reaction) => {
|
|
1072
|
+
const resolved = typeof reaction.resolver === "function" ? reaction.resolver(event) : reaction.resolver;
|
|
1073
|
+
return resolved && resolved.target === stream;
|
|
1074
|
+
}).map((reaction) => ({ ...reaction, event }));
|
|
1075
|
+
});
|
|
1076
|
+
fetchMap.set(stream, { fetch: f, payloads });
|
|
1077
|
+
}
|
|
1078
|
+
const handled = await Promise.all(
|
|
1079
|
+
leased.map((lease) => {
|
|
1080
|
+
const entry = fetchMap.get(lease.stream);
|
|
1081
|
+
const at = entry?.fetch.events.at(-1)?.id || fetch_window_at;
|
|
1082
|
+
const payloads = entry?.payloads ?? [];
|
|
1083
|
+
const batchHandler = batchHandlers.get(lease.stream);
|
|
1084
|
+
if (batchHandler && payloads.length > 0) {
|
|
1085
|
+
return handleBatch({ ...lease, at }, payloads, batchHandler);
|
|
1086
|
+
}
|
|
1087
|
+
return handle({ ...lease, at }, payloads);
|
|
1088
|
+
})
|
|
1089
|
+
);
|
|
1090
|
+
const acked = await ops.ack(
|
|
1091
|
+
handled.filter(({ error }) => !error).map(({ at, lease }) => ({ ...lease, at }))
|
|
1092
|
+
);
|
|
1093
|
+
const blocked = await ops.block(
|
|
1094
|
+
handled.filter(({ block: block2 }) => block2).map(({ lease, error }) => ({ ...lease, error }))
|
|
1095
|
+
);
|
|
1096
|
+
return { leased, fetched, handled, acked, blocked };
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// src/internal/drain-ratio.ts
|
|
1100
|
+
var RATIO_MIN = 0.2;
|
|
1101
|
+
var RATIO_MAX = 0.8;
|
|
1102
|
+
var RATIO_DEFAULT = 0.5;
|
|
1103
|
+
function computeLagLeadRatio(handled, lagging, leading) {
|
|
1104
|
+
let lagging_handled = 0;
|
|
1105
|
+
let leading_handled = 0;
|
|
1106
|
+
for (const { lease, handled: count } of handled) {
|
|
1107
|
+
if (lease.lagging) lagging_handled += count;
|
|
1108
|
+
else leading_handled += count;
|
|
1109
|
+
}
|
|
1110
|
+
const lagging_avg = lagging > 0 ? lagging_handled / lagging : 0;
|
|
1111
|
+
const leading_avg = leading > 0 ? leading_handled / leading : 0;
|
|
1112
|
+
const total = lagging_avg + leading_avg;
|
|
1113
|
+
if (total === 0) return RATIO_DEFAULT;
|
|
1114
|
+
return Math.max(RATIO_MIN, Math.min(RATIO_MAX, lagging_avg / total));
|
|
1115
|
+
}
|
|
1116
|
+
|
|
836
1117
|
// src/internal/merge.ts
|
|
837
1118
|
var import_zod4 = require("zod");
|
|
838
1119
|
function baseTypeName(zodType) {
|
|
@@ -940,6 +1221,15 @@ function mergePatches(existing, incoming, stateName) {
|
|
|
940
1221
|
}
|
|
941
1222
|
return merged;
|
|
942
1223
|
}
|
|
1224
|
+
function mergeEventRegister(target, source) {
|
|
1225
|
+
for (const [eventName, sourceReg] of Object.entries(source)) {
|
|
1226
|
+
const targetReg = target[eventName];
|
|
1227
|
+
if (!targetReg) continue;
|
|
1228
|
+
for (const [name, reaction] of sourceReg.reactions) {
|
|
1229
|
+
targetReg.reactions.set(name, reaction);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
943
1233
|
function mergeProjection(proj, events) {
|
|
944
1234
|
for (const eventName of Object.keys(proj.events)) {
|
|
945
1235
|
const projRegister = proj.events[eventName];
|
|
@@ -984,7 +1274,7 @@ var subscribe = (streams) => store().subscribe(streams);
|
|
|
984
1274
|
|
|
985
1275
|
// src/internal/event-sourcing.ts
|
|
986
1276
|
var import_act_patch = require("@rotorsoft/act-patch");
|
|
987
|
-
var
|
|
1277
|
+
var import_crypto3 = require("crypto");
|
|
988
1278
|
async function snap(snapshot) {
|
|
989
1279
|
try {
|
|
990
1280
|
const { id, stream, name, meta, version } = snapshot.event;
|
|
@@ -1002,6 +1292,20 @@ async function snap(snapshot) {
|
|
|
1002
1292
|
log().error(error);
|
|
1003
1293
|
}
|
|
1004
1294
|
}
|
|
1295
|
+
async function tombstone(stream, expectedVersion, correlation) {
|
|
1296
|
+
try {
|
|
1297
|
+
const [committed] = await store().commit(
|
|
1298
|
+
stream,
|
|
1299
|
+
[{ name: TOMBSTONE_EVENT, data: {} }],
|
|
1300
|
+
{ correlation, causation: {} },
|
|
1301
|
+
expectedVersion
|
|
1302
|
+
);
|
|
1303
|
+
return committed;
|
|
1304
|
+
} catch (error) {
|
|
1305
|
+
if (error instanceof ConcurrencyError) return void 0;
|
|
1306
|
+
throw error;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1005
1309
|
async function load(me, stream, callback, asOf) {
|
|
1006
1310
|
const timeTravel = !!asOf && Object.values(asOf).some((v) => v !== void 0);
|
|
1007
1311
|
const cached = timeTravel ? void 0 : await cache().get(stream);
|
|
@@ -1066,7 +1370,7 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
1066
1370
|
data: skipValidation ? data : validate(name, data, me.events[name])
|
|
1067
1371
|
}));
|
|
1068
1372
|
const meta = {
|
|
1069
|
-
correlation: reactingTo?.meta.correlation || (0,
|
|
1373
|
+
correlation: reactingTo?.meta.correlation || (0, import_crypto3.randomUUID)(),
|
|
1070
1374
|
causation: {
|
|
1071
1375
|
action: {
|
|
1072
1376
|
name: action2,
|
|
@@ -1117,6 +1421,18 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
1117
1421
|
}
|
|
1118
1422
|
|
|
1119
1423
|
// src/internal/tracing.ts
|
|
1424
|
+
var PRETTY = config().env !== "production";
|
|
1425
|
+
var C_BLUE = "\x1B[38;5;39m";
|
|
1426
|
+
var C_ORANGE = "\x1B[38;5;208m";
|
|
1427
|
+
var C_GREEN = "\x1B[38;5;42m";
|
|
1428
|
+
var C_MAGENTA = "\x1B[38;5;165m";
|
|
1429
|
+
var C_DRAIN = "\x1B[38;5;244m";
|
|
1430
|
+
var C_RESET = "\x1B[0m";
|
|
1431
|
+
var es_caption = (caption, color, body) => PRETTY ? `${color}${body}${C_RESET}` : `${caption}: ${body}`;
|
|
1432
|
+
var drain_caption = (caption) => {
|
|
1433
|
+
const tag = `>> ${caption}`;
|
|
1434
|
+
return PRETTY ? `${C_DRAIN}${tag}${C_RESET}` : tag;
|
|
1435
|
+
};
|
|
1120
1436
|
var traced = (inner, exit, entry) => (async (...args) => {
|
|
1121
1437
|
entry?.(...args);
|
|
1122
1438
|
const result = await inner(...args);
|
|
@@ -1125,16 +1441,27 @@ var traced = (inner, exit, entry) => (async (...args) => {
|
|
|
1125
1441
|
});
|
|
1126
1442
|
function buildEs(logger) {
|
|
1127
1443
|
if (logger.level !== "trace") {
|
|
1128
|
-
return {
|
|
1444
|
+
return {
|
|
1445
|
+
snap,
|
|
1446
|
+
load,
|
|
1447
|
+
action,
|
|
1448
|
+
tombstone
|
|
1449
|
+
};
|
|
1129
1450
|
}
|
|
1130
1451
|
return {
|
|
1131
1452
|
snap: traced(snap, void 0, (snapshot) => {
|
|
1132
1453
|
logger.trace(
|
|
1133
|
-
|
|
1454
|
+
es_caption(
|
|
1455
|
+
"snap",
|
|
1456
|
+
C_MAGENTA,
|
|
1457
|
+
`${snapshot.event.stream}@${snapshot.event.version}`
|
|
1458
|
+
)
|
|
1134
1459
|
);
|
|
1135
1460
|
}),
|
|
1136
1461
|
load: traced(load, void 0, (_me, stream, _cb, asOf) => {
|
|
1137
|
-
logger.trace(
|
|
1462
|
+
logger.trace(
|
|
1463
|
+
es_caption("load", C_GREEN, `${stream}${asOf ? " (as-of)" : ""}`)
|
|
1464
|
+
);
|
|
1138
1465
|
}),
|
|
1139
1466
|
action: traced(
|
|
1140
1467
|
action,
|
|
@@ -1143,14 +1470,27 @@ function buildEs(logger) {
|
|
|
1143
1470
|
if (committed.length) {
|
|
1144
1471
|
logger.trace(
|
|
1145
1472
|
committed.map((s) => s.event.data),
|
|
1146
|
-
|
|
1473
|
+
es_caption(
|
|
1474
|
+
"committed",
|
|
1475
|
+
C_ORANGE,
|
|
1476
|
+
`${target.stream}.${committed.map((s) => s.event.name).join(", ")}`
|
|
1477
|
+
)
|
|
1147
1478
|
);
|
|
1148
1479
|
}
|
|
1149
1480
|
},
|
|
1150
1481
|
(_me, action2, target, payload) => {
|
|
1151
|
-
logger.trace(
|
|
1482
|
+
logger.trace(
|
|
1483
|
+
payload,
|
|
1484
|
+
es_caption("action", C_BLUE, `${target.stream}.${action2}`)
|
|
1485
|
+
);
|
|
1152
1486
|
}
|
|
1153
|
-
)
|
|
1487
|
+
),
|
|
1488
|
+
tombstone: traced(tombstone, (committed, stream) => {
|
|
1489
|
+
if (committed)
|
|
1490
|
+
logger.trace(
|
|
1491
|
+
es_caption("tombstoned", C_ORANGE, `${stream}@${committed.version}`)
|
|
1492
|
+
);
|
|
1493
|
+
})
|
|
1154
1494
|
};
|
|
1155
1495
|
}
|
|
1156
1496
|
function buildDrain(logger) {
|
|
@@ -1169,7 +1509,7 @@ function buildDrain(logger) {
|
|
|
1169
1509
|
const data = Object.fromEntries(
|
|
1170
1510
|
leased.map(({ stream, at, retry }) => [stream, { at, retry }])
|
|
1171
1511
|
);
|
|
1172
|
-
logger.trace(data, "
|
|
1512
|
+
logger.trace(data, drain_caption("claimed"));
|
|
1173
1513
|
}
|
|
1174
1514
|
}),
|
|
1175
1515
|
fetch: traced(fetch, (fetched) => {
|
|
@@ -1182,14 +1522,14 @@ function buildDrain(logger) {
|
|
|
1182
1522
|
return [key, value];
|
|
1183
1523
|
})
|
|
1184
1524
|
);
|
|
1185
|
-
logger.trace(data, "
|
|
1525
|
+
logger.trace(data, drain_caption("fetched"));
|
|
1186
1526
|
}),
|
|
1187
1527
|
ack: traced(ack, (acked) => {
|
|
1188
1528
|
if (acked.length) {
|
|
1189
1529
|
const data = Object.fromEntries(
|
|
1190
1530
|
acked.map(({ stream, at, retry }) => [stream, { at, retry }])
|
|
1191
1531
|
);
|
|
1192
|
-
logger.trace(data, "
|
|
1532
|
+
logger.trace(data, drain_caption("acked"));
|
|
1193
1533
|
}
|
|
1194
1534
|
}),
|
|
1195
1535
|
block: traced(block, (blocked) => {
|
|
@@ -1200,27 +1540,31 @@ function buildDrain(logger) {
|
|
|
1200
1540
|
{ at, retry, error }
|
|
1201
1541
|
])
|
|
1202
1542
|
);
|
|
1203
|
-
logger.trace(data, "
|
|
1543
|
+
logger.trace(data, drain_caption("blocked"));
|
|
1204
1544
|
}
|
|
1205
1545
|
}),
|
|
1206
1546
|
subscribe: traced(subscribe, (result, streams) => {
|
|
1207
1547
|
if (result.subscribed) {
|
|
1208
1548
|
const data = streams.map(({ stream }) => stream).join(" ");
|
|
1209
|
-
logger.trace(
|
|
1549
|
+
logger.trace(`${drain_caption("correlated")} ${data}`);
|
|
1210
1550
|
}
|
|
1211
1551
|
})
|
|
1212
1552
|
};
|
|
1213
1553
|
}
|
|
1214
1554
|
|
|
1215
1555
|
// src/act.ts
|
|
1556
|
+
var DEFAULT_MAX_SUBSCRIBED_STREAMS = 1e3;
|
|
1216
1557
|
var Act = class {
|
|
1217
|
-
constructor(registry, _states = /* @__PURE__ */ new Map(), batchHandlers = /* @__PURE__ */ new Map()) {
|
|
1558
|
+
constructor(registry, _states = /* @__PURE__ */ new Map(), batchHandlers = /* @__PURE__ */ new Map(), options = {}) {
|
|
1218
1559
|
this.registry = registry;
|
|
1219
1560
|
this._states = _states;
|
|
1220
1561
|
this._batch_handlers = batchHandlers;
|
|
1562
|
+
this._subscribed_streams = new LruSet(
|
|
1563
|
+
options.maxSubscribedStreams ?? DEFAULT_MAX_SUBSCRIBED_STREAMS
|
|
1564
|
+
);
|
|
1221
1565
|
this._es = buildEs(this._logger);
|
|
1222
1566
|
this._cd = buildDrain(this._logger);
|
|
1223
|
-
const statics =
|
|
1567
|
+
const statics = /* @__PURE__ */ new Map();
|
|
1224
1568
|
for (const [name, register] of Object.entries(this.registry.events)) {
|
|
1225
1569
|
if (register.reactions.size > 0) {
|
|
1226
1570
|
this._reactive_events.add(name);
|
|
@@ -1229,14 +1573,13 @@ var Act = class {
|
|
|
1229
1573
|
if (typeof reaction.resolver === "function") {
|
|
1230
1574
|
this._has_dynamic_resolvers = true;
|
|
1231
1575
|
} else {
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
});
|
|
1576
|
+
const { target, source } = reaction.resolver;
|
|
1577
|
+
const key = `${target}|${source ?? ""}`;
|
|
1578
|
+
if (!statics.has(key)) statics.set(key, { stream: target, source });
|
|
1236
1579
|
}
|
|
1237
1580
|
}
|
|
1238
1581
|
}
|
|
1239
|
-
this._static_targets = statics;
|
|
1582
|
+
this._static_targets = [...statics.values()];
|
|
1240
1583
|
for (const merged of this._states.values()) {
|
|
1241
1584
|
for (const eventName of Object.keys(merged.events)) {
|
|
1242
1585
|
this._event_to_state.set(eventName, merged);
|
|
@@ -1256,20 +1599,42 @@ var Act = class {
|
|
|
1256
1599
|
_settle_timer = void 0;
|
|
1257
1600
|
_settling = false;
|
|
1258
1601
|
_correlation_checkpoint = -1;
|
|
1259
|
-
|
|
1602
|
+
/**
|
|
1603
|
+
* Streams already subscribed via store.subscribe() — both the static
|
|
1604
|
+
* targets registered at init and dynamic targets discovered by
|
|
1605
|
+
* correlate(). correlate() consults this set to avoid re-subscribing
|
|
1606
|
+
* known streams.
|
|
1607
|
+
*
|
|
1608
|
+
* Bounded LRU so apps that mint millions of dynamic targets (one per
|
|
1609
|
+
* aggregate) don't grow this unbounded. Eviction costs at most one
|
|
1610
|
+
* redundant store.subscribe() call per evicted-but-still-active stream
|
|
1611
|
+
* (subscribe is idempotent). Cap configurable via {@link ActOptions}.
|
|
1612
|
+
*/
|
|
1613
|
+
_subscribed_streams;
|
|
1260
1614
|
_has_dynamic_resolvers = false;
|
|
1261
1615
|
_correlation_initialized = false;
|
|
1262
1616
|
/** Event names with at least one registered reaction (computed at build time) */
|
|
1263
1617
|
_reactive_events = /* @__PURE__ */ new Set();
|
|
1264
1618
|
/** Set in do() when a committed event has reactions — cleared by drain() */
|
|
1265
1619
|
_needs_drain = false;
|
|
1620
|
+
/**
|
|
1621
|
+
* Emit a lifecycle event. The payload type is inferred from the event name
|
|
1622
|
+
* via {@link ActLifecycleEvents}.
|
|
1623
|
+
*/
|
|
1266
1624
|
emit(event, args) {
|
|
1267
1625
|
return this._emitter.emit(event, args);
|
|
1268
1626
|
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Register a listener for a lifecycle event. The listener receives the
|
|
1629
|
+
* event-specific payload.
|
|
1630
|
+
*/
|
|
1269
1631
|
on(event, listener) {
|
|
1270
1632
|
this._emitter.on(event, listener);
|
|
1271
1633
|
return this;
|
|
1272
1634
|
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Remove a previously registered lifecycle listener.
|
|
1637
|
+
*/
|
|
1273
1638
|
off(event, listener) {
|
|
1274
1639
|
this._emitter.off(event, listener);
|
|
1275
1640
|
return this;
|
|
@@ -1297,6 +1662,15 @@ var Act = class {
|
|
|
1297
1662
|
_event_to_state = /* @__PURE__ */ new Map();
|
|
1298
1663
|
/** Logger resolved at construction time (after user port configuration) */
|
|
1299
1664
|
_logger = log();
|
|
1665
|
+
/** Pre-bound IAct methods reused across drain cycles. Only `do` varies per
|
|
1666
|
+
* payload (it captures the triggering event for reactingTo auto-inject). */
|
|
1667
|
+
_bound_do = this.do.bind(this);
|
|
1668
|
+
_bound_load = this.load.bind(this);
|
|
1669
|
+
_bound_query = this.query.bind(this);
|
|
1670
|
+
_bound_query_array = this.query_array.bind(this);
|
|
1671
|
+
/** Pre-bound dispatchers handed to runDrainCycle each cycle. */
|
|
1672
|
+
_bound_handle = this.handle.bind(this);
|
|
1673
|
+
_bound_handle_batch = this.handleBatch.bind(this);
|
|
1300
1674
|
/**
|
|
1301
1675
|
* Executes an action on a state instance, committing resulting events.
|
|
1302
1676
|
*
|
|
@@ -1499,35 +1873,55 @@ var Act = class {
|
|
|
1499
1873
|
return events;
|
|
1500
1874
|
}
|
|
1501
1875
|
/**
|
|
1502
|
-
*
|
|
1503
|
-
*
|
|
1504
|
-
*
|
|
1505
|
-
*
|
|
1876
|
+
* Shared finalization for the two reaction-runner shapes (per-event
|
|
1877
|
+
* `handle` and bulk `handleBatch`). Centralizes the error log, retry-vs-
|
|
1878
|
+
* block decision, and the "error reported only when nothing was handled"
|
|
1879
|
+
* rule that's true in both shapes (in batch mode, `handled` is always 0
|
|
1880
|
+
* on failure, so the rule degenerates to "always reported").
|
|
1881
|
+
*/
|
|
1882
|
+
_finalize(lease, handled, at, error, options) {
|
|
1883
|
+
if (!error) return { lease, handled, at };
|
|
1884
|
+
this._logger.error(error);
|
|
1885
|
+
const block2 = lease.retry >= options.maxRetries && options.blockOnError;
|
|
1886
|
+
if (block2)
|
|
1887
|
+
this._logger.error(
|
|
1888
|
+
`Blocking ${lease.stream} after ${lease.retry} retries.`
|
|
1889
|
+
);
|
|
1890
|
+
return {
|
|
1891
|
+
lease,
|
|
1892
|
+
handled,
|
|
1893
|
+
at,
|
|
1894
|
+
error: handled === 0 ? error.message : void 0,
|
|
1895
|
+
block: block2
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
/**
|
|
1899
|
+
* Handles leased reactions one event at a time.
|
|
1506
1900
|
*
|
|
1507
|
-
*
|
|
1508
|
-
*
|
|
1509
|
-
*
|
|
1510
|
-
*
|
|
1901
|
+
* Called by the main `drain` loop after fetching new events. Each handler
|
|
1902
|
+
* receives a scoped `IAct` proxy that auto-injects the triggering event
|
|
1903
|
+
* as `reactingTo` when `do()` is called without it, maintaining
|
|
1904
|
+
* correlation chains by default (#587). Handlers can still pass an
|
|
1905
|
+
* explicit `reactingTo` to override.
|
|
1511
1906
|
*
|
|
1512
1907
|
* @internal
|
|
1513
|
-
* @param lease The lease to handle
|
|
1514
|
-
* @param payloads The reactions to handle
|
|
1515
|
-
* @returns The lease with results
|
|
1516
1908
|
*/
|
|
1517
1909
|
async handle(lease, payloads) {
|
|
1518
1910
|
if (payloads.length === 0) return { lease, handled: 0, at: lease.at };
|
|
1519
1911
|
const stream = lease.stream;
|
|
1520
|
-
let at = payloads.at(0).event.id
|
|
1521
|
-
|
|
1522
|
-
|
|
1912
|
+
let at = payloads.at(0).event.id;
|
|
1913
|
+
let handled = 0;
|
|
1914
|
+
if (lease.retry > 0)
|
|
1915
|
+
this._logger.warn(`Retrying ${stream}@${at} (${lease.retry}).`);
|
|
1916
|
+
const doAction = this._bound_do;
|
|
1523
1917
|
const scopedApp = {
|
|
1524
1918
|
do: doAction,
|
|
1525
|
-
load: this.
|
|
1526
|
-
query: this.
|
|
1527
|
-
query_array: this.
|
|
1919
|
+
load: this._bound_load,
|
|
1920
|
+
query: this._bound_query,
|
|
1921
|
+
query_array: this._bound_query_array
|
|
1528
1922
|
};
|
|
1529
1923
|
for (const payload of payloads) {
|
|
1530
|
-
const { event, handler
|
|
1924
|
+
const { event, handler } = payload;
|
|
1531
1925
|
scopedApp.do = (action2, target, payload2, reactingTo, skipValidation) => doAction(
|
|
1532
1926
|
action2,
|
|
1533
1927
|
target,
|
|
@@ -1540,22 +1934,16 @@ var Act = class {
|
|
|
1540
1934
|
at = event.id;
|
|
1541
1935
|
handled++;
|
|
1542
1936
|
} catch (error) {
|
|
1543
|
-
this.
|
|
1544
|
-
const block2 = lease.retry >= options.maxRetries && options.blockOnError;
|
|
1545
|
-
block2 && this._logger.error(
|
|
1546
|
-
`Blocking ${stream} after ${lease.retry} retries.`
|
|
1547
|
-
);
|
|
1548
|
-
return {
|
|
1937
|
+
return this._finalize(
|
|
1549
1938
|
lease,
|
|
1550
1939
|
handled,
|
|
1551
1940
|
at,
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
};
|
|
1941
|
+
error,
|
|
1942
|
+
payload.options
|
|
1943
|
+
);
|
|
1556
1944
|
}
|
|
1557
1945
|
}
|
|
1558
|
-
return
|
|
1946
|
+
return this._finalize(lease, handled, at, void 0, payloads[0].options);
|
|
1559
1947
|
}
|
|
1560
1948
|
/**
|
|
1561
1949
|
* Handles a batch of events for a projection with a batch handler.
|
|
@@ -1565,33 +1953,26 @@ var Act = class {
|
|
|
1565
1953
|
* in a single call, enabling bulk DB operations.
|
|
1566
1954
|
*
|
|
1567
1955
|
* @internal
|
|
1568
|
-
* @param lease The lease to handle
|
|
1569
|
-
* @param payloads The reactions to handle
|
|
1570
|
-
* @param batchHandler The batch handler for this projection
|
|
1571
|
-
* @returns The lease with results
|
|
1572
1956
|
*/
|
|
1573
1957
|
async handleBatch(lease, payloads, batchHandler) {
|
|
1574
1958
|
const stream = lease.stream;
|
|
1575
1959
|
const events = payloads.map((p) => p.event);
|
|
1576
|
-
const
|
|
1577
|
-
lease.retry > 0
|
|
1578
|
-
|
|
1579
|
-
|
|
1960
|
+
const options = payloads[0].options;
|
|
1961
|
+
if (lease.retry > 0)
|
|
1962
|
+
this._logger.warn(
|
|
1963
|
+
`Retrying batch ${stream}@${events[0].id} (${lease.retry}).`
|
|
1964
|
+
);
|
|
1580
1965
|
try {
|
|
1581
1966
|
await batchHandler(events, stream);
|
|
1582
|
-
return
|
|
1583
|
-
} catch (error) {
|
|
1584
|
-
this._logger.error(error);
|
|
1585
|
-
const { options } = payloads[0];
|
|
1586
|
-
const block2 = lease.retry >= options.maxRetries && options.blockOnError;
|
|
1587
|
-
block2 && this._logger.error(`Blocking ${stream} after ${lease.retry} retries.`);
|
|
1588
|
-
return {
|
|
1967
|
+
return this._finalize(
|
|
1589
1968
|
lease,
|
|
1590
|
-
|
|
1591
|
-
at
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1969
|
+
events.length,
|
|
1970
|
+
events.at(-1).id,
|
|
1971
|
+
void 0,
|
|
1972
|
+
options
|
|
1973
|
+
);
|
|
1974
|
+
} catch (error) {
|
|
1975
|
+
return this._finalize(lease, 0, lease.at, error, options);
|
|
1595
1976
|
}
|
|
1596
1977
|
}
|
|
1597
1978
|
/**
|
|
@@ -1641,81 +2022,46 @@ var Act = class {
|
|
|
1641
2022
|
if (!this._needs_drain) {
|
|
1642
2023
|
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
1643
2024
|
}
|
|
1644
|
-
if (
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
fetched.forEach(({ stream, events }) => {
|
|
1666
|
-
const payloads = events.flatMap((event) => {
|
|
1667
|
-
const register = this.registry.events[event.name];
|
|
1668
|
-
if (!register) return [];
|
|
1669
|
-
return [...register.reactions.values()].filter((reaction) => {
|
|
1670
|
-
const resolved = typeof reaction.resolver === "function" ? reaction.resolver(event) : reaction.resolver;
|
|
1671
|
-
return resolved && resolved.target === stream;
|
|
1672
|
-
}).map((reaction) => ({ ...reaction, event }));
|
|
1673
|
-
});
|
|
1674
|
-
payloadsMap.set(stream, payloads);
|
|
1675
|
-
});
|
|
1676
|
-
const handled = await Promise.all(
|
|
1677
|
-
leased.map((lease) => {
|
|
1678
|
-
const streamFetch = fetched.find((f) => f.stream === lease.stream);
|
|
1679
|
-
const at = streamFetch?.events.at(-1)?.id || fetch_window_at;
|
|
1680
|
-
const payloads = payloadsMap.get(lease.stream);
|
|
1681
|
-
const batchHandler = this._batch_handlers.get(lease.stream);
|
|
1682
|
-
if (batchHandler && payloads.length > 0) {
|
|
1683
|
-
return this.handleBatch({ ...lease, at }, payloads, batchHandler);
|
|
1684
|
-
}
|
|
1685
|
-
return this.handle({ ...lease, at }, payloads);
|
|
1686
|
-
})
|
|
1687
|
-
);
|
|
1688
|
-
const [lagging_handled, leading_handled] = handled.reduce(
|
|
1689
|
-
([lagging_handled2, leading_handled2], { lease, handled: handled2 }) => [
|
|
1690
|
-
lagging_handled2 + (lease.lagging ? handled2 : 0),
|
|
1691
|
-
leading_handled2 + (lease.lagging ? 0 : handled2)
|
|
1692
|
-
],
|
|
1693
|
-
[0, 0]
|
|
1694
|
-
);
|
|
1695
|
-
const lagging_avg = lagging > 0 ? lagging_handled / lagging : 0;
|
|
1696
|
-
const leading_avg = leading > 0 ? leading_handled / leading : 0;
|
|
1697
|
-
const total = lagging_avg + leading_avg;
|
|
1698
|
-
this._drain_lag2lead_ratio = total > 0 ? Math.max(0.2, Math.min(0.8, lagging_avg / total)) : 0.5;
|
|
1699
|
-
const acked = await this._cd.ack(
|
|
1700
|
-
handled.filter(({ error }) => !error).map(({ at, lease }) => ({ ...lease, at }))
|
|
1701
|
-
);
|
|
1702
|
-
if (acked.length) this.emit("acked", acked);
|
|
1703
|
-
const blocked = await this._cd.block(
|
|
1704
|
-
handled.filter(({ block: block2 }) => block2).map(({ lease, error }) => ({ ...lease, error }))
|
|
1705
|
-
);
|
|
1706
|
-
if (blocked.length) this.emit("blocked", blocked);
|
|
1707
|
-
const result = { fetched, leased, acked, blocked };
|
|
1708
|
-
const hasErrors = handled.some(({ error }) => error);
|
|
1709
|
-
if (!acked.length && !blocked.length && !hasErrors)
|
|
1710
|
-
this._needs_drain = false;
|
|
1711
|
-
return result;
|
|
1712
|
-
} catch (error) {
|
|
1713
|
-
this._logger.error(error);
|
|
1714
|
-
} finally {
|
|
1715
|
-
this._drain_locked = false;
|
|
2025
|
+
if (this._drain_locked) {
|
|
2026
|
+
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
2027
|
+
}
|
|
2028
|
+
try {
|
|
2029
|
+
this._drain_locked = true;
|
|
2030
|
+
const lagging = Math.ceil(streamLimit * this._drain_lag2lead_ratio);
|
|
2031
|
+
const leading = streamLimit - lagging;
|
|
2032
|
+
const cycle = await runDrainCycle(
|
|
2033
|
+
this._cd,
|
|
2034
|
+
this.registry,
|
|
2035
|
+
this._batch_handlers,
|
|
2036
|
+
this._bound_handle,
|
|
2037
|
+
this._bound_handle_batch,
|
|
2038
|
+
lagging,
|
|
2039
|
+
leading,
|
|
2040
|
+
eventLimit,
|
|
2041
|
+
leaseMillis
|
|
2042
|
+
);
|
|
2043
|
+
if (!cycle) {
|
|
2044
|
+
this._needs_drain = false;
|
|
2045
|
+
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
1716
2046
|
}
|
|
2047
|
+
const { leased, fetched, handled, acked, blocked } = cycle;
|
|
2048
|
+
this._drain_lag2lead_ratio = computeLagLeadRatio(
|
|
2049
|
+
handled,
|
|
2050
|
+
lagging,
|
|
2051
|
+
leading
|
|
2052
|
+
);
|
|
2053
|
+
if (acked.length) this.emit("acked", acked);
|
|
2054
|
+
if (blocked.length) this.emit("blocked", blocked);
|
|
2055
|
+
const hasErrors = handled.some(({ error }) => error);
|
|
2056
|
+
if (!acked.length && !blocked.length && !hasErrors)
|
|
2057
|
+
this._needs_drain = false;
|
|
2058
|
+
return { fetched, leased, acked, blocked };
|
|
2059
|
+
} catch (error) {
|
|
2060
|
+
this._logger.error(error);
|
|
2061
|
+
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
2062
|
+
} finally {
|
|
2063
|
+
this._drain_locked = false;
|
|
1717
2064
|
}
|
|
1718
|
-
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
1719
2065
|
}
|
|
1720
2066
|
/**
|
|
1721
2067
|
* Discovers and registers new streams dynamically based on reaction resolvers.
|
|
@@ -1776,7 +2122,7 @@ var Act = class {
|
|
|
1776
2122
|
this._correlation_checkpoint = watermark;
|
|
1777
2123
|
if (this._reactive_events.size > 0) this._needs_drain = true;
|
|
1778
2124
|
for (const { stream } of this._static_targets) {
|
|
1779
|
-
this.
|
|
2125
|
+
this._subscribed_streams.add(stream);
|
|
1780
2126
|
}
|
|
1781
2127
|
}
|
|
1782
2128
|
async correlate(query = { after: -1, limit: 10 }) {
|
|
@@ -1794,7 +2140,7 @@ var Act = class {
|
|
|
1794
2140
|
for (const reaction of register.reactions.values()) {
|
|
1795
2141
|
if (typeof reaction.resolver !== "function") continue;
|
|
1796
2142
|
const resolved = reaction.resolver(event);
|
|
1797
|
-
if (resolved && !this.
|
|
2143
|
+
if (resolved && !this._subscribed_streams.has(resolved.target)) {
|
|
1798
2144
|
const entry = correlated.get(resolved.target) || {
|
|
1799
2145
|
source: resolved.source,
|
|
1800
2146
|
payloads: []
|
|
@@ -1820,7 +2166,7 @@ var Act = class {
|
|
|
1820
2166
|
this._correlation_checkpoint = last_id;
|
|
1821
2167
|
if (subscribed) {
|
|
1822
2168
|
for (const { stream } of streams) {
|
|
1823
|
-
this.
|
|
2169
|
+
this._subscribed_streams.add(stream);
|
|
1824
2170
|
}
|
|
1825
2171
|
}
|
|
1826
2172
|
return { subscribed, last_id };
|
|
@@ -2003,143 +2349,14 @@ var Act = class {
|
|
|
2003
2349
|
*/
|
|
2004
2350
|
async close(targets) {
|
|
2005
2351
|
if (!targets.length) return { truncated: /* @__PURE__ */ new Map(), skipped: [] };
|
|
2006
|
-
const targetMap = new Map(targets.map((t) => [t.stream, t]));
|
|
2007
|
-
const streams = [...targetMap.keys()];
|
|
2008
2352
|
await this.correlate({ limit: 1e3 });
|
|
2009
|
-
const
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
await store().query(
|
|
2016
|
-
(e) => {
|
|
2017
|
-
if (e.name === TOMBSTONE_EVENT) return;
|
|
2018
|
-
if (maxId === -1) {
|
|
2019
|
-
maxId = e.id;
|
|
2020
|
-
version = e.version;
|
|
2021
|
-
}
|
|
2022
|
-
if (e.name !== SNAP_EVENT && lastEventName === void 0) {
|
|
2023
|
-
lastEventName = e.name;
|
|
2024
|
-
}
|
|
2025
|
-
},
|
|
2026
|
-
// limit: 2 covers the typical snapshot-at-head case (snapshot is
|
|
2027
|
-
// always preceded by the domain event it captured). Streams with
|
|
2028
|
-
// unusual layouts fall back to no-seed via the lookup miss path.
|
|
2029
|
-
{ stream: s, stream_exact: true, backward: true, limit: 2 }
|
|
2030
|
-
);
|
|
2031
|
-
if (maxId >= 0) streamInfo.set(s, { maxId, version, lastEventName });
|
|
2032
|
-
})
|
|
2033
|
-
);
|
|
2034
|
-
const skipped = [];
|
|
2035
|
-
let safe;
|
|
2036
|
-
if (this._reactive_events.size === 0) {
|
|
2037
|
-
safe = [...streamInfo.keys()];
|
|
2038
|
-
} else {
|
|
2039
|
-
const pendingSet = /* @__PURE__ */ new Set();
|
|
2040
|
-
await store().query_streams((position) => {
|
|
2041
|
-
const sourceRe = position.source ? RegExp(position.source) : void 0;
|
|
2042
|
-
for (const [stream, info] of streamInfo) {
|
|
2043
|
-
if ((!sourceRe || sourceRe.test(stream)) && position.at < info.maxId) {
|
|
2044
|
-
pendingSet.add(stream);
|
|
2045
|
-
}
|
|
2046
|
-
}
|
|
2047
|
-
});
|
|
2048
|
-
safe = [];
|
|
2049
|
-
for (const [stream] of streamInfo) {
|
|
2050
|
-
if (pendingSet.has(stream)) {
|
|
2051
|
-
skipped.push(stream);
|
|
2052
|
-
} else {
|
|
2053
|
-
safe.push(stream);
|
|
2054
|
-
}
|
|
2055
|
-
}
|
|
2056
|
-
}
|
|
2057
|
-
if (!safe.length) {
|
|
2058
|
-
const result2 = { truncated: /* @__PURE__ */ new Map(), skipped };
|
|
2059
|
-
this.emit("closed", result2);
|
|
2060
|
-
return result2;
|
|
2061
|
-
}
|
|
2062
|
-
const correlation = (0, import_crypto2.randomUUID)();
|
|
2063
|
-
const guarded = [];
|
|
2064
|
-
const guardEvents = /* @__PURE__ */ new Map();
|
|
2065
|
-
await Promise.all(
|
|
2066
|
-
safe.map(async (stream) => {
|
|
2067
|
-
try {
|
|
2068
|
-
const info = streamInfo.get(stream);
|
|
2069
|
-
const [committed] = await store().commit(
|
|
2070
|
-
stream,
|
|
2071
|
-
[{ name: TOMBSTONE_EVENT, data: {} }],
|
|
2072
|
-
{ correlation, causation: {} },
|
|
2073
|
-
info.version
|
|
2074
|
-
);
|
|
2075
|
-
guarded.push(stream);
|
|
2076
|
-
guardEvents.set(stream, { id: committed.id, stream });
|
|
2077
|
-
} catch {
|
|
2078
|
-
skipped.push(stream);
|
|
2079
|
-
}
|
|
2080
|
-
})
|
|
2081
|
-
);
|
|
2082
|
-
if (!guarded.length) {
|
|
2083
|
-
const result2 = { truncated: /* @__PURE__ */ new Map(), skipped };
|
|
2084
|
-
this.emit("closed", result2);
|
|
2085
|
-
return result2;
|
|
2086
|
-
}
|
|
2087
|
-
const seedStates = /* @__PURE__ */ new Map();
|
|
2088
|
-
await Promise.all(
|
|
2089
|
-
guarded.filter((s) => targetMap.get(s)?.restart).map(async (stream) => {
|
|
2090
|
-
const lastEventName = streamInfo.get(stream)?.lastEventName;
|
|
2091
|
-
const ownerState = lastEventName ? this._event_to_state.get(lastEventName) : void 0;
|
|
2092
|
-
if (!ownerState) {
|
|
2093
|
-
this._logger.error(
|
|
2094
|
-
`Cannot seed restart for "${stream}": no registered state owns event "${lastEventName ?? "<none>"}". Stream will be tombstoned instead.`
|
|
2095
|
-
);
|
|
2096
|
-
return;
|
|
2097
|
-
}
|
|
2098
|
-
const snap2 = await this._es.load(ownerState, stream);
|
|
2099
|
-
seedStates.set(stream, snap2.state);
|
|
2100
|
-
})
|
|
2101
|
-
);
|
|
2102
|
-
for (const stream of guarded) {
|
|
2103
|
-
const archiveFn = targetMap.get(stream)?.archive;
|
|
2104
|
-
if (archiveFn) await archiveFn();
|
|
2105
|
-
}
|
|
2106
|
-
const truncTargets = guarded.map((stream) => {
|
|
2107
|
-
const snapshot = seedStates.get(stream);
|
|
2108
|
-
const guard = guardEvents.get(stream);
|
|
2109
|
-
return {
|
|
2110
|
-
stream,
|
|
2111
|
-
snapshot,
|
|
2112
|
-
meta: {
|
|
2113
|
-
correlation,
|
|
2114
|
-
causation: {
|
|
2115
|
-
event: {
|
|
2116
|
-
id: guard.id,
|
|
2117
|
-
name: TOMBSTONE_EVENT,
|
|
2118
|
-
stream: guard.stream
|
|
2119
|
-
}
|
|
2120
|
-
}
|
|
2121
|
-
}
|
|
2122
|
-
};
|
|
2353
|
+
const result = await runCloseCycle(targets, {
|
|
2354
|
+
reactiveEventsSize: this._reactive_events.size,
|
|
2355
|
+
eventToState: this._event_to_state,
|
|
2356
|
+
load: this._es.load,
|
|
2357
|
+
tombstone: this._es.tombstone,
|
|
2358
|
+
logger: this._logger
|
|
2123
2359
|
});
|
|
2124
|
-
const truncated = await store().truncate(truncTargets);
|
|
2125
|
-
await Promise.all(
|
|
2126
|
-
guarded.map(async (stream) => {
|
|
2127
|
-
const entry = truncated.get(stream);
|
|
2128
|
-
const state2 = seedStates.get(stream);
|
|
2129
|
-
if (state2 && entry) {
|
|
2130
|
-
await cache().set(stream, {
|
|
2131
|
-
state: state2,
|
|
2132
|
-
version: entry.committed.version,
|
|
2133
|
-
event_id: entry.committed.id,
|
|
2134
|
-
patches: 0,
|
|
2135
|
-
snaps: 1
|
|
2136
|
-
});
|
|
2137
|
-
} else {
|
|
2138
|
-
await cache().invalidate(stream);
|
|
2139
|
-
}
|
|
2140
|
-
})
|
|
2141
|
-
);
|
|
2142
|
-
const result = { truncated, skipped };
|
|
2143
2360
|
this.emit("closed", result);
|
|
2144
2361
|
return result;
|
|
2145
2362
|
}
|
|
@@ -2208,7 +2425,7 @@ var Act = class {
|
|
|
2208
2425
|
}
|
|
2209
2426
|
};
|
|
2210
2427
|
|
|
2211
|
-
// src/act-builder.ts
|
|
2428
|
+
// src/builders/act-builder.ts
|
|
2212
2429
|
function registerBatchHandler(proj, batchHandlers) {
|
|
2213
2430
|
if (!proj.batchHandler || !proj.target) return;
|
|
2214
2431
|
const existing = batchHandlers.get(proj.target);
|
|
@@ -2217,56 +2434,33 @@ function registerBatchHandler(proj, batchHandlers) {
|
|
|
2217
2434
|
}
|
|
2218
2435
|
batchHandlers.set(proj.target, proj.batchHandler);
|
|
2219
2436
|
}
|
|
2220
|
-
function act(
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
},
|
|
2437
|
+
function act() {
|
|
2438
|
+
const states = /* @__PURE__ */ new Map();
|
|
2439
|
+
const registry = {
|
|
2440
|
+
actions: {},
|
|
2441
|
+
events: {}
|
|
2442
|
+
};
|
|
2443
|
+
const pendingProjections = [];
|
|
2444
|
+
const batchHandlers = /* @__PURE__ */ new Map();
|
|
2224
2445
|
const builder = {
|
|
2225
2446
|
withState: (state2) => {
|
|
2226
2447
|
registerState(state2, states, registry.actions, registry.events);
|
|
2227
|
-
return
|
|
2228
|
-
states,
|
|
2229
|
-
registry,
|
|
2230
|
-
pendingProjections,
|
|
2231
|
-
batchHandlers
|
|
2232
|
-
);
|
|
2448
|
+
return builder;
|
|
2233
2449
|
},
|
|
2234
2450
|
withSlice: (input) => {
|
|
2235
2451
|
for (const s of input.states.values()) {
|
|
2236
2452
|
registerState(s, states, registry.actions, registry.events);
|
|
2237
2453
|
}
|
|
2238
|
-
|
|
2239
|
-
const sliceRegister = input.events[eventName];
|
|
2240
|
-
for (const [name, reaction] of sliceRegister.reactions) {
|
|
2241
|
-
registry.events[eventName].reactions.set(name, reaction);
|
|
2242
|
-
}
|
|
2243
|
-
}
|
|
2454
|
+
mergeEventRegister(registry.events, input.events);
|
|
2244
2455
|
pendingProjections.push(...input.projections);
|
|
2245
|
-
return
|
|
2246
|
-
states,
|
|
2247
|
-
registry,
|
|
2248
|
-
pendingProjections,
|
|
2249
|
-
batchHandlers
|
|
2250
|
-
);
|
|
2456
|
+
return builder;
|
|
2251
2457
|
},
|
|
2252
2458
|
withProjection: (proj) => {
|
|
2253
2459
|
mergeProjection(proj, registry.events);
|
|
2254
2460
|
registerBatchHandler(proj, batchHandlers);
|
|
2255
|
-
return
|
|
2256
|
-
states,
|
|
2257
|
-
registry,
|
|
2258
|
-
pendingProjections,
|
|
2259
|
-
batchHandlers
|
|
2260
|
-
);
|
|
2261
|
-
},
|
|
2262
|
-
withActor: () => {
|
|
2263
|
-
return act(
|
|
2264
|
-
states,
|
|
2265
|
-
registry,
|
|
2266
|
-
pendingProjections,
|
|
2267
|
-
batchHandlers
|
|
2268
|
-
);
|
|
2461
|
+
return builder;
|
|
2269
2462
|
},
|
|
2463
|
+
withActor: () => builder,
|
|
2270
2464
|
on: (event) => ({
|
|
2271
2465
|
do: (handler, options) => {
|
|
2272
2466
|
const reaction = {
|
|
@@ -2282,19 +2476,15 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
|
2282
2476
|
`Reaction handler for "${String(event)}" must be a named function`
|
|
2283
2477
|
);
|
|
2284
2478
|
registry.events[event].reactions.set(handler.name, reaction);
|
|
2285
|
-
return {
|
|
2286
|
-
...builder,
|
|
2479
|
+
return Object.assign(builder, {
|
|
2287
2480
|
to(resolver) {
|
|
2288
|
-
|
|
2289
|
-
...reaction,
|
|
2290
|
-
resolver: typeof resolver === "string" ? { target: resolver } : resolver
|
|
2291
|
-
});
|
|
2481
|
+
reaction.resolver = typeof resolver === "string" ? { target: resolver } : resolver;
|
|
2292
2482
|
return builder;
|
|
2293
2483
|
}
|
|
2294
|
-
};
|
|
2484
|
+
});
|
|
2295
2485
|
}
|
|
2296
2486
|
}),
|
|
2297
|
-
build: () => {
|
|
2487
|
+
build: (options) => {
|
|
2298
2488
|
for (const proj of pendingProjections) {
|
|
2299
2489
|
mergeProjection(proj, registry.events);
|
|
2300
2490
|
registerBatchHandler(proj, batchHandlers);
|
|
@@ -2302,7 +2492,8 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
|
2302
2492
|
return new Act(
|
|
2303
2493
|
registry,
|
|
2304
2494
|
states,
|
|
2305
|
-
batchHandlers
|
|
2495
|
+
batchHandlers,
|
|
2496
|
+
options
|
|
2306
2497
|
);
|
|
2307
2498
|
},
|
|
2308
2499
|
events: registry.events
|
|
@@ -2310,8 +2501,9 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
|
2310
2501
|
return builder;
|
|
2311
2502
|
}
|
|
2312
2503
|
|
|
2313
|
-
// src/projection-builder.ts
|
|
2314
|
-
function _projection(target
|
|
2504
|
+
// src/builders/projection-builder.ts
|
|
2505
|
+
function _projection(target) {
|
|
2506
|
+
const events = {};
|
|
2315
2507
|
const defaultResolver = typeof target === "string" ? { target } : void 0;
|
|
2316
2508
|
const base = {
|
|
2317
2509
|
on: (entry) => {
|
|
@@ -2341,17 +2533,13 @@ function _projection(target, events) {
|
|
|
2341
2533
|
`Projection handler for "${event}" must be a named function`
|
|
2342
2534
|
);
|
|
2343
2535
|
register.reactions.set(handler.name, reaction);
|
|
2344
|
-
const
|
|
2345
|
-
return {
|
|
2346
|
-
...nextBuilder,
|
|
2536
|
+
const widened = base;
|
|
2537
|
+
return Object.assign(widened, {
|
|
2347
2538
|
to(resolver) {
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
resolver: typeof resolver === "string" ? { target: resolver } : resolver
|
|
2351
|
-
});
|
|
2352
|
-
return nextBuilder;
|
|
2539
|
+
reaction.resolver = typeof resolver === "string" ? { target: resolver } : resolver;
|
|
2540
|
+
return widened;
|
|
2353
2541
|
}
|
|
2354
|
-
};
|
|
2542
|
+
});
|
|
2355
2543
|
}
|
|
2356
2544
|
};
|
|
2357
2545
|
},
|
|
@@ -2363,8 +2551,7 @@ function _projection(target, events) {
|
|
|
2363
2551
|
events
|
|
2364
2552
|
};
|
|
2365
2553
|
if (typeof target === "string") {
|
|
2366
|
-
return {
|
|
2367
|
-
...base,
|
|
2554
|
+
return Object.assign(base, {
|
|
2368
2555
|
batch: (handler) => ({
|
|
2369
2556
|
build: () => ({
|
|
2370
2557
|
_tag: "Projection",
|
|
@@ -2373,34 +2560,28 @@ function _projection(target, events) {
|
|
|
2373
2560
|
batchHandler: handler
|
|
2374
2561
|
})
|
|
2375
2562
|
})
|
|
2376
|
-
};
|
|
2563
|
+
});
|
|
2377
2564
|
}
|
|
2378
2565
|
return base;
|
|
2379
2566
|
}
|
|
2380
|
-
function projection(target
|
|
2381
|
-
return _projection(target
|
|
2567
|
+
function projection(target) {
|
|
2568
|
+
return _projection(target);
|
|
2382
2569
|
}
|
|
2383
2570
|
|
|
2384
|
-
// src/slice-builder.ts
|
|
2385
|
-
function slice(
|
|
2571
|
+
// src/builders/slice-builder.ts
|
|
2572
|
+
function slice() {
|
|
2573
|
+
const states = /* @__PURE__ */ new Map();
|
|
2574
|
+
const actions = {};
|
|
2575
|
+
const events = {};
|
|
2576
|
+
const projections = [];
|
|
2386
2577
|
const builder = {
|
|
2387
2578
|
withState: (state2) => {
|
|
2388
2579
|
registerState(state2, states, actions, events);
|
|
2389
|
-
return
|
|
2390
|
-
states,
|
|
2391
|
-
actions,
|
|
2392
|
-
events,
|
|
2393
|
-
projections
|
|
2394
|
-
);
|
|
2580
|
+
return builder;
|
|
2395
2581
|
},
|
|
2396
2582
|
withProjection: (proj) => {
|
|
2397
2583
|
projections.push(proj);
|
|
2398
|
-
return
|
|
2399
|
-
states,
|
|
2400
|
-
actions,
|
|
2401
|
-
events,
|
|
2402
|
-
projections
|
|
2403
|
-
);
|
|
2584
|
+
return builder;
|
|
2404
2585
|
},
|
|
2405
2586
|
on: (event) => ({
|
|
2406
2587
|
do: (handler, options) => {
|
|
@@ -2417,16 +2598,12 @@ function slice(states = /* @__PURE__ */ new Map(), actions = {}, events = {}, pr
|
|
|
2417
2598
|
`Reaction handler for "${String(event)}" must be a named function`
|
|
2418
2599
|
);
|
|
2419
2600
|
events[event].reactions.set(handler.name, reaction);
|
|
2420
|
-
return {
|
|
2421
|
-
...builder,
|
|
2601
|
+
return Object.assign(builder, {
|
|
2422
2602
|
to(resolver) {
|
|
2423
|
-
|
|
2424
|
-
...reaction,
|
|
2425
|
-
resolver: typeof resolver === "string" ? { target: resolver } : resolver
|
|
2426
|
-
});
|
|
2603
|
+
reaction.resolver = typeof resolver === "string" ? { target: resolver } : resolver;
|
|
2427
2604
|
return builder;
|
|
2428
2605
|
}
|
|
2429
|
-
};
|
|
2606
|
+
});
|
|
2430
2607
|
}
|
|
2431
2608
|
}),
|
|
2432
2609
|
build: () => ({
|
|
@@ -2440,7 +2617,7 @@ function slice(states = /* @__PURE__ */ new Map(), actions = {}, events = {}, pr
|
|
|
2440
2617
|
return builder;
|
|
2441
2618
|
}
|
|
2442
2619
|
|
|
2443
|
-
// src/state-builder.ts
|
|
2620
|
+
// src/builders/state-builder.ts
|
|
2444
2621
|
function state(entry) {
|
|
2445
2622
|
const keys = Object.keys(entry);
|
|
2446
2623
|
if (keys.length !== 1) throw new Error("state() requires exactly one key");
|
|
@@ -2458,7 +2635,7 @@ function state(entry) {
|
|
|
2458
2635
|
return [k, fn];
|
|
2459
2636
|
})
|
|
2460
2637
|
);
|
|
2461
|
-
const
|
|
2638
|
+
const internal = {
|
|
2462
2639
|
events,
|
|
2463
2640
|
actions: {},
|
|
2464
2641
|
state: stateSchema,
|
|
@@ -2466,18 +2643,12 @@ function state(entry) {
|
|
|
2466
2643
|
init,
|
|
2467
2644
|
patch: defaultPatch,
|
|
2468
2645
|
on: {}
|
|
2469
|
-
}
|
|
2646
|
+
};
|
|
2647
|
+
const builder = action_builder(internal);
|
|
2470
2648
|
return Object.assign(builder, {
|
|
2471
2649
|
patch(customPatch) {
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
actions: {},
|
|
2475
|
-
state: stateSchema,
|
|
2476
|
-
name,
|
|
2477
|
-
init,
|
|
2478
|
-
patch: { ...defaultPatch, ...customPatch },
|
|
2479
|
-
on: {}
|
|
2480
|
-
});
|
|
2650
|
+
Object.assign(internal.patch, customPatch);
|
|
2651
|
+
return builder;
|
|
2481
2652
|
}
|
|
2482
2653
|
});
|
|
2483
2654
|
}
|
|
@@ -2486,50 +2657,43 @@ function state(entry) {
|
|
|
2486
2657
|
};
|
|
2487
2658
|
}
|
|
2488
2659
|
function action_builder(state2) {
|
|
2489
|
-
|
|
2660
|
+
const internal = state2;
|
|
2661
|
+
const builder = {
|
|
2490
2662
|
on(entry) {
|
|
2491
2663
|
const keys = Object.keys(entry);
|
|
2492
2664
|
if (keys.length !== 1) throw new Error(".on() requires exactly one key");
|
|
2493
2665
|
const action2 = keys[0];
|
|
2494
2666
|
const schema = entry[action2];
|
|
2495
|
-
if (action2 in
|
|
2667
|
+
if (action2 in internal.actions)
|
|
2496
2668
|
throw new Error(`Duplicate action "${action2}"`);
|
|
2497
|
-
|
|
2498
|
-
...state2.actions,
|
|
2499
|
-
[action2]: schema
|
|
2500
|
-
};
|
|
2501
|
-
const on = { ...state2.on };
|
|
2502
|
-
const _given = { ...state2.given };
|
|
2669
|
+
internal.actions[action2] = schema;
|
|
2503
2670
|
function given(rules) {
|
|
2504
|
-
|
|
2671
|
+
(internal.given ??= {})[action2] = rules;
|
|
2505
2672
|
return { emit };
|
|
2506
2673
|
}
|
|
2507
2674
|
function emit(handler) {
|
|
2508
2675
|
if (typeof handler === "string") {
|
|
2509
2676
|
const eventName = handler;
|
|
2510
|
-
on[action2] = (
|
|
2677
|
+
internal.on[action2] = (payload) => [
|
|
2678
|
+
eventName,
|
|
2679
|
+
payload
|
|
2680
|
+
];
|
|
2511
2681
|
} else {
|
|
2512
|
-
on[action2] = handler;
|
|
2682
|
+
internal.on[action2] = handler;
|
|
2513
2683
|
}
|
|
2514
|
-
return
|
|
2515
|
-
...state2,
|
|
2516
|
-
actions,
|
|
2517
|
-
on,
|
|
2518
|
-
given: _given
|
|
2519
|
-
});
|
|
2684
|
+
return builder;
|
|
2520
2685
|
}
|
|
2521
2686
|
return { given, emit };
|
|
2522
2687
|
},
|
|
2523
2688
|
snap(snap2) {
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
snap: snap2
|
|
2527
|
-
});
|
|
2689
|
+
internal.snap = snap2;
|
|
2690
|
+
return builder;
|
|
2528
2691
|
},
|
|
2529
2692
|
build() {
|
|
2530
|
-
return
|
|
2693
|
+
return internal;
|
|
2531
2694
|
}
|
|
2532
2695
|
};
|
|
2696
|
+
return builder;
|
|
2533
2697
|
}
|
|
2534
2698
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2535
2699
|
0 && (module.exports = {
|
|
@@ -2539,6 +2703,7 @@ function action_builder(state2) {
|
|
|
2539
2703
|
CommittedMetaSchema,
|
|
2540
2704
|
ConcurrencyError,
|
|
2541
2705
|
ConsoleLogger,
|
|
2706
|
+
DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
2542
2707
|
Environments,
|
|
2543
2708
|
Errors,
|
|
2544
2709
|
EventMetaSchema,
|