@rotorsoft/act 0.25.1 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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
- return this.handle(
1233
- { ...lease, at },
1234
- payloadsMap.get(lease.stream)
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(
@@ -1673,14 +1713,15 @@ var _void_ = () => void 0;
1673
1713
  function act(states = /* @__PURE__ */ new Map(), registry = {
1674
1714
  actions: {},
1675
1715
  events: {}
1676
- }, pendingProjections = []) {
1716
+ }, pendingProjections = [], batchHandlers = /* @__PURE__ */ new Map()) {
1677
1717
  const builder = {
1678
1718
  withState: (state2) => {
1679
1719
  registerState(state2, states, registry.actions, registry.events);
1680
1720
  return act(
1681
1721
  states,
1682
1722
  registry,
1683
- pendingProjections
1723
+ pendingProjections,
1724
+ batchHandlers
1684
1725
  );
1685
1726
  },
1686
1727
  withSlice: (input) => {
@@ -1697,22 +1738,28 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
1697
1738
  return act(
1698
1739
  states,
1699
1740
  registry,
1700
- pendingProjections
1741
+ pendingProjections,
1742
+ batchHandlers
1701
1743
  );
1702
1744
  },
1703
1745
  withProjection: (proj) => {
1704
1746
  mergeProjection(proj, registry.events);
1747
+ if (proj.batchHandler && proj.target) {
1748
+ batchHandlers.set(proj.target, proj.batchHandler);
1749
+ }
1705
1750
  return act(
1706
1751
  states,
1707
1752
  registry,
1708
- pendingProjections
1753
+ pendingProjections,
1754
+ batchHandlers
1709
1755
  );
1710
1756
  },
1711
1757
  withActor: () => {
1712
1758
  return act(
1713
1759
  states,
1714
1760
  registry,
1715
- pendingProjections
1761
+ pendingProjections,
1762
+ batchHandlers
1716
1763
  );
1717
1764
  },
1718
1765
  on: (event) => ({
@@ -1749,10 +1796,14 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
1749
1796
  build: () => {
1750
1797
  for (const proj of pendingProjections) {
1751
1798
  mergeProjection(proj, registry.events);
1799
+ if (proj.batchHandler && proj.target) {
1800
+ batchHandlers.set(proj.target, proj.batchHandler);
1801
+ }
1752
1802
  }
1753
1803
  return new Act(
1754
1804
  registry,
1755
- states
1805
+ states,
1806
+ batchHandlers
1756
1807
  );
1757
1808
  },
1758
1809
  events: registry.events
@@ -1761,9 +1812,9 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
1761
1812
  }
1762
1813
 
1763
1814
  // src/projection-builder.ts
1764
- function projection(target, events = {}) {
1765
- const defaultResolver = target ? { target } : void 0;
1766
- const builder = {
1815
+ function _projection(target, events) {
1816
+ const defaultResolver = typeof target === "string" ? { target } : void 0;
1817
+ const base = {
1767
1818
  on: (entry) => {
1768
1819
  const keys = Object.keys(entry);
1769
1820
  if (keys.length !== 1) throw new Error(".on() requires exactly one key");
@@ -1788,10 +1839,7 @@ function projection(target, events = {}) {
1788
1839
  const register = events[event];
1789
1840
  const name = handler.name || `${event}_${register.reactions.size}`;
1790
1841
  register.reactions.set(name, reaction);
1791
- const nextBuilder = projection(
1792
- target,
1793
- events
1794
- );
1842
+ const nextBuilder = _projection(target, events);
1795
1843
  return {
1796
1844
  ...nextBuilder,
1797
1845
  to(resolver) {
@@ -1814,11 +1862,28 @@ function projection(target, events = {}) {
1814
1862
  },
1815
1863
  build: () => ({
1816
1864
  _tag: "Projection",
1817
- events
1865
+ events,
1866
+ ...target !== void 0 && { target }
1818
1867
  }),
1819
1868
  events
1820
1869
  };
1821
- return builder;
1870
+ if (typeof target === "string") {
1871
+ return {
1872
+ ...base,
1873
+ batch: (handler) => ({
1874
+ build: () => ({
1875
+ _tag: "Projection",
1876
+ events,
1877
+ target,
1878
+ batchHandler: handler
1879
+ })
1880
+ })
1881
+ };
1882
+ }
1883
+ return base;
1884
+ }
1885
+ function projection(target, events = {}) {
1886
+ return _projection(target, events);
1822
1887
  }
1823
1888
 
1824
1889
  // src/slice-builder.ts