@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.js
CHANGED
|
@@ -117,26 +117,75 @@ var ConsoleLogger = class _ConsoleLogger {
|
|
|
117
117
|
}
|
|
118
118
|
};
|
|
119
119
|
|
|
120
|
+
// src/internal/lru-map.ts
|
|
121
|
+
var LruMap = class {
|
|
122
|
+
constructor(_maxSize) {
|
|
123
|
+
this._maxSize = _maxSize;
|
|
124
|
+
}
|
|
125
|
+
_entries = /* @__PURE__ */ new Map();
|
|
126
|
+
get(key) {
|
|
127
|
+
const v = this._entries.get(key);
|
|
128
|
+
if (v === void 0) return void 0;
|
|
129
|
+
this._entries.delete(key);
|
|
130
|
+
this._entries.set(key, v);
|
|
131
|
+
return v;
|
|
132
|
+
}
|
|
133
|
+
has(key) {
|
|
134
|
+
return this._entries.has(key);
|
|
135
|
+
}
|
|
136
|
+
set(key, value) {
|
|
137
|
+
this._entries.delete(key);
|
|
138
|
+
if (this._entries.size >= this._maxSize) {
|
|
139
|
+
const oldest = this._entries.keys().next().value;
|
|
140
|
+
if (oldest !== void 0) this._entries.delete(oldest);
|
|
141
|
+
}
|
|
142
|
+
this._entries.set(key, value);
|
|
143
|
+
}
|
|
144
|
+
delete(key) {
|
|
145
|
+
return this._entries.delete(key);
|
|
146
|
+
}
|
|
147
|
+
clear() {
|
|
148
|
+
this._entries.clear();
|
|
149
|
+
}
|
|
150
|
+
get size() {
|
|
151
|
+
return this._entries.size;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
var LruSet = class {
|
|
155
|
+
_map;
|
|
156
|
+
constructor(maxSize) {
|
|
157
|
+
this._map = new LruMap(maxSize);
|
|
158
|
+
}
|
|
159
|
+
has(value) {
|
|
160
|
+
return this._map.has(value);
|
|
161
|
+
}
|
|
162
|
+
add(value) {
|
|
163
|
+
this._map.set(value, true);
|
|
164
|
+
}
|
|
165
|
+
delete(value) {
|
|
166
|
+
return this._map.delete(value);
|
|
167
|
+
}
|
|
168
|
+
clear() {
|
|
169
|
+
this._map.clear();
|
|
170
|
+
}
|
|
171
|
+
get size() {
|
|
172
|
+
return this._map.size;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
120
176
|
// src/adapters/InMemoryCache.ts
|
|
121
177
|
var InMemoryCache = class {
|
|
122
|
-
|
|
123
|
-
|
|
178
|
+
// CacheEntry<any> lets `get<TState>` and `set<TState>` flow without casts:
|
|
179
|
+
// any is bidirectionally compatible with the per-call TState binding, while
|
|
180
|
+
// the public Cache interface still presents a typed surface to callers.
|
|
181
|
+
_entries;
|
|
124
182
|
constructor(options) {
|
|
125
|
-
this.
|
|
183
|
+
this._entries = new LruMap(options?.maxSize ?? 1e3);
|
|
126
184
|
}
|
|
127
185
|
async get(stream) {
|
|
128
|
-
|
|
129
|
-
if (!entry) return void 0;
|
|
130
|
-
this._entries.delete(stream);
|
|
131
|
-
this._entries.set(stream, entry);
|
|
132
|
-
return entry;
|
|
186
|
+
return this._entries.get(stream);
|
|
133
187
|
}
|
|
134
188
|
async set(stream, entry) {
|
|
135
|
-
this._entries.delete(stream);
|
|
136
|
-
if (this._entries.size >= this._maxSize) {
|
|
137
|
-
const first = this._entries.keys().next().value;
|
|
138
|
-
this._entries.delete(first);
|
|
139
|
-
}
|
|
140
189
|
this._entries.set(stream, entry);
|
|
141
190
|
}
|
|
142
191
|
async invalidate(stream) {
|
|
@@ -179,12 +228,19 @@ var BaseSchema = PackageSchema.extend({
|
|
|
179
228
|
});
|
|
180
229
|
var { NODE_ENV, LOG_LEVEL, LOG_SINGLE_LINE, SLEEP_MS } = process.env;
|
|
181
230
|
var env = NODE_ENV || "development";
|
|
182
|
-
var logLevel = LOG_LEVEL || (NODE_ENV === "test" ? "
|
|
231
|
+
var logLevel = LOG_LEVEL || (NODE_ENV === "test" ? "fatal" : NODE_ENV === "production" ? "info" : "trace");
|
|
183
232
|
var logSingleLine = (LOG_SINGLE_LINE || "true") === "true";
|
|
184
233
|
var sleepMs = parseInt(NODE_ENV === "test" ? "0" : SLEEP_MS ?? "100", 10);
|
|
185
234
|
var pkg = getPackage();
|
|
235
|
+
var _validated;
|
|
186
236
|
var config = () => {
|
|
187
|
-
|
|
237
|
+
if (!_validated) {
|
|
238
|
+
_validated = extend(
|
|
239
|
+
{ ...pkg, env, logLevel, logSingleLine, sleepMs },
|
|
240
|
+
BaseSchema
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
return _validated;
|
|
188
244
|
};
|
|
189
245
|
|
|
190
246
|
// src/utils.ts
|
|
@@ -607,7 +663,7 @@ function port(injector) {
|
|
|
607
663
|
if (!adapters.has(injector.name)) {
|
|
608
664
|
const injected = injector(adapter);
|
|
609
665
|
adapters.set(injector.name, injected);
|
|
610
|
-
|
|
666
|
+
log().info(`[act] + ${injector.name}:${injected.constructor.name}`);
|
|
611
667
|
}
|
|
612
668
|
return adapters.get(injector.name);
|
|
613
669
|
};
|
|
@@ -633,7 +689,7 @@ async function disposeAndExit(code = "EXIT") {
|
|
|
633
689
|
}
|
|
634
690
|
for (const adapter of [...adapters.values()].reverse()) {
|
|
635
691
|
await adapter.dispose();
|
|
636
|
-
|
|
692
|
+
log().info(`[act] - ${adapter.constructor.name}`);
|
|
637
693
|
}
|
|
638
694
|
adapters.clear();
|
|
639
695
|
config().env !== "test" && process.exit(code === "ERROR" ? 1 : 0);
|
|
@@ -664,9 +720,233 @@ process.once("unhandledRejection", async (arg) => {
|
|
|
664
720
|
});
|
|
665
721
|
|
|
666
722
|
// src/act.ts
|
|
667
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
668
723
|
import EventEmitter from "events";
|
|
669
724
|
|
|
725
|
+
// src/internal/close-cycle.ts
|
|
726
|
+
import { randomUUID } from "crypto";
|
|
727
|
+
async function runCloseCycle(targets, deps) {
|
|
728
|
+
if (!targets.length) return { truncated: /* @__PURE__ */ new Map(), skipped: [] };
|
|
729
|
+
const targetMap = new Map(targets.map((t) => [t.stream, t]));
|
|
730
|
+
const streams = [...targetMap.keys()];
|
|
731
|
+
const skipped = [];
|
|
732
|
+
const streamInfo = await scanStreamHeads(streams);
|
|
733
|
+
const safe = await partitionBySafety(
|
|
734
|
+
streamInfo,
|
|
735
|
+
deps.reactiveEventsSize,
|
|
736
|
+
skipped
|
|
737
|
+
);
|
|
738
|
+
if (!safe.length) return { truncated: /* @__PURE__ */ new Map(), skipped };
|
|
739
|
+
const correlation = randomUUID();
|
|
740
|
+
const { guarded, guardEvents } = await guardWithTombstones(
|
|
741
|
+
safe,
|
|
742
|
+
streamInfo,
|
|
743
|
+
correlation,
|
|
744
|
+
deps.tombstone,
|
|
745
|
+
skipped
|
|
746
|
+
);
|
|
747
|
+
if (!guarded.length) return { truncated: /* @__PURE__ */ new Map(), skipped };
|
|
748
|
+
const seedStates = await loadRestartSeeds(
|
|
749
|
+
guarded,
|
|
750
|
+
targetMap,
|
|
751
|
+
streamInfo,
|
|
752
|
+
deps.eventToState,
|
|
753
|
+
deps.load,
|
|
754
|
+
deps.logger
|
|
755
|
+
);
|
|
756
|
+
await runArchiveCallbacks(guarded, targetMap);
|
|
757
|
+
const truncated = await truncateAndWarmCache(
|
|
758
|
+
guarded,
|
|
759
|
+
seedStates,
|
|
760
|
+
guardEvents,
|
|
761
|
+
correlation
|
|
762
|
+
);
|
|
763
|
+
return { truncated, skipped };
|
|
764
|
+
}
|
|
765
|
+
async function scanStreamHeads(streams) {
|
|
766
|
+
const out = /* @__PURE__ */ new Map();
|
|
767
|
+
await Promise.all(
|
|
768
|
+
streams.map(async (s) => {
|
|
769
|
+
let maxId = -1;
|
|
770
|
+
let version = -1;
|
|
771
|
+
let lastEventName;
|
|
772
|
+
await store().query(
|
|
773
|
+
(e) => {
|
|
774
|
+
if (e.name === TOMBSTONE_EVENT) return;
|
|
775
|
+
if (maxId === -1) {
|
|
776
|
+
maxId = e.id;
|
|
777
|
+
version = e.version;
|
|
778
|
+
}
|
|
779
|
+
if (e.name !== SNAP_EVENT && lastEventName === void 0) {
|
|
780
|
+
lastEventName = e.name;
|
|
781
|
+
}
|
|
782
|
+
},
|
|
783
|
+
// limit: 2 covers the typical snapshot-at-head case (snapshot is
|
|
784
|
+
// always preceded by the domain event it captured). Streams with
|
|
785
|
+
// unusual layouts fall back to no-seed via the lookup miss path.
|
|
786
|
+
{ stream: s, stream_exact: true, backward: true, limit: 2 }
|
|
787
|
+
);
|
|
788
|
+
if (maxId >= 0) out.set(s, { maxId, version, lastEventName });
|
|
789
|
+
})
|
|
790
|
+
);
|
|
791
|
+
return out;
|
|
792
|
+
}
|
|
793
|
+
async function partitionBySafety(streamInfo, reactiveEventsSize, skipped) {
|
|
794
|
+
if (reactiveEventsSize === 0) return [...streamInfo.keys()];
|
|
795
|
+
const pendingSet = /* @__PURE__ */ new Set();
|
|
796
|
+
await store().query_streams((position) => {
|
|
797
|
+
const sourceRe = position.source ? RegExp(position.source) : void 0;
|
|
798
|
+
for (const [stream, info] of streamInfo) {
|
|
799
|
+
if ((!sourceRe || sourceRe.test(stream)) && position.at < info.maxId) {
|
|
800
|
+
pendingSet.add(stream);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
const safe = [];
|
|
805
|
+
for (const [stream] of streamInfo) {
|
|
806
|
+
if (pendingSet.has(stream)) skipped.push(stream);
|
|
807
|
+
else safe.push(stream);
|
|
808
|
+
}
|
|
809
|
+
return safe;
|
|
810
|
+
}
|
|
811
|
+
async function guardWithTombstones(safe, streamInfo, correlation, tombstone2, skipped) {
|
|
812
|
+
const guarded = [];
|
|
813
|
+
const guardEvents = /* @__PURE__ */ new Map();
|
|
814
|
+
await Promise.all(
|
|
815
|
+
safe.map(async (stream) => {
|
|
816
|
+
const info = streamInfo.get(stream);
|
|
817
|
+
const committed = await tombstone2(stream, info.version, correlation);
|
|
818
|
+
if (committed) {
|
|
819
|
+
guarded.push(stream);
|
|
820
|
+
guardEvents.set(stream, { id: committed.id, stream });
|
|
821
|
+
} else {
|
|
822
|
+
skipped.push(stream);
|
|
823
|
+
}
|
|
824
|
+
})
|
|
825
|
+
);
|
|
826
|
+
return { guarded, guardEvents };
|
|
827
|
+
}
|
|
828
|
+
async function loadRestartSeeds(guarded, targetMap, streamInfo, eventToState, load2, logger) {
|
|
829
|
+
const seedStates = /* @__PURE__ */ new Map();
|
|
830
|
+
await Promise.all(
|
|
831
|
+
guarded.filter((s) => targetMap.get(s)?.restart).map(async (stream) => {
|
|
832
|
+
const lastEventName = streamInfo.get(stream)?.lastEventName;
|
|
833
|
+
const ownerState = lastEventName ? eventToState.get(lastEventName) : void 0;
|
|
834
|
+
if (!ownerState) {
|
|
835
|
+
logger.error(
|
|
836
|
+
`Cannot seed restart for "${stream}": no registered state owns event "${lastEventName ?? "<none>"}". Stream will be tombstoned instead.`
|
|
837
|
+
);
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
const snap2 = await load2(ownerState, stream);
|
|
841
|
+
seedStates.set(stream, snap2.state);
|
|
842
|
+
})
|
|
843
|
+
);
|
|
844
|
+
return seedStates;
|
|
845
|
+
}
|
|
846
|
+
async function runArchiveCallbacks(guarded, targetMap) {
|
|
847
|
+
for (const stream of guarded) {
|
|
848
|
+
const archiveFn = targetMap.get(stream)?.archive;
|
|
849
|
+
if (archiveFn) await archiveFn();
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
async function truncateAndWarmCache(guarded, seedStates, guardEvents, correlation) {
|
|
853
|
+
const truncTargets = guarded.map((stream) => {
|
|
854
|
+
const snapshot = seedStates.get(stream);
|
|
855
|
+
const guard = guardEvents.get(stream);
|
|
856
|
+
return {
|
|
857
|
+
stream,
|
|
858
|
+
snapshot,
|
|
859
|
+
meta: {
|
|
860
|
+
correlation,
|
|
861
|
+
causation: {
|
|
862
|
+
event: { id: guard.id, name: TOMBSTONE_EVENT, stream: guard.stream }
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
});
|
|
867
|
+
const truncated = await store().truncate(truncTargets);
|
|
868
|
+
await Promise.all(
|
|
869
|
+
guarded.map(async (stream) => {
|
|
870
|
+
const entry = truncated.get(stream);
|
|
871
|
+
const state2 = seedStates.get(stream);
|
|
872
|
+
if (state2 && entry) {
|
|
873
|
+
await cache().set(stream, {
|
|
874
|
+
state: state2,
|
|
875
|
+
version: entry.committed.version,
|
|
876
|
+
event_id: entry.committed.id,
|
|
877
|
+
patches: 0,
|
|
878
|
+
snaps: 1
|
|
879
|
+
});
|
|
880
|
+
} else {
|
|
881
|
+
await cache().invalidate(stream);
|
|
882
|
+
}
|
|
883
|
+
})
|
|
884
|
+
);
|
|
885
|
+
return truncated;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// src/internal/drain-cycle.ts
|
|
889
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
890
|
+
async function runDrainCycle(ops, registry, batchHandlers, handle, handleBatch, lagging, leading, eventLimit, leaseMillis) {
|
|
891
|
+
const leased = await ops.claim(lagging, leading, randomUUID2(), leaseMillis);
|
|
892
|
+
if (!leased.length) return void 0;
|
|
893
|
+
const fetched = await ops.fetch(leased, eventLimit);
|
|
894
|
+
const fetchMap = /* @__PURE__ */ new Map();
|
|
895
|
+
const fetch_window_at = fetched.reduce(
|
|
896
|
+
(max, { at, events }) => Math.max(max, events.at(-1)?.id || at),
|
|
897
|
+
0
|
|
898
|
+
);
|
|
899
|
+
for (const f of fetched) {
|
|
900
|
+
const { stream, events } = f;
|
|
901
|
+
const payloads = events.flatMap((event) => {
|
|
902
|
+
const register = registry.events[event.name];
|
|
903
|
+
if (!register) return [];
|
|
904
|
+
return [...register.reactions.values()].filter((reaction) => {
|
|
905
|
+
const resolved = typeof reaction.resolver === "function" ? reaction.resolver(event) : reaction.resolver;
|
|
906
|
+
return resolved && resolved.target === stream;
|
|
907
|
+
}).map((reaction) => ({ ...reaction, event }));
|
|
908
|
+
});
|
|
909
|
+
fetchMap.set(stream, { fetch: f, payloads });
|
|
910
|
+
}
|
|
911
|
+
const handled = await Promise.all(
|
|
912
|
+
leased.map((lease) => {
|
|
913
|
+
const entry = fetchMap.get(lease.stream);
|
|
914
|
+
const at = entry?.fetch.events.at(-1)?.id || fetch_window_at;
|
|
915
|
+
const payloads = entry?.payloads ?? [];
|
|
916
|
+
const batchHandler = batchHandlers.get(lease.stream);
|
|
917
|
+
if (batchHandler && payloads.length > 0) {
|
|
918
|
+
return handleBatch({ ...lease, at }, payloads, batchHandler);
|
|
919
|
+
}
|
|
920
|
+
return handle({ ...lease, at }, payloads);
|
|
921
|
+
})
|
|
922
|
+
);
|
|
923
|
+
const acked = await ops.ack(
|
|
924
|
+
handled.filter(({ error }) => !error).map(({ at, lease }) => ({ ...lease, at }))
|
|
925
|
+
);
|
|
926
|
+
const blocked = await ops.block(
|
|
927
|
+
handled.filter(({ block: block2 }) => block2).map(({ lease, error }) => ({ ...lease, error }))
|
|
928
|
+
);
|
|
929
|
+
return { leased, fetched, handled, acked, blocked };
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// src/internal/drain-ratio.ts
|
|
933
|
+
var RATIO_MIN = 0.2;
|
|
934
|
+
var RATIO_MAX = 0.8;
|
|
935
|
+
var RATIO_DEFAULT = 0.5;
|
|
936
|
+
function computeLagLeadRatio(handled, lagging, leading) {
|
|
937
|
+
let lagging_handled = 0;
|
|
938
|
+
let leading_handled = 0;
|
|
939
|
+
for (const { lease, handled: count } of handled) {
|
|
940
|
+
if (lease.lagging) lagging_handled += count;
|
|
941
|
+
else leading_handled += count;
|
|
942
|
+
}
|
|
943
|
+
const lagging_avg = lagging > 0 ? lagging_handled / lagging : 0;
|
|
944
|
+
const leading_avg = leading > 0 ? leading_handled / leading : 0;
|
|
945
|
+
const total = lagging_avg + leading_avg;
|
|
946
|
+
if (total === 0) return RATIO_DEFAULT;
|
|
947
|
+
return Math.max(RATIO_MIN, Math.min(RATIO_MAX, lagging_avg / total));
|
|
948
|
+
}
|
|
949
|
+
|
|
670
950
|
// src/internal/merge.ts
|
|
671
951
|
import { ZodObject } from "zod";
|
|
672
952
|
function baseTypeName(zodType) {
|
|
@@ -774,6 +1054,15 @@ function mergePatches(existing, incoming, stateName) {
|
|
|
774
1054
|
}
|
|
775
1055
|
return merged;
|
|
776
1056
|
}
|
|
1057
|
+
function mergeEventRegister(target, source) {
|
|
1058
|
+
for (const [eventName, sourceReg] of Object.entries(source)) {
|
|
1059
|
+
const targetReg = target[eventName];
|
|
1060
|
+
if (!targetReg) continue;
|
|
1061
|
+
for (const [name, reaction] of sourceReg.reactions) {
|
|
1062
|
+
targetReg.reactions.set(name, reaction);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
777
1066
|
function mergeProjection(proj, events) {
|
|
778
1067
|
for (const eventName of Object.keys(proj.events)) {
|
|
779
1068
|
const projRegister = proj.events[eventName];
|
|
@@ -818,7 +1107,7 @@ var subscribe = (streams) => store().subscribe(streams);
|
|
|
818
1107
|
|
|
819
1108
|
// src/internal/event-sourcing.ts
|
|
820
1109
|
import { patch } from "@rotorsoft/act-patch";
|
|
821
|
-
import { randomUUID } from "crypto";
|
|
1110
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
822
1111
|
async function snap(snapshot) {
|
|
823
1112
|
try {
|
|
824
1113
|
const { id, stream, name, meta, version } = snapshot.event;
|
|
@@ -836,6 +1125,20 @@ async function snap(snapshot) {
|
|
|
836
1125
|
log().error(error);
|
|
837
1126
|
}
|
|
838
1127
|
}
|
|
1128
|
+
async function tombstone(stream, expectedVersion, correlation) {
|
|
1129
|
+
try {
|
|
1130
|
+
const [committed] = await store().commit(
|
|
1131
|
+
stream,
|
|
1132
|
+
[{ name: TOMBSTONE_EVENT, data: {} }],
|
|
1133
|
+
{ correlation, causation: {} },
|
|
1134
|
+
expectedVersion
|
|
1135
|
+
);
|
|
1136
|
+
return committed;
|
|
1137
|
+
} catch (error) {
|
|
1138
|
+
if (error instanceof ConcurrencyError) return void 0;
|
|
1139
|
+
throw error;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
839
1142
|
async function load(me, stream, callback, asOf) {
|
|
840
1143
|
const timeTravel = !!asOf && Object.values(asOf).some((v) => v !== void 0);
|
|
841
1144
|
const cached = timeTravel ? void 0 : await cache().get(stream);
|
|
@@ -900,7 +1203,7 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
900
1203
|
data: skipValidation ? data : validate(name, data, me.events[name])
|
|
901
1204
|
}));
|
|
902
1205
|
const meta = {
|
|
903
|
-
correlation: reactingTo?.meta.correlation ||
|
|
1206
|
+
correlation: reactingTo?.meta.correlation || randomUUID3(),
|
|
904
1207
|
causation: {
|
|
905
1208
|
action: {
|
|
906
1209
|
name: action2,
|
|
@@ -951,6 +1254,18 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
951
1254
|
}
|
|
952
1255
|
|
|
953
1256
|
// src/internal/tracing.ts
|
|
1257
|
+
var PRETTY = config().env !== "production";
|
|
1258
|
+
var C_BLUE = "\x1B[38;5;39m";
|
|
1259
|
+
var C_ORANGE = "\x1B[38;5;208m";
|
|
1260
|
+
var C_GREEN = "\x1B[38;5;42m";
|
|
1261
|
+
var C_MAGENTA = "\x1B[38;5;165m";
|
|
1262
|
+
var C_DRAIN = "\x1B[38;5;244m";
|
|
1263
|
+
var C_RESET = "\x1B[0m";
|
|
1264
|
+
var es_caption = (caption, color, body) => PRETTY ? `${color}${body}${C_RESET}` : `${caption}: ${body}`;
|
|
1265
|
+
var drain_caption = (caption) => {
|
|
1266
|
+
const tag = `>> ${caption}`;
|
|
1267
|
+
return PRETTY ? `${C_DRAIN}${tag}${C_RESET}` : tag;
|
|
1268
|
+
};
|
|
954
1269
|
var traced = (inner, exit, entry) => (async (...args) => {
|
|
955
1270
|
entry?.(...args);
|
|
956
1271
|
const result = await inner(...args);
|
|
@@ -959,16 +1274,27 @@ var traced = (inner, exit, entry) => (async (...args) => {
|
|
|
959
1274
|
});
|
|
960
1275
|
function buildEs(logger) {
|
|
961
1276
|
if (logger.level !== "trace") {
|
|
962
|
-
return {
|
|
1277
|
+
return {
|
|
1278
|
+
snap,
|
|
1279
|
+
load,
|
|
1280
|
+
action,
|
|
1281
|
+
tombstone
|
|
1282
|
+
};
|
|
963
1283
|
}
|
|
964
1284
|
return {
|
|
965
1285
|
snap: traced(snap, void 0, (snapshot) => {
|
|
966
1286
|
logger.trace(
|
|
967
|
-
|
|
1287
|
+
es_caption(
|
|
1288
|
+
"snap",
|
|
1289
|
+
C_MAGENTA,
|
|
1290
|
+
`${snapshot.event.stream}@${snapshot.event.version}`
|
|
1291
|
+
)
|
|
968
1292
|
);
|
|
969
1293
|
}),
|
|
970
1294
|
load: traced(load, void 0, (_me, stream, _cb, asOf) => {
|
|
971
|
-
logger.trace(
|
|
1295
|
+
logger.trace(
|
|
1296
|
+
es_caption("load", C_GREEN, `${stream}${asOf ? " (as-of)" : ""}`)
|
|
1297
|
+
);
|
|
972
1298
|
}),
|
|
973
1299
|
action: traced(
|
|
974
1300
|
action,
|
|
@@ -977,14 +1303,27 @@ function buildEs(logger) {
|
|
|
977
1303
|
if (committed.length) {
|
|
978
1304
|
logger.trace(
|
|
979
1305
|
committed.map((s) => s.event.data),
|
|
980
|
-
|
|
1306
|
+
es_caption(
|
|
1307
|
+
"committed",
|
|
1308
|
+
C_ORANGE,
|
|
1309
|
+
`${target.stream}.${committed.map((s) => s.event.name).join(", ")}`
|
|
1310
|
+
)
|
|
981
1311
|
);
|
|
982
1312
|
}
|
|
983
1313
|
},
|
|
984
1314
|
(_me, action2, target, payload) => {
|
|
985
|
-
logger.trace(
|
|
1315
|
+
logger.trace(
|
|
1316
|
+
payload,
|
|
1317
|
+
es_caption("action", C_BLUE, `${target.stream}.${action2}`)
|
|
1318
|
+
);
|
|
986
1319
|
}
|
|
987
|
-
)
|
|
1320
|
+
),
|
|
1321
|
+
tombstone: traced(tombstone, (committed, stream) => {
|
|
1322
|
+
if (committed)
|
|
1323
|
+
logger.trace(
|
|
1324
|
+
es_caption("tombstoned", C_ORANGE, `${stream}@${committed.version}`)
|
|
1325
|
+
);
|
|
1326
|
+
})
|
|
988
1327
|
};
|
|
989
1328
|
}
|
|
990
1329
|
function buildDrain(logger) {
|
|
@@ -1003,7 +1342,7 @@ function buildDrain(logger) {
|
|
|
1003
1342
|
const data = Object.fromEntries(
|
|
1004
1343
|
leased.map(({ stream, at, retry }) => [stream, { at, retry }])
|
|
1005
1344
|
);
|
|
1006
|
-
logger.trace(data, "
|
|
1345
|
+
logger.trace(data, drain_caption("claimed"));
|
|
1007
1346
|
}
|
|
1008
1347
|
}),
|
|
1009
1348
|
fetch: traced(fetch, (fetched) => {
|
|
@@ -1016,14 +1355,14 @@ function buildDrain(logger) {
|
|
|
1016
1355
|
return [key, value];
|
|
1017
1356
|
})
|
|
1018
1357
|
);
|
|
1019
|
-
logger.trace(data, "
|
|
1358
|
+
logger.trace(data, drain_caption("fetched"));
|
|
1020
1359
|
}),
|
|
1021
1360
|
ack: traced(ack, (acked) => {
|
|
1022
1361
|
if (acked.length) {
|
|
1023
1362
|
const data = Object.fromEntries(
|
|
1024
1363
|
acked.map(({ stream, at, retry }) => [stream, { at, retry }])
|
|
1025
1364
|
);
|
|
1026
|
-
logger.trace(data, "
|
|
1365
|
+
logger.trace(data, drain_caption("acked"));
|
|
1027
1366
|
}
|
|
1028
1367
|
}),
|
|
1029
1368
|
block: traced(block, (blocked) => {
|
|
@@ -1034,27 +1373,31 @@ function buildDrain(logger) {
|
|
|
1034
1373
|
{ at, retry, error }
|
|
1035
1374
|
])
|
|
1036
1375
|
);
|
|
1037
|
-
logger.trace(data, "
|
|
1376
|
+
logger.trace(data, drain_caption("blocked"));
|
|
1038
1377
|
}
|
|
1039
1378
|
}),
|
|
1040
1379
|
subscribe: traced(subscribe, (result, streams) => {
|
|
1041
1380
|
if (result.subscribed) {
|
|
1042
1381
|
const data = streams.map(({ stream }) => stream).join(" ");
|
|
1043
|
-
logger.trace(
|
|
1382
|
+
logger.trace(`${drain_caption("correlated")} ${data}`);
|
|
1044
1383
|
}
|
|
1045
1384
|
})
|
|
1046
1385
|
};
|
|
1047
1386
|
}
|
|
1048
1387
|
|
|
1049
1388
|
// src/act.ts
|
|
1389
|
+
var DEFAULT_MAX_SUBSCRIBED_STREAMS = 1e3;
|
|
1050
1390
|
var Act = class {
|
|
1051
|
-
constructor(registry, _states = /* @__PURE__ */ new Map(), batchHandlers = /* @__PURE__ */ new Map()) {
|
|
1391
|
+
constructor(registry, _states = /* @__PURE__ */ new Map(), batchHandlers = /* @__PURE__ */ new Map(), options = {}) {
|
|
1052
1392
|
this.registry = registry;
|
|
1053
1393
|
this._states = _states;
|
|
1054
1394
|
this._batch_handlers = batchHandlers;
|
|
1395
|
+
this._subscribed_streams = new LruSet(
|
|
1396
|
+
options.maxSubscribedStreams ?? DEFAULT_MAX_SUBSCRIBED_STREAMS
|
|
1397
|
+
);
|
|
1055
1398
|
this._es = buildEs(this._logger);
|
|
1056
1399
|
this._cd = buildDrain(this._logger);
|
|
1057
|
-
const statics =
|
|
1400
|
+
const statics = /* @__PURE__ */ new Map();
|
|
1058
1401
|
for (const [name, register] of Object.entries(this.registry.events)) {
|
|
1059
1402
|
if (register.reactions.size > 0) {
|
|
1060
1403
|
this._reactive_events.add(name);
|
|
@@ -1063,14 +1406,13 @@ var Act = class {
|
|
|
1063
1406
|
if (typeof reaction.resolver === "function") {
|
|
1064
1407
|
this._has_dynamic_resolvers = true;
|
|
1065
1408
|
} else {
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
});
|
|
1409
|
+
const { target, source } = reaction.resolver;
|
|
1410
|
+
const key = `${target}|${source ?? ""}`;
|
|
1411
|
+
if (!statics.has(key)) statics.set(key, { stream: target, source });
|
|
1070
1412
|
}
|
|
1071
1413
|
}
|
|
1072
1414
|
}
|
|
1073
|
-
this._static_targets = statics;
|
|
1415
|
+
this._static_targets = [...statics.values()];
|
|
1074
1416
|
for (const merged of this._states.values()) {
|
|
1075
1417
|
for (const eventName of Object.keys(merged.events)) {
|
|
1076
1418
|
this._event_to_state.set(eventName, merged);
|
|
@@ -1090,20 +1432,42 @@ var Act = class {
|
|
|
1090
1432
|
_settle_timer = void 0;
|
|
1091
1433
|
_settling = false;
|
|
1092
1434
|
_correlation_checkpoint = -1;
|
|
1093
|
-
|
|
1435
|
+
/**
|
|
1436
|
+
* Streams already subscribed via store.subscribe() — both the static
|
|
1437
|
+
* targets registered at init and dynamic targets discovered by
|
|
1438
|
+
* correlate(). correlate() consults this set to avoid re-subscribing
|
|
1439
|
+
* known streams.
|
|
1440
|
+
*
|
|
1441
|
+
* Bounded LRU so apps that mint millions of dynamic targets (one per
|
|
1442
|
+
* aggregate) don't grow this unbounded. Eviction costs at most one
|
|
1443
|
+
* redundant store.subscribe() call per evicted-but-still-active stream
|
|
1444
|
+
* (subscribe is idempotent). Cap configurable via {@link ActOptions}.
|
|
1445
|
+
*/
|
|
1446
|
+
_subscribed_streams;
|
|
1094
1447
|
_has_dynamic_resolvers = false;
|
|
1095
1448
|
_correlation_initialized = false;
|
|
1096
1449
|
/** Event names with at least one registered reaction (computed at build time) */
|
|
1097
1450
|
_reactive_events = /* @__PURE__ */ new Set();
|
|
1098
1451
|
/** Set in do() when a committed event has reactions — cleared by drain() */
|
|
1099
1452
|
_needs_drain = false;
|
|
1453
|
+
/**
|
|
1454
|
+
* Emit a lifecycle event. The payload type is inferred from the event name
|
|
1455
|
+
* via {@link ActLifecycleEvents}.
|
|
1456
|
+
*/
|
|
1100
1457
|
emit(event, args) {
|
|
1101
1458
|
return this._emitter.emit(event, args);
|
|
1102
1459
|
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Register a listener for a lifecycle event. The listener receives the
|
|
1462
|
+
* event-specific payload.
|
|
1463
|
+
*/
|
|
1103
1464
|
on(event, listener) {
|
|
1104
1465
|
this._emitter.on(event, listener);
|
|
1105
1466
|
return this;
|
|
1106
1467
|
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Remove a previously registered lifecycle listener.
|
|
1470
|
+
*/
|
|
1107
1471
|
off(event, listener) {
|
|
1108
1472
|
this._emitter.off(event, listener);
|
|
1109
1473
|
return this;
|
|
@@ -1131,6 +1495,15 @@ var Act = class {
|
|
|
1131
1495
|
_event_to_state = /* @__PURE__ */ new Map();
|
|
1132
1496
|
/** Logger resolved at construction time (after user port configuration) */
|
|
1133
1497
|
_logger = log();
|
|
1498
|
+
/** Pre-bound IAct methods reused across drain cycles. Only `do` varies per
|
|
1499
|
+
* payload (it captures the triggering event for reactingTo auto-inject). */
|
|
1500
|
+
_bound_do = this.do.bind(this);
|
|
1501
|
+
_bound_load = this.load.bind(this);
|
|
1502
|
+
_bound_query = this.query.bind(this);
|
|
1503
|
+
_bound_query_array = this.query_array.bind(this);
|
|
1504
|
+
/** Pre-bound dispatchers handed to runDrainCycle each cycle. */
|
|
1505
|
+
_bound_handle = this.handle.bind(this);
|
|
1506
|
+
_bound_handle_batch = this.handleBatch.bind(this);
|
|
1134
1507
|
/**
|
|
1135
1508
|
* Executes an action on a state instance, committing resulting events.
|
|
1136
1509
|
*
|
|
@@ -1333,35 +1706,55 @@ var Act = class {
|
|
|
1333
1706
|
return events;
|
|
1334
1707
|
}
|
|
1335
1708
|
/**
|
|
1336
|
-
*
|
|
1337
|
-
*
|
|
1338
|
-
*
|
|
1339
|
-
*
|
|
1709
|
+
* Shared finalization for the two reaction-runner shapes (per-event
|
|
1710
|
+
* `handle` and bulk `handleBatch`). Centralizes the error log, retry-vs-
|
|
1711
|
+
* block decision, and the "error reported only when nothing was handled"
|
|
1712
|
+
* rule that's true in both shapes (in batch mode, `handled` is always 0
|
|
1713
|
+
* on failure, so the rule degenerates to "always reported").
|
|
1714
|
+
*/
|
|
1715
|
+
_finalize(lease, handled, at, error, options) {
|
|
1716
|
+
if (!error) return { lease, handled, at };
|
|
1717
|
+
this._logger.error(error);
|
|
1718
|
+
const block2 = lease.retry >= options.maxRetries && options.blockOnError;
|
|
1719
|
+
if (block2)
|
|
1720
|
+
this._logger.error(
|
|
1721
|
+
`Blocking ${lease.stream} after ${lease.retry} retries.`
|
|
1722
|
+
);
|
|
1723
|
+
return {
|
|
1724
|
+
lease,
|
|
1725
|
+
handled,
|
|
1726
|
+
at,
|
|
1727
|
+
error: handled === 0 ? error.message : void 0,
|
|
1728
|
+
block: block2
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
/**
|
|
1732
|
+
* Handles leased reactions one event at a time.
|
|
1340
1733
|
*
|
|
1341
|
-
*
|
|
1342
|
-
*
|
|
1343
|
-
*
|
|
1344
|
-
*
|
|
1734
|
+
* Called by the main `drain` loop after fetching new events. Each handler
|
|
1735
|
+
* receives a scoped `IAct` proxy that auto-injects the triggering event
|
|
1736
|
+
* as `reactingTo` when `do()` is called without it, maintaining
|
|
1737
|
+
* correlation chains by default (#587). Handlers can still pass an
|
|
1738
|
+
* explicit `reactingTo` to override.
|
|
1345
1739
|
*
|
|
1346
1740
|
* @internal
|
|
1347
|
-
* @param lease The lease to handle
|
|
1348
|
-
* @param payloads The reactions to handle
|
|
1349
|
-
* @returns The lease with results
|
|
1350
1741
|
*/
|
|
1351
1742
|
async handle(lease, payloads) {
|
|
1352
1743
|
if (payloads.length === 0) return { lease, handled: 0, at: lease.at };
|
|
1353
1744
|
const stream = lease.stream;
|
|
1354
|
-
let at = payloads.at(0).event.id
|
|
1355
|
-
|
|
1356
|
-
|
|
1745
|
+
let at = payloads.at(0).event.id;
|
|
1746
|
+
let handled = 0;
|
|
1747
|
+
if (lease.retry > 0)
|
|
1748
|
+
this._logger.warn(`Retrying ${stream}@${at} (${lease.retry}).`);
|
|
1749
|
+
const doAction = this._bound_do;
|
|
1357
1750
|
const scopedApp = {
|
|
1358
1751
|
do: doAction,
|
|
1359
|
-
load: this.
|
|
1360
|
-
query: this.
|
|
1361
|
-
query_array: this.
|
|
1752
|
+
load: this._bound_load,
|
|
1753
|
+
query: this._bound_query,
|
|
1754
|
+
query_array: this._bound_query_array
|
|
1362
1755
|
};
|
|
1363
1756
|
for (const payload of payloads) {
|
|
1364
|
-
const { event, handler
|
|
1757
|
+
const { event, handler } = payload;
|
|
1365
1758
|
scopedApp.do = (action2, target, payload2, reactingTo, skipValidation) => doAction(
|
|
1366
1759
|
action2,
|
|
1367
1760
|
target,
|
|
@@ -1374,22 +1767,16 @@ var Act = class {
|
|
|
1374
1767
|
at = event.id;
|
|
1375
1768
|
handled++;
|
|
1376
1769
|
} catch (error) {
|
|
1377
|
-
this.
|
|
1378
|
-
const block2 = lease.retry >= options.maxRetries && options.blockOnError;
|
|
1379
|
-
block2 && this._logger.error(
|
|
1380
|
-
`Blocking ${stream} after ${lease.retry} retries.`
|
|
1381
|
-
);
|
|
1382
|
-
return {
|
|
1770
|
+
return this._finalize(
|
|
1383
1771
|
lease,
|
|
1384
1772
|
handled,
|
|
1385
1773
|
at,
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
};
|
|
1774
|
+
error,
|
|
1775
|
+
payload.options
|
|
1776
|
+
);
|
|
1390
1777
|
}
|
|
1391
1778
|
}
|
|
1392
|
-
return
|
|
1779
|
+
return this._finalize(lease, handled, at, void 0, payloads[0].options);
|
|
1393
1780
|
}
|
|
1394
1781
|
/**
|
|
1395
1782
|
* Handles a batch of events for a projection with a batch handler.
|
|
@@ -1399,33 +1786,26 @@ var Act = class {
|
|
|
1399
1786
|
* in a single call, enabling bulk DB operations.
|
|
1400
1787
|
*
|
|
1401
1788
|
* @internal
|
|
1402
|
-
* @param lease The lease to handle
|
|
1403
|
-
* @param payloads The reactions to handle
|
|
1404
|
-
* @param batchHandler The batch handler for this projection
|
|
1405
|
-
* @returns The lease with results
|
|
1406
1789
|
*/
|
|
1407
1790
|
async handleBatch(lease, payloads, batchHandler) {
|
|
1408
1791
|
const stream = lease.stream;
|
|
1409
1792
|
const events = payloads.map((p) => p.event);
|
|
1410
|
-
const
|
|
1411
|
-
lease.retry > 0
|
|
1412
|
-
|
|
1413
|
-
|
|
1793
|
+
const options = payloads[0].options;
|
|
1794
|
+
if (lease.retry > 0)
|
|
1795
|
+
this._logger.warn(
|
|
1796
|
+
`Retrying batch ${stream}@${events[0].id} (${lease.retry}).`
|
|
1797
|
+
);
|
|
1414
1798
|
try {
|
|
1415
1799
|
await batchHandler(events, stream);
|
|
1416
|
-
return
|
|
1417
|
-
} catch (error) {
|
|
1418
|
-
this._logger.error(error);
|
|
1419
|
-
const { options } = payloads[0];
|
|
1420
|
-
const block2 = lease.retry >= options.maxRetries && options.blockOnError;
|
|
1421
|
-
block2 && this._logger.error(`Blocking ${stream} after ${lease.retry} retries.`);
|
|
1422
|
-
return {
|
|
1800
|
+
return this._finalize(
|
|
1423
1801
|
lease,
|
|
1424
|
-
|
|
1425
|
-
at
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1802
|
+
events.length,
|
|
1803
|
+
events.at(-1).id,
|
|
1804
|
+
void 0,
|
|
1805
|
+
options
|
|
1806
|
+
);
|
|
1807
|
+
} catch (error) {
|
|
1808
|
+
return this._finalize(lease, 0, lease.at, error, options);
|
|
1429
1809
|
}
|
|
1430
1810
|
}
|
|
1431
1811
|
/**
|
|
@@ -1475,81 +1855,46 @@ var Act = class {
|
|
|
1475
1855
|
if (!this._needs_drain) {
|
|
1476
1856
|
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
1477
1857
|
}
|
|
1478
|
-
if (
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
fetched.forEach(({ stream, events }) => {
|
|
1500
|
-
const payloads = events.flatMap((event) => {
|
|
1501
|
-
const register = this.registry.events[event.name];
|
|
1502
|
-
if (!register) return [];
|
|
1503
|
-
return [...register.reactions.values()].filter((reaction) => {
|
|
1504
|
-
const resolved = typeof reaction.resolver === "function" ? reaction.resolver(event) : reaction.resolver;
|
|
1505
|
-
return resolved && resolved.target === stream;
|
|
1506
|
-
}).map((reaction) => ({ ...reaction, event }));
|
|
1507
|
-
});
|
|
1508
|
-
payloadsMap.set(stream, payloads);
|
|
1509
|
-
});
|
|
1510
|
-
const handled = await Promise.all(
|
|
1511
|
-
leased.map((lease) => {
|
|
1512
|
-
const streamFetch = fetched.find((f) => f.stream === lease.stream);
|
|
1513
|
-
const at = streamFetch?.events.at(-1)?.id || fetch_window_at;
|
|
1514
|
-
const payloads = payloadsMap.get(lease.stream);
|
|
1515
|
-
const batchHandler = this._batch_handlers.get(lease.stream);
|
|
1516
|
-
if (batchHandler && payloads.length > 0) {
|
|
1517
|
-
return this.handleBatch({ ...lease, at }, payloads, batchHandler);
|
|
1518
|
-
}
|
|
1519
|
-
return this.handle({ ...lease, at }, payloads);
|
|
1520
|
-
})
|
|
1521
|
-
);
|
|
1522
|
-
const [lagging_handled, leading_handled] = handled.reduce(
|
|
1523
|
-
([lagging_handled2, leading_handled2], { lease, handled: handled2 }) => [
|
|
1524
|
-
lagging_handled2 + (lease.lagging ? handled2 : 0),
|
|
1525
|
-
leading_handled2 + (lease.lagging ? 0 : handled2)
|
|
1526
|
-
],
|
|
1527
|
-
[0, 0]
|
|
1528
|
-
);
|
|
1529
|
-
const lagging_avg = lagging > 0 ? lagging_handled / lagging : 0;
|
|
1530
|
-
const leading_avg = leading > 0 ? leading_handled / leading : 0;
|
|
1531
|
-
const total = lagging_avg + leading_avg;
|
|
1532
|
-
this._drain_lag2lead_ratio = total > 0 ? Math.max(0.2, Math.min(0.8, lagging_avg / total)) : 0.5;
|
|
1533
|
-
const acked = await this._cd.ack(
|
|
1534
|
-
handled.filter(({ error }) => !error).map(({ at, lease }) => ({ ...lease, at }))
|
|
1535
|
-
);
|
|
1536
|
-
if (acked.length) this.emit("acked", acked);
|
|
1537
|
-
const blocked = await this._cd.block(
|
|
1538
|
-
handled.filter(({ block: block2 }) => block2).map(({ lease, error }) => ({ ...lease, error }))
|
|
1539
|
-
);
|
|
1540
|
-
if (blocked.length) this.emit("blocked", blocked);
|
|
1541
|
-
const result = { fetched, leased, acked, blocked };
|
|
1542
|
-
const hasErrors = handled.some(({ error }) => error);
|
|
1543
|
-
if (!acked.length && !blocked.length && !hasErrors)
|
|
1544
|
-
this._needs_drain = false;
|
|
1545
|
-
return result;
|
|
1546
|
-
} catch (error) {
|
|
1547
|
-
this._logger.error(error);
|
|
1548
|
-
} finally {
|
|
1549
|
-
this._drain_locked = false;
|
|
1858
|
+
if (this._drain_locked) {
|
|
1859
|
+
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
1860
|
+
}
|
|
1861
|
+
try {
|
|
1862
|
+
this._drain_locked = true;
|
|
1863
|
+
const lagging = Math.ceil(streamLimit * this._drain_lag2lead_ratio);
|
|
1864
|
+
const leading = streamLimit - lagging;
|
|
1865
|
+
const cycle = await runDrainCycle(
|
|
1866
|
+
this._cd,
|
|
1867
|
+
this.registry,
|
|
1868
|
+
this._batch_handlers,
|
|
1869
|
+
this._bound_handle,
|
|
1870
|
+
this._bound_handle_batch,
|
|
1871
|
+
lagging,
|
|
1872
|
+
leading,
|
|
1873
|
+
eventLimit,
|
|
1874
|
+
leaseMillis
|
|
1875
|
+
);
|
|
1876
|
+
if (!cycle) {
|
|
1877
|
+
this._needs_drain = false;
|
|
1878
|
+
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
1550
1879
|
}
|
|
1880
|
+
const { leased, fetched, handled, acked, blocked } = cycle;
|
|
1881
|
+
this._drain_lag2lead_ratio = computeLagLeadRatio(
|
|
1882
|
+
handled,
|
|
1883
|
+
lagging,
|
|
1884
|
+
leading
|
|
1885
|
+
);
|
|
1886
|
+
if (acked.length) this.emit("acked", acked);
|
|
1887
|
+
if (blocked.length) this.emit("blocked", blocked);
|
|
1888
|
+
const hasErrors = handled.some(({ error }) => error);
|
|
1889
|
+
if (!acked.length && !blocked.length && !hasErrors)
|
|
1890
|
+
this._needs_drain = false;
|
|
1891
|
+
return { fetched, leased, acked, blocked };
|
|
1892
|
+
} catch (error) {
|
|
1893
|
+
this._logger.error(error);
|
|
1894
|
+
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
1895
|
+
} finally {
|
|
1896
|
+
this._drain_locked = false;
|
|
1551
1897
|
}
|
|
1552
|
-
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
1553
1898
|
}
|
|
1554
1899
|
/**
|
|
1555
1900
|
* Discovers and registers new streams dynamically based on reaction resolvers.
|
|
@@ -1610,7 +1955,7 @@ var Act = class {
|
|
|
1610
1955
|
this._correlation_checkpoint = watermark;
|
|
1611
1956
|
if (this._reactive_events.size > 0) this._needs_drain = true;
|
|
1612
1957
|
for (const { stream } of this._static_targets) {
|
|
1613
|
-
this.
|
|
1958
|
+
this._subscribed_streams.add(stream);
|
|
1614
1959
|
}
|
|
1615
1960
|
}
|
|
1616
1961
|
async correlate(query = { after: -1, limit: 10 }) {
|
|
@@ -1628,7 +1973,7 @@ var Act = class {
|
|
|
1628
1973
|
for (const reaction of register.reactions.values()) {
|
|
1629
1974
|
if (typeof reaction.resolver !== "function") continue;
|
|
1630
1975
|
const resolved = reaction.resolver(event);
|
|
1631
|
-
if (resolved && !this.
|
|
1976
|
+
if (resolved && !this._subscribed_streams.has(resolved.target)) {
|
|
1632
1977
|
const entry = correlated.get(resolved.target) || {
|
|
1633
1978
|
source: resolved.source,
|
|
1634
1979
|
payloads: []
|
|
@@ -1654,7 +1999,7 @@ var Act = class {
|
|
|
1654
1999
|
this._correlation_checkpoint = last_id;
|
|
1655
2000
|
if (subscribed) {
|
|
1656
2001
|
for (const { stream } of streams) {
|
|
1657
|
-
this.
|
|
2002
|
+
this._subscribed_streams.add(stream);
|
|
1658
2003
|
}
|
|
1659
2004
|
}
|
|
1660
2005
|
return { subscribed, last_id };
|
|
@@ -1837,143 +2182,14 @@ var Act = class {
|
|
|
1837
2182
|
*/
|
|
1838
2183
|
async close(targets) {
|
|
1839
2184
|
if (!targets.length) return { truncated: /* @__PURE__ */ new Map(), skipped: [] };
|
|
1840
|
-
const targetMap = new Map(targets.map((t) => [t.stream, t]));
|
|
1841
|
-
const streams = [...targetMap.keys()];
|
|
1842
2185
|
await this.correlate({ limit: 1e3 });
|
|
1843
|
-
const
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
await store().query(
|
|
1850
|
-
(e) => {
|
|
1851
|
-
if (e.name === TOMBSTONE_EVENT) return;
|
|
1852
|
-
if (maxId === -1) {
|
|
1853
|
-
maxId = e.id;
|
|
1854
|
-
version = e.version;
|
|
1855
|
-
}
|
|
1856
|
-
if (e.name !== SNAP_EVENT && lastEventName === void 0) {
|
|
1857
|
-
lastEventName = e.name;
|
|
1858
|
-
}
|
|
1859
|
-
},
|
|
1860
|
-
// limit: 2 covers the typical snapshot-at-head case (snapshot is
|
|
1861
|
-
// always preceded by the domain event it captured). Streams with
|
|
1862
|
-
// unusual layouts fall back to no-seed via the lookup miss path.
|
|
1863
|
-
{ stream: s, stream_exact: true, backward: true, limit: 2 }
|
|
1864
|
-
);
|
|
1865
|
-
if (maxId >= 0) streamInfo.set(s, { maxId, version, lastEventName });
|
|
1866
|
-
})
|
|
1867
|
-
);
|
|
1868
|
-
const skipped = [];
|
|
1869
|
-
let safe;
|
|
1870
|
-
if (this._reactive_events.size === 0) {
|
|
1871
|
-
safe = [...streamInfo.keys()];
|
|
1872
|
-
} else {
|
|
1873
|
-
const pendingSet = /* @__PURE__ */ new Set();
|
|
1874
|
-
await store().query_streams((position) => {
|
|
1875
|
-
const sourceRe = position.source ? RegExp(position.source) : void 0;
|
|
1876
|
-
for (const [stream, info] of streamInfo) {
|
|
1877
|
-
if ((!sourceRe || sourceRe.test(stream)) && position.at < info.maxId) {
|
|
1878
|
-
pendingSet.add(stream);
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
1881
|
-
});
|
|
1882
|
-
safe = [];
|
|
1883
|
-
for (const [stream] of streamInfo) {
|
|
1884
|
-
if (pendingSet.has(stream)) {
|
|
1885
|
-
skipped.push(stream);
|
|
1886
|
-
} else {
|
|
1887
|
-
safe.push(stream);
|
|
1888
|
-
}
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1891
|
-
if (!safe.length) {
|
|
1892
|
-
const result2 = { truncated: /* @__PURE__ */ new Map(), skipped };
|
|
1893
|
-
this.emit("closed", result2);
|
|
1894
|
-
return result2;
|
|
1895
|
-
}
|
|
1896
|
-
const correlation = randomUUID2();
|
|
1897
|
-
const guarded = [];
|
|
1898
|
-
const guardEvents = /* @__PURE__ */ new Map();
|
|
1899
|
-
await Promise.all(
|
|
1900
|
-
safe.map(async (stream) => {
|
|
1901
|
-
try {
|
|
1902
|
-
const info = streamInfo.get(stream);
|
|
1903
|
-
const [committed] = await store().commit(
|
|
1904
|
-
stream,
|
|
1905
|
-
[{ name: TOMBSTONE_EVENT, data: {} }],
|
|
1906
|
-
{ correlation, causation: {} },
|
|
1907
|
-
info.version
|
|
1908
|
-
);
|
|
1909
|
-
guarded.push(stream);
|
|
1910
|
-
guardEvents.set(stream, { id: committed.id, stream });
|
|
1911
|
-
} catch {
|
|
1912
|
-
skipped.push(stream);
|
|
1913
|
-
}
|
|
1914
|
-
})
|
|
1915
|
-
);
|
|
1916
|
-
if (!guarded.length) {
|
|
1917
|
-
const result2 = { truncated: /* @__PURE__ */ new Map(), skipped };
|
|
1918
|
-
this.emit("closed", result2);
|
|
1919
|
-
return result2;
|
|
1920
|
-
}
|
|
1921
|
-
const seedStates = /* @__PURE__ */ new Map();
|
|
1922
|
-
await Promise.all(
|
|
1923
|
-
guarded.filter((s) => targetMap.get(s)?.restart).map(async (stream) => {
|
|
1924
|
-
const lastEventName = streamInfo.get(stream)?.lastEventName;
|
|
1925
|
-
const ownerState = lastEventName ? this._event_to_state.get(lastEventName) : void 0;
|
|
1926
|
-
if (!ownerState) {
|
|
1927
|
-
this._logger.error(
|
|
1928
|
-
`Cannot seed restart for "${stream}": no registered state owns event "${lastEventName ?? "<none>"}". Stream will be tombstoned instead.`
|
|
1929
|
-
);
|
|
1930
|
-
return;
|
|
1931
|
-
}
|
|
1932
|
-
const snap2 = await this._es.load(ownerState, stream);
|
|
1933
|
-
seedStates.set(stream, snap2.state);
|
|
1934
|
-
})
|
|
1935
|
-
);
|
|
1936
|
-
for (const stream of guarded) {
|
|
1937
|
-
const archiveFn = targetMap.get(stream)?.archive;
|
|
1938
|
-
if (archiveFn) await archiveFn();
|
|
1939
|
-
}
|
|
1940
|
-
const truncTargets = guarded.map((stream) => {
|
|
1941
|
-
const snapshot = seedStates.get(stream);
|
|
1942
|
-
const guard = guardEvents.get(stream);
|
|
1943
|
-
return {
|
|
1944
|
-
stream,
|
|
1945
|
-
snapshot,
|
|
1946
|
-
meta: {
|
|
1947
|
-
correlation,
|
|
1948
|
-
causation: {
|
|
1949
|
-
event: {
|
|
1950
|
-
id: guard.id,
|
|
1951
|
-
name: TOMBSTONE_EVENT,
|
|
1952
|
-
stream: guard.stream
|
|
1953
|
-
}
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
};
|
|
2186
|
+
const result = await runCloseCycle(targets, {
|
|
2187
|
+
reactiveEventsSize: this._reactive_events.size,
|
|
2188
|
+
eventToState: this._event_to_state,
|
|
2189
|
+
load: this._es.load,
|
|
2190
|
+
tombstone: this._es.tombstone,
|
|
2191
|
+
logger: this._logger
|
|
1957
2192
|
});
|
|
1958
|
-
const truncated = await store().truncate(truncTargets);
|
|
1959
|
-
await Promise.all(
|
|
1960
|
-
guarded.map(async (stream) => {
|
|
1961
|
-
const entry = truncated.get(stream);
|
|
1962
|
-
const state2 = seedStates.get(stream);
|
|
1963
|
-
if (state2 && entry) {
|
|
1964
|
-
await cache().set(stream, {
|
|
1965
|
-
state: state2,
|
|
1966
|
-
version: entry.committed.version,
|
|
1967
|
-
event_id: entry.committed.id,
|
|
1968
|
-
patches: 0,
|
|
1969
|
-
snaps: 1
|
|
1970
|
-
});
|
|
1971
|
-
} else {
|
|
1972
|
-
await cache().invalidate(stream);
|
|
1973
|
-
}
|
|
1974
|
-
})
|
|
1975
|
-
);
|
|
1976
|
-
const result = { truncated, skipped };
|
|
1977
2193
|
this.emit("closed", result);
|
|
1978
2194
|
return result;
|
|
1979
2195
|
}
|
|
@@ -2042,7 +2258,7 @@ var Act = class {
|
|
|
2042
2258
|
}
|
|
2043
2259
|
};
|
|
2044
2260
|
|
|
2045
|
-
// src/act-builder.ts
|
|
2261
|
+
// src/builders/act-builder.ts
|
|
2046
2262
|
function registerBatchHandler(proj, batchHandlers) {
|
|
2047
2263
|
if (!proj.batchHandler || !proj.target) return;
|
|
2048
2264
|
const existing = batchHandlers.get(proj.target);
|
|
@@ -2051,56 +2267,33 @@ function registerBatchHandler(proj, batchHandlers) {
|
|
|
2051
2267
|
}
|
|
2052
2268
|
batchHandlers.set(proj.target, proj.batchHandler);
|
|
2053
2269
|
}
|
|
2054
|
-
function act(
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
},
|
|
2270
|
+
function act() {
|
|
2271
|
+
const states = /* @__PURE__ */ new Map();
|
|
2272
|
+
const registry = {
|
|
2273
|
+
actions: {},
|
|
2274
|
+
events: {}
|
|
2275
|
+
};
|
|
2276
|
+
const pendingProjections = [];
|
|
2277
|
+
const batchHandlers = /* @__PURE__ */ new Map();
|
|
2058
2278
|
const builder = {
|
|
2059
2279
|
withState: (state2) => {
|
|
2060
2280
|
registerState(state2, states, registry.actions, registry.events);
|
|
2061
|
-
return
|
|
2062
|
-
states,
|
|
2063
|
-
registry,
|
|
2064
|
-
pendingProjections,
|
|
2065
|
-
batchHandlers
|
|
2066
|
-
);
|
|
2281
|
+
return builder;
|
|
2067
2282
|
},
|
|
2068
2283
|
withSlice: (input) => {
|
|
2069
2284
|
for (const s of input.states.values()) {
|
|
2070
2285
|
registerState(s, states, registry.actions, registry.events);
|
|
2071
2286
|
}
|
|
2072
|
-
|
|
2073
|
-
const sliceRegister = input.events[eventName];
|
|
2074
|
-
for (const [name, reaction] of sliceRegister.reactions) {
|
|
2075
|
-
registry.events[eventName].reactions.set(name, reaction);
|
|
2076
|
-
}
|
|
2077
|
-
}
|
|
2287
|
+
mergeEventRegister(registry.events, input.events);
|
|
2078
2288
|
pendingProjections.push(...input.projections);
|
|
2079
|
-
return
|
|
2080
|
-
states,
|
|
2081
|
-
registry,
|
|
2082
|
-
pendingProjections,
|
|
2083
|
-
batchHandlers
|
|
2084
|
-
);
|
|
2289
|
+
return builder;
|
|
2085
2290
|
},
|
|
2086
2291
|
withProjection: (proj) => {
|
|
2087
2292
|
mergeProjection(proj, registry.events);
|
|
2088
2293
|
registerBatchHandler(proj, batchHandlers);
|
|
2089
|
-
return
|
|
2090
|
-
states,
|
|
2091
|
-
registry,
|
|
2092
|
-
pendingProjections,
|
|
2093
|
-
batchHandlers
|
|
2094
|
-
);
|
|
2095
|
-
},
|
|
2096
|
-
withActor: () => {
|
|
2097
|
-
return act(
|
|
2098
|
-
states,
|
|
2099
|
-
registry,
|
|
2100
|
-
pendingProjections,
|
|
2101
|
-
batchHandlers
|
|
2102
|
-
);
|
|
2294
|
+
return builder;
|
|
2103
2295
|
},
|
|
2296
|
+
withActor: () => builder,
|
|
2104
2297
|
on: (event) => ({
|
|
2105
2298
|
do: (handler, options) => {
|
|
2106
2299
|
const reaction = {
|
|
@@ -2116,19 +2309,15 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
|
2116
2309
|
`Reaction handler for "${String(event)}" must be a named function`
|
|
2117
2310
|
);
|
|
2118
2311
|
registry.events[event].reactions.set(handler.name, reaction);
|
|
2119
|
-
return {
|
|
2120
|
-
...builder,
|
|
2312
|
+
return Object.assign(builder, {
|
|
2121
2313
|
to(resolver) {
|
|
2122
|
-
|
|
2123
|
-
...reaction,
|
|
2124
|
-
resolver: typeof resolver === "string" ? { target: resolver } : resolver
|
|
2125
|
-
});
|
|
2314
|
+
reaction.resolver = typeof resolver === "string" ? { target: resolver } : resolver;
|
|
2126
2315
|
return builder;
|
|
2127
2316
|
}
|
|
2128
|
-
};
|
|
2317
|
+
});
|
|
2129
2318
|
}
|
|
2130
2319
|
}),
|
|
2131
|
-
build: () => {
|
|
2320
|
+
build: (options) => {
|
|
2132
2321
|
for (const proj of pendingProjections) {
|
|
2133
2322
|
mergeProjection(proj, registry.events);
|
|
2134
2323
|
registerBatchHandler(proj, batchHandlers);
|
|
@@ -2136,7 +2325,8 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
|
2136
2325
|
return new Act(
|
|
2137
2326
|
registry,
|
|
2138
2327
|
states,
|
|
2139
|
-
batchHandlers
|
|
2328
|
+
batchHandlers,
|
|
2329
|
+
options
|
|
2140
2330
|
);
|
|
2141
2331
|
},
|
|
2142
2332
|
events: registry.events
|
|
@@ -2144,8 +2334,9 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
|
2144
2334
|
return builder;
|
|
2145
2335
|
}
|
|
2146
2336
|
|
|
2147
|
-
// src/projection-builder.ts
|
|
2148
|
-
function _projection(target
|
|
2337
|
+
// src/builders/projection-builder.ts
|
|
2338
|
+
function _projection(target) {
|
|
2339
|
+
const events = {};
|
|
2149
2340
|
const defaultResolver = typeof target === "string" ? { target } : void 0;
|
|
2150
2341
|
const base = {
|
|
2151
2342
|
on: (entry) => {
|
|
@@ -2175,17 +2366,13 @@ function _projection(target, events) {
|
|
|
2175
2366
|
`Projection handler for "${event}" must be a named function`
|
|
2176
2367
|
);
|
|
2177
2368
|
register.reactions.set(handler.name, reaction);
|
|
2178
|
-
const
|
|
2179
|
-
return {
|
|
2180
|
-
...nextBuilder,
|
|
2369
|
+
const widened = base;
|
|
2370
|
+
return Object.assign(widened, {
|
|
2181
2371
|
to(resolver) {
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
resolver: typeof resolver === "string" ? { target: resolver } : resolver
|
|
2185
|
-
});
|
|
2186
|
-
return nextBuilder;
|
|
2372
|
+
reaction.resolver = typeof resolver === "string" ? { target: resolver } : resolver;
|
|
2373
|
+
return widened;
|
|
2187
2374
|
}
|
|
2188
|
-
};
|
|
2375
|
+
});
|
|
2189
2376
|
}
|
|
2190
2377
|
};
|
|
2191
2378
|
},
|
|
@@ -2197,8 +2384,7 @@ function _projection(target, events) {
|
|
|
2197
2384
|
events
|
|
2198
2385
|
};
|
|
2199
2386
|
if (typeof target === "string") {
|
|
2200
|
-
return {
|
|
2201
|
-
...base,
|
|
2387
|
+
return Object.assign(base, {
|
|
2202
2388
|
batch: (handler) => ({
|
|
2203
2389
|
build: () => ({
|
|
2204
2390
|
_tag: "Projection",
|
|
@@ -2207,34 +2393,28 @@ function _projection(target, events) {
|
|
|
2207
2393
|
batchHandler: handler
|
|
2208
2394
|
})
|
|
2209
2395
|
})
|
|
2210
|
-
};
|
|
2396
|
+
});
|
|
2211
2397
|
}
|
|
2212
2398
|
return base;
|
|
2213
2399
|
}
|
|
2214
|
-
function projection(target
|
|
2215
|
-
return _projection(target
|
|
2400
|
+
function projection(target) {
|
|
2401
|
+
return _projection(target);
|
|
2216
2402
|
}
|
|
2217
2403
|
|
|
2218
|
-
// src/slice-builder.ts
|
|
2219
|
-
function slice(
|
|
2404
|
+
// src/builders/slice-builder.ts
|
|
2405
|
+
function slice() {
|
|
2406
|
+
const states = /* @__PURE__ */ new Map();
|
|
2407
|
+
const actions = {};
|
|
2408
|
+
const events = {};
|
|
2409
|
+
const projections = [];
|
|
2220
2410
|
const builder = {
|
|
2221
2411
|
withState: (state2) => {
|
|
2222
2412
|
registerState(state2, states, actions, events);
|
|
2223
|
-
return
|
|
2224
|
-
states,
|
|
2225
|
-
actions,
|
|
2226
|
-
events,
|
|
2227
|
-
projections
|
|
2228
|
-
);
|
|
2413
|
+
return builder;
|
|
2229
2414
|
},
|
|
2230
2415
|
withProjection: (proj) => {
|
|
2231
2416
|
projections.push(proj);
|
|
2232
|
-
return
|
|
2233
|
-
states,
|
|
2234
|
-
actions,
|
|
2235
|
-
events,
|
|
2236
|
-
projections
|
|
2237
|
-
);
|
|
2417
|
+
return builder;
|
|
2238
2418
|
},
|
|
2239
2419
|
on: (event) => ({
|
|
2240
2420
|
do: (handler, options) => {
|
|
@@ -2251,16 +2431,12 @@ function slice(states = /* @__PURE__ */ new Map(), actions = {}, events = {}, pr
|
|
|
2251
2431
|
`Reaction handler for "${String(event)}" must be a named function`
|
|
2252
2432
|
);
|
|
2253
2433
|
events[event].reactions.set(handler.name, reaction);
|
|
2254
|
-
return {
|
|
2255
|
-
...builder,
|
|
2434
|
+
return Object.assign(builder, {
|
|
2256
2435
|
to(resolver) {
|
|
2257
|
-
|
|
2258
|
-
...reaction,
|
|
2259
|
-
resolver: typeof resolver === "string" ? { target: resolver } : resolver
|
|
2260
|
-
});
|
|
2436
|
+
reaction.resolver = typeof resolver === "string" ? { target: resolver } : resolver;
|
|
2261
2437
|
return builder;
|
|
2262
2438
|
}
|
|
2263
|
-
};
|
|
2439
|
+
});
|
|
2264
2440
|
}
|
|
2265
2441
|
}),
|
|
2266
2442
|
build: () => ({
|
|
@@ -2274,7 +2450,7 @@ function slice(states = /* @__PURE__ */ new Map(), actions = {}, events = {}, pr
|
|
|
2274
2450
|
return builder;
|
|
2275
2451
|
}
|
|
2276
2452
|
|
|
2277
|
-
// src/state-builder.ts
|
|
2453
|
+
// src/builders/state-builder.ts
|
|
2278
2454
|
function state(entry) {
|
|
2279
2455
|
const keys = Object.keys(entry);
|
|
2280
2456
|
if (keys.length !== 1) throw new Error("state() requires exactly one key");
|
|
@@ -2292,7 +2468,7 @@ function state(entry) {
|
|
|
2292
2468
|
return [k, fn];
|
|
2293
2469
|
})
|
|
2294
2470
|
);
|
|
2295
|
-
const
|
|
2471
|
+
const internal = {
|
|
2296
2472
|
events,
|
|
2297
2473
|
actions: {},
|
|
2298
2474
|
state: stateSchema,
|
|
@@ -2300,18 +2476,12 @@ function state(entry) {
|
|
|
2300
2476
|
init,
|
|
2301
2477
|
patch: defaultPatch,
|
|
2302
2478
|
on: {}
|
|
2303
|
-
}
|
|
2479
|
+
};
|
|
2480
|
+
const builder = action_builder(internal);
|
|
2304
2481
|
return Object.assign(builder, {
|
|
2305
2482
|
patch(customPatch) {
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
actions: {},
|
|
2309
|
-
state: stateSchema,
|
|
2310
|
-
name,
|
|
2311
|
-
init,
|
|
2312
|
-
patch: { ...defaultPatch, ...customPatch },
|
|
2313
|
-
on: {}
|
|
2314
|
-
});
|
|
2483
|
+
Object.assign(internal.patch, customPatch);
|
|
2484
|
+
return builder;
|
|
2315
2485
|
}
|
|
2316
2486
|
});
|
|
2317
2487
|
}
|
|
@@ -2320,50 +2490,43 @@ function state(entry) {
|
|
|
2320
2490
|
};
|
|
2321
2491
|
}
|
|
2322
2492
|
function action_builder(state2) {
|
|
2323
|
-
|
|
2493
|
+
const internal = state2;
|
|
2494
|
+
const builder = {
|
|
2324
2495
|
on(entry) {
|
|
2325
2496
|
const keys = Object.keys(entry);
|
|
2326
2497
|
if (keys.length !== 1) throw new Error(".on() requires exactly one key");
|
|
2327
2498
|
const action2 = keys[0];
|
|
2328
2499
|
const schema = entry[action2];
|
|
2329
|
-
if (action2 in
|
|
2500
|
+
if (action2 in internal.actions)
|
|
2330
2501
|
throw new Error(`Duplicate action "${action2}"`);
|
|
2331
|
-
|
|
2332
|
-
...state2.actions,
|
|
2333
|
-
[action2]: schema
|
|
2334
|
-
};
|
|
2335
|
-
const on = { ...state2.on };
|
|
2336
|
-
const _given = { ...state2.given };
|
|
2502
|
+
internal.actions[action2] = schema;
|
|
2337
2503
|
function given(rules) {
|
|
2338
|
-
|
|
2504
|
+
(internal.given ??= {})[action2] = rules;
|
|
2339
2505
|
return { emit };
|
|
2340
2506
|
}
|
|
2341
2507
|
function emit(handler) {
|
|
2342
2508
|
if (typeof handler === "string") {
|
|
2343
2509
|
const eventName = handler;
|
|
2344
|
-
on[action2] = (
|
|
2510
|
+
internal.on[action2] = (payload) => [
|
|
2511
|
+
eventName,
|
|
2512
|
+
payload
|
|
2513
|
+
];
|
|
2345
2514
|
} else {
|
|
2346
|
-
on[action2] = handler;
|
|
2515
|
+
internal.on[action2] = handler;
|
|
2347
2516
|
}
|
|
2348
|
-
return
|
|
2349
|
-
...state2,
|
|
2350
|
-
actions,
|
|
2351
|
-
on,
|
|
2352
|
-
given: _given
|
|
2353
|
-
});
|
|
2517
|
+
return builder;
|
|
2354
2518
|
}
|
|
2355
2519
|
return { given, emit };
|
|
2356
2520
|
},
|
|
2357
2521
|
snap(snap2) {
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
snap: snap2
|
|
2361
|
-
});
|
|
2522
|
+
internal.snap = snap2;
|
|
2523
|
+
return builder;
|
|
2362
2524
|
},
|
|
2363
2525
|
build() {
|
|
2364
|
-
return
|
|
2526
|
+
return internal;
|
|
2365
2527
|
}
|
|
2366
2528
|
};
|
|
2529
|
+
return builder;
|
|
2367
2530
|
}
|
|
2368
2531
|
export {
|
|
2369
2532
|
Act,
|
|
@@ -2372,6 +2535,7 @@ export {
|
|
|
2372
2535
|
CommittedMetaSchema,
|
|
2373
2536
|
ConcurrencyError,
|
|
2374
2537
|
ConsoleLogger,
|
|
2538
|
+
DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
2375
2539
|
Environments,
|
|
2376
2540
|
Errors,
|
|
2377
2541
|
EventMetaSchema,
|