@rotorsoft/act 0.25.2 → 0.26.1
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/README.md +2 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/act-builder.d.ts +2 -2
- package/dist/@types/act-builder.d.ts.map +1 -1
- package/dist/@types/act.d.ts +18 -2
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/merge.d.ts.map +1 -1
- package/dist/@types/projection-builder.d.ts +38 -53
- package/dist/@types/projection-builder.d.ts.map +1 -1
- package/dist/@types/slice-builder.d.ts.map +1 -1
- package/dist/@types/types/reaction.d.ts +42 -0
- package/dist/@types/types/reaction.d.ts.map +1 -1
- package/dist/index.cjs +115 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +115 -37
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -309,7 +309,7 @@ var InMemoryStream = class {
|
|
|
309
309
|
_error = "";
|
|
310
310
|
_leased_by = void 0;
|
|
311
311
|
_leased_until = void 0;
|
|
312
|
-
get
|
|
312
|
+
get is_available() {
|
|
313
313
|
return !this._blocked && (!this._leased_until || this._leased_until <= /* @__PURE__ */ new Date());
|
|
314
314
|
}
|
|
315
315
|
get at() {
|
|
@@ -503,7 +503,7 @@ var InMemoryStore = class {
|
|
|
503
503
|
async claim(lagging, leading, by, millis) {
|
|
504
504
|
await sleep();
|
|
505
505
|
const available = [...this._streams.values()].filter(
|
|
506
|
-
(s) => s.
|
|
506
|
+
(s) => s.is_available && (s.at < 0 || this._events.some(
|
|
507
507
|
(e) => e.id > s.at && e.name !== SNAP_EVENT && (!s.source || RegExp(s.source).test(e.stream))
|
|
508
508
|
))
|
|
509
509
|
);
|
|
@@ -747,7 +747,7 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
747
747
|
if (!stream) throw new Error("Missing target stream");
|
|
748
748
|
payload = skipValidation ? payload : validate(action2, payload, me.actions[action2]);
|
|
749
749
|
const snapshot = await load(me, stream);
|
|
750
|
-
const expected = expectedVersion
|
|
750
|
+
const expected = expectedVersion ?? snapshot.event?.version;
|
|
751
751
|
logger2.trace(
|
|
752
752
|
payload,
|
|
753
753
|
`\u{1F535} ${stream}.${action2}${typeof expected === "number" ? `.${expected}` : ""}`
|
|
@@ -806,7 +806,7 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
806
806
|
);
|
|
807
807
|
} catch (error) {
|
|
808
808
|
if (error.name === "ERR_CONCURRENCY") {
|
|
809
|
-
|
|
809
|
+
await cache().invalidate(stream);
|
|
810
810
|
}
|
|
811
811
|
throw error;
|
|
812
812
|
}
|
|
@@ -834,9 +834,10 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
834
834
|
var logger3 = log();
|
|
835
835
|
var tracer = build_tracer(config().logLevel);
|
|
836
836
|
var Act = class {
|
|
837
|
-
constructor(registry, _states = /* @__PURE__ */ new Map()) {
|
|
837
|
+
constructor(registry, _states = /* @__PURE__ */ new Map(), batchHandlers = /* @__PURE__ */ new Map()) {
|
|
838
838
|
this.registry = registry;
|
|
839
839
|
this._states = _states;
|
|
840
|
+
this._batch_handlers = batchHandlers;
|
|
840
841
|
const statics = [];
|
|
841
842
|
for (const [name, register] of Object.entries(this.registry.events)) {
|
|
842
843
|
if (register.reactions.size > 0) {
|
|
@@ -894,6 +895,8 @@ var Act = class {
|
|
|
894
895
|
*/
|
|
895
896
|
/** Static resolver targets collected at build time */
|
|
896
897
|
_static_targets;
|
|
898
|
+
/** Batch handlers for static-target projections (target → handler) */
|
|
899
|
+
_batch_handlers;
|
|
897
900
|
/**
|
|
898
901
|
* Executes an action on a state instance, committing resulting events.
|
|
899
902
|
*
|
|
@@ -1135,6 +1138,41 @@ var Act = class {
|
|
|
1135
1138
|
}
|
|
1136
1139
|
return { lease, handled, at };
|
|
1137
1140
|
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Handles a batch of events for a projection with a batch handler.
|
|
1143
|
+
*
|
|
1144
|
+
* Called by `drain()` when a leased stream is a static-target projection
|
|
1145
|
+
* with a registered batch handler. All events are passed to the handler
|
|
1146
|
+
* in a single call, enabling bulk DB operations.
|
|
1147
|
+
*
|
|
1148
|
+
* @internal
|
|
1149
|
+
* @param lease The lease to handle
|
|
1150
|
+
* @param payloads The reactions to handle
|
|
1151
|
+
* @param batchHandler The batch handler for this projection
|
|
1152
|
+
* @returns The lease with results
|
|
1153
|
+
*/
|
|
1154
|
+
async handleBatch(lease, payloads, batchHandler) {
|
|
1155
|
+
const stream = lease.stream;
|
|
1156
|
+
const events = payloads.map((p) => p.event);
|
|
1157
|
+
const at = events.at(-1).id;
|
|
1158
|
+
lease.retry > 0 && logger3.warn(`Retrying batch ${stream}@${events[0].id} (${lease.retry}).`);
|
|
1159
|
+
try {
|
|
1160
|
+
await batchHandler(events, stream);
|
|
1161
|
+
return { lease, handled: events.length, at };
|
|
1162
|
+
} catch (error) {
|
|
1163
|
+
logger3.error(error);
|
|
1164
|
+
const { options } = payloads[0];
|
|
1165
|
+
const block = lease.retry >= options.maxRetries && options.blockOnError;
|
|
1166
|
+
block && logger3.error(`Blocking ${stream} after ${lease.retry} retries.`);
|
|
1167
|
+
return {
|
|
1168
|
+
lease,
|
|
1169
|
+
handled: 0,
|
|
1170
|
+
at: lease.at,
|
|
1171
|
+
error: error.message,
|
|
1172
|
+
block
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1138
1176
|
/**
|
|
1139
1177
|
* Processes pending reactions by draining uncommitted events from the event store.
|
|
1140
1178
|
*
|
|
@@ -1229,10 +1267,12 @@ var Act = class {
|
|
|
1229
1267
|
leased.map((lease) => {
|
|
1230
1268
|
const streamFetch = fetched.find((f) => f.stream === lease.stream);
|
|
1231
1269
|
const at = streamFetch?.events.at(-1)?.id || fetch_window_at;
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1270
|
+
const payloads = payloadsMap.get(lease.stream);
|
|
1271
|
+
const batchHandler = this._batch_handlers.get(lease.stream);
|
|
1272
|
+
if (batchHandler && payloads.length > 0) {
|
|
1273
|
+
return this.handleBatch({ ...lease, at }, payloads, batchHandler);
|
|
1274
|
+
}
|
|
1275
|
+
return this.handle({ ...lease, at }, payloads);
|
|
1236
1276
|
})
|
|
1237
1277
|
);
|
|
1238
1278
|
const [lagging_handled, leading_handled] = handled.reduce(
|
|
@@ -1617,7 +1657,11 @@ function registerState(state2, states, actions, events) {
|
|
|
1617
1657
|
patch: mergedPatch,
|
|
1618
1658
|
on: { ...existing.on, ...state2.on },
|
|
1619
1659
|
given: { ...existing.given, ...state2.given },
|
|
1620
|
-
snap: state2.snap
|
|
1660
|
+
snap: state2.snap && existing.snap && state2.snap !== existing.snap ? (() => {
|
|
1661
|
+
throw new Error(
|
|
1662
|
+
`Duplicate snap strategy for state "${state2.name}"`
|
|
1663
|
+
);
|
|
1664
|
+
})() : state2.snap || existing.snap
|
|
1621
1665
|
};
|
|
1622
1666
|
states.set(state2.name, merged);
|
|
1623
1667
|
for (const name of Object.keys(merged.actions)) {
|
|
@@ -1673,14 +1717,15 @@ var _void_ = () => void 0;
|
|
|
1673
1717
|
function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
1674
1718
|
actions: {},
|
|
1675
1719
|
events: {}
|
|
1676
|
-
}, pendingProjections = []) {
|
|
1720
|
+
}, pendingProjections = [], batchHandlers = /* @__PURE__ */ new Map()) {
|
|
1677
1721
|
const builder = {
|
|
1678
1722
|
withState: (state2) => {
|
|
1679
1723
|
registerState(state2, states, registry.actions, registry.events);
|
|
1680
1724
|
return act(
|
|
1681
1725
|
states,
|
|
1682
1726
|
registry,
|
|
1683
|
-
pendingProjections
|
|
1727
|
+
pendingProjections,
|
|
1728
|
+
batchHandlers
|
|
1684
1729
|
);
|
|
1685
1730
|
},
|
|
1686
1731
|
withSlice: (input) => {
|
|
@@ -1697,22 +1742,28 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
|
1697
1742
|
return act(
|
|
1698
1743
|
states,
|
|
1699
1744
|
registry,
|
|
1700
|
-
pendingProjections
|
|
1745
|
+
pendingProjections,
|
|
1746
|
+
batchHandlers
|
|
1701
1747
|
);
|
|
1702
1748
|
},
|
|
1703
1749
|
withProjection: (proj) => {
|
|
1704
1750
|
mergeProjection(proj, registry.events);
|
|
1751
|
+
if (proj.batchHandler && proj.target) {
|
|
1752
|
+
batchHandlers.set(proj.target, proj.batchHandler);
|
|
1753
|
+
}
|
|
1705
1754
|
return act(
|
|
1706
1755
|
states,
|
|
1707
1756
|
registry,
|
|
1708
|
-
pendingProjections
|
|
1757
|
+
pendingProjections,
|
|
1758
|
+
batchHandlers
|
|
1709
1759
|
);
|
|
1710
1760
|
},
|
|
1711
1761
|
withActor: () => {
|
|
1712
1762
|
return act(
|
|
1713
1763
|
states,
|
|
1714
1764
|
registry,
|
|
1715
|
-
pendingProjections
|
|
1765
|
+
pendingProjections,
|
|
1766
|
+
batchHandlers
|
|
1716
1767
|
);
|
|
1717
1768
|
},
|
|
1718
1769
|
on: (event) => ({
|
|
@@ -1725,19 +1776,22 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
|
1725
1776
|
maxRetries: options?.maxRetries ?? 3
|
|
1726
1777
|
}
|
|
1727
1778
|
};
|
|
1728
|
-
|
|
1729
|
-
|
|
1779
|
+
if (!handler.name)
|
|
1780
|
+
throw new Error(
|
|
1781
|
+
`Reaction handler for "${String(event)}" must be a named function`
|
|
1782
|
+
);
|
|
1783
|
+
registry.events[event].reactions.set(handler.name, reaction);
|
|
1730
1784
|
return {
|
|
1731
1785
|
...builder,
|
|
1732
1786
|
to(resolver) {
|
|
1733
|
-
registry.events[event].reactions.set(name, {
|
|
1787
|
+
registry.events[event].reactions.set(handler.name, {
|
|
1734
1788
|
...reaction,
|
|
1735
1789
|
resolver: typeof resolver === "string" ? { target: resolver } : resolver
|
|
1736
1790
|
});
|
|
1737
1791
|
return builder;
|
|
1738
1792
|
},
|
|
1739
1793
|
void() {
|
|
1740
|
-
registry.events[event].reactions.set(name, {
|
|
1794
|
+
registry.events[event].reactions.set(handler.name, {
|
|
1741
1795
|
...reaction,
|
|
1742
1796
|
resolver: _void_
|
|
1743
1797
|
});
|
|
@@ -1749,10 +1803,14 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
|
1749
1803
|
build: () => {
|
|
1750
1804
|
for (const proj of pendingProjections) {
|
|
1751
1805
|
mergeProjection(proj, registry.events);
|
|
1806
|
+
if (proj.batchHandler && proj.target) {
|
|
1807
|
+
batchHandlers.set(proj.target, proj.batchHandler);
|
|
1808
|
+
}
|
|
1752
1809
|
}
|
|
1753
1810
|
return new Act(
|
|
1754
1811
|
registry,
|
|
1755
|
-
states
|
|
1812
|
+
states,
|
|
1813
|
+
batchHandlers
|
|
1756
1814
|
);
|
|
1757
1815
|
},
|
|
1758
1816
|
events: registry.events
|
|
@@ -1761,9 +1819,9 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
|
1761
1819
|
}
|
|
1762
1820
|
|
|
1763
1821
|
// src/projection-builder.ts
|
|
1764
|
-
function
|
|
1765
|
-
const defaultResolver = target ? { target } : void 0;
|
|
1766
|
-
const
|
|
1822
|
+
function _projection(target, events) {
|
|
1823
|
+
const defaultResolver = typeof target === "string" ? { target } : void 0;
|
|
1824
|
+
const base = {
|
|
1767
1825
|
on: (entry) => {
|
|
1768
1826
|
const keys = Object.keys(entry);
|
|
1769
1827
|
if (keys.length !== 1) throw new Error(".on() requires exactly one key");
|
|
@@ -1786,23 +1844,23 @@ function projection(target, events = {}) {
|
|
|
1786
1844
|
}
|
|
1787
1845
|
};
|
|
1788
1846
|
const register = events[event];
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
);
|
|
1847
|
+
if (!handler.name)
|
|
1848
|
+
throw new Error(
|
|
1849
|
+
`Projection handler for "${event}" must be a named function`
|
|
1850
|
+
);
|
|
1851
|
+
register.reactions.set(handler.name, reaction);
|
|
1852
|
+
const nextBuilder = _projection(target, events);
|
|
1795
1853
|
return {
|
|
1796
1854
|
...nextBuilder,
|
|
1797
1855
|
to(resolver) {
|
|
1798
|
-
register.reactions.set(name, {
|
|
1856
|
+
register.reactions.set(handler.name, {
|
|
1799
1857
|
...reaction,
|
|
1800
1858
|
resolver: typeof resolver === "string" ? { target: resolver } : resolver
|
|
1801
1859
|
});
|
|
1802
1860
|
return nextBuilder;
|
|
1803
1861
|
},
|
|
1804
1862
|
void() {
|
|
1805
|
-
register.reactions.set(name, {
|
|
1863
|
+
register.reactions.set(handler.name, {
|
|
1806
1864
|
...reaction,
|
|
1807
1865
|
resolver: _void_
|
|
1808
1866
|
});
|
|
@@ -1814,11 +1872,28 @@ function projection(target, events = {}) {
|
|
|
1814
1872
|
},
|
|
1815
1873
|
build: () => ({
|
|
1816
1874
|
_tag: "Projection",
|
|
1817
|
-
events
|
|
1875
|
+
events,
|
|
1876
|
+
...target !== void 0 && { target }
|
|
1818
1877
|
}),
|
|
1819
1878
|
events
|
|
1820
1879
|
};
|
|
1821
|
-
|
|
1880
|
+
if (typeof target === "string") {
|
|
1881
|
+
return {
|
|
1882
|
+
...base,
|
|
1883
|
+
batch: (handler) => ({
|
|
1884
|
+
build: () => ({
|
|
1885
|
+
_tag: "Projection",
|
|
1886
|
+
events,
|
|
1887
|
+
target,
|
|
1888
|
+
batchHandler: handler
|
|
1889
|
+
})
|
|
1890
|
+
})
|
|
1891
|
+
};
|
|
1892
|
+
}
|
|
1893
|
+
return base;
|
|
1894
|
+
}
|
|
1895
|
+
function projection(target, events = {}) {
|
|
1896
|
+
return _projection(target, events);
|
|
1822
1897
|
}
|
|
1823
1898
|
|
|
1824
1899
|
// src/slice-builder.ts
|
|
@@ -1852,19 +1927,22 @@ function slice(states = /* @__PURE__ */ new Map(), actions = {}, events = {}, pr
|
|
|
1852
1927
|
maxRetries: options?.maxRetries ?? 3
|
|
1853
1928
|
}
|
|
1854
1929
|
};
|
|
1855
|
-
|
|
1856
|
-
|
|
1930
|
+
if (!handler.name)
|
|
1931
|
+
throw new Error(
|
|
1932
|
+
`Reaction handler for "${String(event)}" must be a named function`
|
|
1933
|
+
);
|
|
1934
|
+
events[event].reactions.set(handler.name, reaction);
|
|
1857
1935
|
return {
|
|
1858
1936
|
...builder,
|
|
1859
1937
|
to(resolver) {
|
|
1860
|
-
events[event].reactions.set(name, {
|
|
1938
|
+
events[event].reactions.set(handler.name, {
|
|
1861
1939
|
...reaction,
|
|
1862
1940
|
resolver: typeof resolver === "string" ? { target: resolver } : resolver
|
|
1863
1941
|
});
|
|
1864
1942
|
return builder;
|
|
1865
1943
|
},
|
|
1866
1944
|
void() {
|
|
1867
|
-
events[event].reactions.set(name, {
|
|
1945
|
+
events[event].reactions.set(handler.name, {
|
|
1868
1946
|
...reaction,
|
|
1869
1947
|
resolver: _void_
|
|
1870
1948
|
});
|