@rotorsoft/act 1.34.4 → 1.35.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.
Files changed (41) hide show
  1. package/README.md +2 -2
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/@types/act.d.ts +8 -6
  4. package/dist/@types/act.d.ts.map +1 -1
  5. package/dist/@types/builders/act-builder.d.ts.map +1 -1
  6. package/dist/@types/builders/state-builder.d.ts +1 -1
  7. package/dist/@types/internal/correlate-cycle.d.ts +4 -2
  8. package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
  9. package/dist/@types/internal/event-sourcing.d.ts +1 -6
  10. package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
  11. package/dist/@types/internal/projection-fold.d.ts +1 -1
  12. package/dist/@types/internal/projection-fold.d.ts.map +1 -1
  13. package/dist/@types/internal/tracing.d.ts.map +1 -1
  14. package/dist/@types/types/act.d.ts +5 -21
  15. package/dist/@types/types/act.d.ts.map +1 -1
  16. package/dist/@types/types/action.d.ts +34 -16
  17. package/dist/@types/types/action.d.ts.map +1 -1
  18. package/dist/@types/types/builders.d.ts +26 -30
  19. package/dist/@types/types/builders.d.ts.map +1 -1
  20. package/dist/@types/types/errors.d.ts +56 -29
  21. package/dist/@types/types/errors.d.ts.map +1 -1
  22. package/dist/{chunk-GBAMBUDN.cjs → chunk-BNTVE7RQ.cjs} +13 -13
  23. package/dist/{chunk-GBAMBUDN.cjs.map → chunk-BNTVE7RQ.cjs.map} +1 -1
  24. package/dist/{chunk-BKCNU6FF.js → chunk-DH2HW5XJ.js} +13 -4
  25. package/dist/chunk-DH2HW5XJ.js.map +1 -0
  26. package/dist/{chunk-RF5MV72K.cjs → chunk-L66OOP2F.cjs} +14 -5
  27. package/dist/chunk-L66OOP2F.cjs.map +1 -0
  28. package/dist/{chunk-IEM2NS6J.js → chunk-MHPUVTMX.js} +2 -2
  29. package/dist/index.cjs +287 -263
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.js +96 -72
  32. package/dist/index.js.map +1 -1
  33. package/dist/test/index.cjs +4 -4
  34. package/dist/test/index.js +2 -2
  35. package/dist/types/index.cjs +4 -2
  36. package/dist/types/index.cjs.map +1 -1
  37. package/dist/types/index.js +3 -1
  38. package/package.json +1 -1
  39. package/dist/chunk-BKCNU6FF.js.map +0 -1
  40. package/dist/chunk-RF5MV72K.cjs.map +0 -1
  41. /package/dist/{chunk-IEM2NS6J.js.map → chunk-MHPUVTMX.js.map} +0 -0
package/dist/index.js CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  sleep,
41
41
  store,
42
42
  validate
43
- } from "./chunk-IEM2NS6J.js";
43
+ } from "./chunk-MHPUVTMX.js";
44
44
  import {
45
45
  ActorSchema,
46
46
  CausationEventSchema,
@@ -57,6 +57,7 @@ import {
57
57
  InvariantError,
58
58
  LogLevels,
59
59
  NonRetryableError,
60
+ PreconditionError,
60
61
  QuerySchema,
61
62
  REDACTED,
62
63
  SHREDDED,
@@ -70,7 +71,7 @@ import {
70
71
  pii_fields,
71
72
  pii_schemas,
72
73
  sensitive
73
- } from "./chunk-BKCNU6FF.js";
74
+ } from "./chunk-DH2HW5XJ.js";
74
75
 
75
76
  // src/signals.ts
76
77
  process.once("SIGINT", async (arg) => {
@@ -1265,9 +1266,9 @@ var CorrelateCycle = class {
1265
1266
  return pattern.test(stream);
1266
1267
  }
1267
1268
  /** Scan past the checkpoint, subscribe new targets and raise their work marks. */
1268
- async correlate(query, lease = false) {
1269
+ async correlate(limit, lease = false) {
1269
1270
  if (this._restoring) return { kind: "idle", last_id: this._checkpoint };
1270
- const pass = this._scan(query, lease);
1271
+ const pass = this._scan(limit, lease);
1271
1272
  this._passes.add(pass);
1272
1273
  try {
1273
1274
  return await pass;
@@ -1275,7 +1276,7 @@ var CorrelateCycle = class {
1275
1276
  this._passes.delete(pass);
1276
1277
  }
1277
1278
  }
1278
- async _scan(query, lease) {
1279
+ async _scan(limit, lease) {
1279
1280
  await this._init();
1280
1281
  const epoch = this._epoch;
1281
1282
  const idle = () => ({
@@ -1370,7 +1371,7 @@ var CorrelateCycle = class {
1370
1371
  },
1371
1372
  // Resolvers see `pii: null`: correlate runs actor-less, and one
1372
1373
  // undecryptable row would otherwise stop every stream's reactions.
1373
- { after, limit: query.limit, with_pii: false }
1374
+ { after, limit, with_pii: false }
1374
1375
  );
1375
1376
  if (this._declared_lanes.size > 1) await this._recall(correlated);
1376
1377
  const read = last_id > after;
@@ -1409,7 +1410,7 @@ var CorrelateCycle = class {
1409
1410
  return { kind: "scanned", last_id, subscribed, marked, read };
1410
1411
  }
1411
1412
  this._checkpoint = last_id;
1412
- if (!stopped && found !== query.limit) this._lowered = raised;
1413
+ if (!stopped && found !== limit) this._lowered = raised;
1413
1414
  return { kind: "scanned", last_id, subscribed: 0, marked: 0, read };
1414
1415
  }
1415
1416
  /**
@@ -1647,14 +1648,30 @@ var subscribe = (streams, correlated_at, correlator) => store().subscribe(stream
1647
1648
 
1648
1649
  // src/internal/event-sourcing.ts
1649
1650
  import { patch } from "@rotorsoft/act-patch";
1651
+ import { prettifyError, ZodError } from "zod";
1650
1652
  var bare_patch = (_me, state2, partial) => patch(state2, partial);
1651
- var validating_patch = (me, state2, partial, event) => {
1652
- const next = patch(state2, partial);
1653
- return validate(
1654
- `${me.name}.${String(event.name)}#${event.id}`,
1655
- next,
1656
- me.state
1657
- );
1653
+ var value_at = (value, path) => path.reduce(
1654
+ (at, key) => at?.[key],
1655
+ value
1656
+ );
1657
+ var check_invariants = (target, state2, schema, pii_aware) => {
1658
+ let result;
1659
+ try {
1660
+ result = schema.safeParse(state2);
1661
+ } catch (error) {
1662
+ throw new ValidationError(target, state2, error);
1663
+ }
1664
+ if (result.success) return;
1665
+ const issues = pii_aware ? result.error.issues.filter((issue) => {
1666
+ const value = value_at(state2, issue.path);
1667
+ return value !== REDACTED && value !== SHREDDED;
1668
+ }) : result.error.issues;
1669
+ if (issues.length)
1670
+ throw new ValidationError(
1671
+ target,
1672
+ state2,
1673
+ prettifyError(new ZodError(issues))
1674
+ );
1658
1675
  };
1659
1676
  var DEFAULT_BATCH = 500;
1660
1677
  var cache_put = (stream, entry) => cache().set(stream, entry).catch(
@@ -1911,8 +1928,8 @@ async function action(me, action2, target, payload, options, patch_fn = bare_pat
1911
1928
  if (snapshot.event?.name === TOMBSTONE_EVENT)
1912
1929
  throw new StreamClosedError(stream);
1913
1930
  const expected = expectedVersion ?? (snapshot.version >= 0 ? snapshot.version : void 0);
1914
- for (const { valid: valid2, description } of me.given?.[action2] ?? [])
1915
- if (!valid2(snapshot.state, actor))
1931
+ for (const { valid, description } of me.given?.[action2] ?? [])
1932
+ if (!valid(snapshot.state, actor))
1916
1933
  throw new InvariantError(
1917
1934
  action2,
1918
1935
  validated,
@@ -1922,25 +1939,22 @@ async function action(me, action2, target, payload, options, patch_fn = bare_pat
1922
1939
  );
1923
1940
  for (const { holds, description } of me.requires?.[action2] ?? [])
1924
1941
  if (!holds(validated, snapshot.state, actor))
1925
- throw new InvariantError(
1942
+ throw new PreconditionError(
1926
1943
  action2,
1927
1944
  validated,
1928
1945
  target,
1929
1946
  snapshot,
1930
- description,
1931
- "precondition"
1947
+ description
1932
1948
  );
1933
1949
  const result = me.on[action2](validated, snapshot, target);
1934
- if (!result) return [snapshot];
1935
- if (Array.isArray(result) && result.length === 0) {
1950
+ if (!result || Array.isArray(result) && !result.length)
1936
1951
  return [snapshot];
1937
- }
1938
1952
  const tuples = Array.isArray(result[0]) ? result : [result];
1939
- const valid = tuples.map(([name, data]) => ({
1953
+ const events = tuples.map(([name, data]) => ({
1940
1954
  name,
1941
1955
  data: validate(name, data, me.events[name])
1942
1956
  }));
1943
- const emitted = valid.map((e) => me.message(e));
1957
+ const emitted = events.map((e) => me.message(e));
1944
1958
  const meta = {
1945
1959
  correlation: reactingTo?.meta.correlation || correlator({
1946
1960
  action: action2,
@@ -1961,6 +1975,28 @@ async function action(me, action2, target, payload, options, patch_fn = bare_pat
1961
1975
  } : void 0
1962
1976
  }
1963
1977
  };
1978
+ let { state: state2, patches } = snapshot;
1979
+ const created = /* @__PURE__ */ new Date();
1980
+ const folded = events.map((e, i) => {
1981
+ const event = {
1982
+ ...e,
1983
+ id: -1,
1984
+ stream,
1985
+ version: snapshot.version + i + 1,
1986
+ created,
1987
+ meta
1988
+ };
1989
+ const patch2 = me.patch[event.name](event, state2);
1990
+ state2 = patch_fn(me, state2, patch2, event);
1991
+ patches++;
1992
+ return { state: state2, patch: patch2, patches };
1993
+ });
1994
+ check_invariants(
1995
+ `${me.name}.${action2}`,
1996
+ state2,
1997
+ me.state,
1998
+ me.pii_aware
1999
+ );
1964
2000
  let committed;
1965
2001
  try {
1966
2002
  committed = await store().commit(
@@ -1975,24 +2011,17 @@ async function action(me, action2, target, payload, options, patch_fn = bare_pat
1975
2011
  if (error instanceof ConcurrencyError) void cache_drop(stream);
1976
2012
  throw error;
1977
2013
  }
1978
- let { state: state2, patches } = snapshot;
1979
- const snapshots = committed.map((row, i) => {
1980
- const event = { ...row, data: valid[i].data };
1981
- const p = me.patch[event.name](event, state2);
1982
- state2 = patch_fn(me, state2, p, event);
1983
- patches++;
1984
- return {
1985
- event,
1986
- state: state2,
1987
- version: event.version,
1988
- id: event.id,
1989
- patches,
1990
- snaps: snapshot.snaps,
1991
- patch: p,
1992
- cache_hit: snapshot.cache_hit,
1993
- replayed: snapshot.replayed
1994
- };
1995
- });
2014
+ const snapshots = committed.map((row, i) => ({
2015
+ event: { ...row, data: events[i].data },
2016
+ state: folded[i].state,
2017
+ version: row.version,
2018
+ id: row.id,
2019
+ patches: folded[i].patches,
2020
+ snaps: snapshot.snaps,
2021
+ patch: folded[i].patch,
2022
+ cache_hit: snapshot.cache_hit,
2023
+ replayed: snapshot.replayed
2024
+ }));
1996
2025
  const last = snapshots.at(-1);
1997
2026
  const contiguous = committed[0].version === snapshot.version + 1;
1998
2027
  const snapped = contiguous && me.snap?.(last);
@@ -2569,7 +2598,7 @@ function pii_strip(event, fields) {
2569
2598
 
2570
2599
  // src/internal/projection-fold.ts
2571
2600
  var FOLD_RESET = /* @__PURE__ */ Symbol("act.fold.reset");
2572
- function make_fold_handler(me, flush, config2, patch_fn = bare_patch, sensitive_fields) {
2601
+ function make_fold_handler(me, flush, config2, patch_fn, sensitive_fields) {
2573
2602
  const load_me = {
2574
2603
  ...me,
2575
2604
  view: (raw, actor) => {
@@ -2933,10 +2962,8 @@ var SettleLoop = class {
2933
2962
  };
2934
2963
 
2935
2964
  // src/act.ts
2936
- function validate_only_lanes(options, lanes) {
2937
- if (!options.onlyLanes || options.onlyLanes.length === 0) return;
2938
- const declared = /* @__PURE__ */ new Set(["default", ...lanes.map((l) => l.name)]);
2939
- const unknown = options.onlyLanes.filter((l) => !declared.has(l));
2965
+ function validate_only_lanes(only, declared) {
2966
+ const unknown = (only ?? []).filter((l) => !declared.includes(l));
2940
2967
  if (unknown.length > 0)
2941
2968
  throw new Error(
2942
2969
  `ActOptions.onlyLanes references undeclared lane(s): ${unknown.map((l) => `"${l}"`).join(", ")}`
@@ -3040,7 +3067,7 @@ var Act = class {
3040
3067
  this._batch_handlers = batch_handlers;
3041
3068
  this._lanes = lanes;
3042
3069
  this._declared_lanes = ["default", ...lanes.map((l) => l.name)];
3043
- validate_only_lanes(options, lanes);
3070
+ validate_only_lanes(options.onlyLanes, this._declared_lanes);
3044
3071
  this._ports = options.scoped ?? default_scope();
3045
3072
  const ports = this._ports;
3046
3073
  this._scoped = make_run_scoped({
@@ -3082,7 +3109,7 @@ var Act = class {
3082
3109
  handle,
3083
3110
  handle_batch
3084
3111
  );
3085
- this._advise_orphaned_lanes(options);
3112
+ this._advise_orphaned_lanes();
3086
3113
  this._audit_deps = this._build_audit_deps();
3087
3114
  this._correlate = this._build_correlate(options, classification);
3088
3115
  this._settle = this._build_settle(options);
@@ -3107,7 +3134,7 @@ var Act = class {
3107
3134
  /** Apps that declare no lanes pass no lane filter to `claim`. */
3108
3135
  _build_drain_controllers(options, lanes, handle, handle_batch) {
3109
3136
  const all_lanes = this._declared_lanes;
3110
- const only_set = options.onlyLanes && options.onlyLanes.length > 0 ? new Set(options.onlyLanes) : void 0;
3137
+ const only_set = options.onlyLanes?.length ? new Set(options.onlyLanes) : void 0;
3111
3138
  const active_lanes = only_set ? all_lanes.filter((n) => only_set.has(n)) : all_lanes;
3112
3139
  const single_default_lane = lanes.length === 0;
3113
3140
  const controllers = /* @__PURE__ */ new Map();
@@ -3144,15 +3171,14 @@ var Act = class {
3144
3171
  leaseMillis: cfg.leaseMillis
3145
3172
  }
3146
3173
  });
3147
- if (cfg?.cycleMs !== void 0 && options.drain !== false)
3174
+ if (cfg?.cycleMs !== void 0 && this._drain)
3148
3175
  controller.start(cfg.cycleMs);
3149
3176
  controllers.set(name, controller);
3150
3177
  }
3151
3178
  return controllers;
3152
3179
  }
3153
3180
  /** One process can't see the cluster, so it reports its own lane gaps. */
3154
- _advise_orphaned_lanes(options) {
3155
- if (!options.onlyLanes || options.onlyLanes.length === 0) return;
3181
+ _advise_orphaned_lanes() {
3156
3182
  const active = new Set(this._drain_controllers.keys());
3157
3183
  const orphaned = this._declared_lanes.filter((name) => !active.has(name));
3158
3184
  if (orphaned.length === 0) return;
@@ -3202,10 +3228,7 @@ var Act = class {
3202
3228
  _build_settle(options) {
3203
3229
  return new SettleLoop(
3204
3230
  {
3205
- correlate: (q) => this._correlate_pass(
3206
- { ...q, limit: q.limit ?? this._poll_limit ?? 100 },
3207
- true
3208
- ),
3231
+ correlate: (q) => this._correlate_pass(q.limit ?? this._poll_limit ?? 100, true),
3209
3232
  // `settle()` validated the options, and `_schedule` skips writer-only
3210
3233
  // instances, so the loop drains through the internal path.
3211
3234
  drain: (o) => this._scoped(() => this._drain_all(o)),
@@ -3300,9 +3323,10 @@ var Act = class {
3300
3323
  }
3301
3324
  /**
3302
3325
  * Executes an action: validates the payload, loads the state, checks its
3303
- * invariants, and commits the events its handler emits, with optimistic
3304
- * concurrency on the stream. The reactions to those events settle on
3305
- * their own (unless `autoSettle: false`).
3326
+ * preconditions, runs the handler, folds the events it emits, checks the
3327
+ * state's invariants (its schema) on the result, and commits them with
3328
+ * optimistic concurrency on the stream. The reactions to those events
3329
+ * settle on their own (unless `autoSettle: false`).
3306
3330
  *
3307
3331
  * @template TKey - Action name from registered actions
3308
3332
  * @param action - The name of the action to execute
@@ -3313,8 +3337,9 @@ var Act = class {
3313
3337
  * framework or orchestrator-level correlator for this call only.
3314
3338
  * @returns Array of snapshots for all affected states (usually one)
3315
3339
  *
3316
- * @throws {ValidationError} If payload doesn't match action schema
3317
- * @throws {InvariantError} If business rules are violated
3340
+ * @throws {ValidationError} If the payload doesn't match the action schema,
3341
+ * or the state the action leaves breaks the state schema
3342
+ * @throws {PreconditionError} If business rules are violated
3318
3343
  * @throws {ConcurrencyError} If another process modified the stream
3319
3344
  *
3320
3345
  * @example Basic action execution
@@ -3333,7 +3358,7 @@ var Act = class {
3333
3358
  *
3334
3359
  * @see {@link Target} for target structure
3335
3360
  * @see {@link Snapshot} for return value structure
3336
- * @see {@link ValidationError}, {@link InvariantError}, {@link ConcurrencyError}
3361
+ * @see {@link ValidationError}, {@link PreconditionError}, {@link ConcurrencyError}
3337
3362
  */
3338
3363
  async do(action2, target, payload, options) {
3339
3364
  const reacting_to = options?.reactingTo ?? current_reacting();
@@ -3567,17 +3592,17 @@ var Act = class {
3567
3592
  * @see {@link start_correlations} for automatic periodic correlation
3568
3593
  */
3569
3594
  async correlate(query = {}) {
3570
- const pass = await this._correlate_pass({ limit: query.limit ?? 10 });
3595
+ const pass = await this._correlate_pass(query.limit ?? 10);
3571
3596
  return {
3572
3597
  subscribed: pass.kind === "scanned" ? pass.subscribed : 0,
3573
3598
  last_id: pass.last_id
3574
3599
  };
3575
3600
  }
3576
3601
  /** One correlate pass; arms every lane when it found work. */
3577
- async _correlate_pass(query, lease = false) {
3602
+ async _correlate_pass(limit, lease = false) {
3578
3603
  if (!this._drain) return { kind: "idle", last_id: -1 };
3579
3604
  return this._scoped(async () => {
3580
- const pass = await this._correlate.correlate(query, lease);
3605
+ const pass = await this._correlate.correlate(limit, lease);
3581
3606
  if (pass.kind === "scanned" && pass.marked > 0) this._arm_all();
3582
3607
  return pass;
3583
3608
  });
@@ -4640,7 +4665,7 @@ function act() {
4640
4665
  if (reset) Object.defineProperty(wrapped, FOLD_RESET, { value: reset });
4641
4666
  return wrapped;
4642
4667
  };
4643
- const make_batch_handlers = (patch_fn) => {
4668
+ const make_batch_handlers = () => {
4644
4669
  const handlers = new Map(batch_handlers);
4645
4670
  for (const spec of fold_specs) {
4646
4671
  handlers.set(
@@ -4650,7 +4675,7 @@ function act() {
4650
4675
  spec.merged,
4651
4676
  spec.flush,
4652
4677
  spec.config,
4653
- patch_fn,
4678
+ bare_patch,
4654
4679
  // Head loads strip sensitive keys like the warm path.
4655
4680
  registry.sensitive_fields
4656
4681
  )
@@ -4731,7 +4756,6 @@ function act() {
4731
4756
  },
4732
4757
  build: (options) => {
4733
4758
  resolveActConfig(options);
4734
- const patch_fn = options?.validateFoldedState === true ? validating_patch : bare_patch;
4735
4759
  if (!_built) {
4736
4760
  for (const proj of pending_projections) add_projection(proj);
4737
4761
  for (const proj of fold_projections) {
@@ -4792,10 +4816,9 @@ function act() {
4792
4816
  return new Act(
4793
4817
  registry,
4794
4818
  states,
4795
- make_batch_handlers(patch_fn),
4819
+ make_batch_handlers(),
4796
4820
  options,
4797
- lanes,
4798
- patch_fn
4821
+ lanes
4799
4822
  );
4800
4823
  },
4801
4824
  events: registry.events
@@ -5270,6 +5293,7 @@ export {
5270
5293
  LogLevels,
5271
5294
  NonRetryableError,
5272
5295
  PackageSchema,
5296
+ PreconditionError,
5273
5297
  QuerySchema,
5274
5298
  REDACTED,
5275
5299
  SHREDDED,