@voltro/testing 0.65.0 → 0.66.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.d.ts CHANGED
@@ -20,6 +20,7 @@ import { RestRouteDescriptor } from '@voltro/protocol/rest';
20
20
  import { RestServeBindings } from '@voltro/protocol/rest';
21
21
  import { Row } from '@voltro/database';
22
22
  import { RowFilter } from '@voltro/runtime';
23
+ import { RpcConnection } from '@voltro/runtime';
23
24
  import { RpcInterceptor } from '@voltro/protocol';
24
25
  import { RpcKind } from '@voltro/protocol';
25
26
  import { Schema } from 'effect';
@@ -351,6 +352,10 @@ export declare interface MakeTestContextOptions<RowFilterCtx = unknown> {
351
352
  /** The acting subject. Default: anonymous, no tenant. Read it back at
352
353
  * `ctx.request.subject` (the same place a live handler reads it). */
353
354
  readonly subject?: Subject;
355
+ /** Optional connection fixture, or the actual RuntimeContext.connection from
356
+ * a listener test. Shared by transaction/subject re-scopes, never invented by
357
+ * the harness; omitting it models an internal invocation. */
358
+ readonly connection?: RpcConnection;
354
359
  /** Tables to wire into the store's schema. Default: all globally
355
360
  * registered tables (whatever the test imported). */
356
361
  readonly tables?: ReadonlyArray<TableLike>;
@@ -930,6 +935,11 @@ export declare const user: (id: string, options?: TestSubjectOptions) => Subject
930
935
  */
931
936
  export declare const withFrozenTime: <A>(at: Date | number | string | MockClock, body: (clock: MockClock) => A) => A;
932
937
 
938
+ /** Model another native RPC connection against the SAME test store/services.
939
+ * The connection may come from a real listener. Passing undefined models an
940
+ * internal call; no identity is fabricated or copied from another caller. */
941
+ export declare const withRpcConnection: (ctx: TestContext, connection: RpcConnection | undefined) => TestContext;
942
+
933
943
  /** A workflow + its execute function, as the framework pairs them via
934
944
  * `workflow.toLayer(execute)`. Structural (no `@effect/workflow` generic
935
945
  * signature) so callers can pass the value from `workflow({ name, ... })`
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { AUTO_FILLED_COLUMNS as e, allRegisteredTables as t, changeDelete as n, changeInsert as r, changeSoftDelete as i, changeUpdate as a, clearRelationsRegistry as o, missingRequiredColumns as s, registerRelations as c } from "@voltro/database";
2
2
  import { Cause as l, Effect as u, Exit as d, Layer as f, Schema as p } from "effect";
3
3
  import { SubjectService as m, anonymousSubject as h, checkGuardsEffect as g, composeAuthStrategies as _, composeRpcInterceptors as v, eventRoute as y, systemSubject as b, tenantScopedSubject as ee } from "@voltro/protocol";
4
- import { EventBus as te, InMemoryDataStore as ne, NO_ROW_FILTER as x, appValidation as re, applyRowFilterToDescriptor as ie, clearSystemStoreHandle as ae, getRowFilter as S, getSystemStoreHandle as oe, makeAppAccess as se, makeDataLoader as ce, makeEffectStoreLayer as le, makeEventPublisher as ue, makeOutboxFacade as de, makeSchemaRegistry as fe, publishEvent as pe, resolveRowFilterScopeFor as me, runProvidedEffect as C, runWithDeadlockRetry as he, setSystemStoreHandle as w, wrapStoreWithMixinBehaviour as T } from "@voltro/runtime";
4
+ import { EventBus as x, InMemoryDataStore as te, NO_ROW_FILTER as S, appValidation as ne, applyRowFilterToDescriptor as re, clearSystemStoreHandle as ie, getRowFilter as C, getSystemStoreHandle as ae, makeAppAccess as oe, makeConnectionState as se, makeDataLoader as ce, makeEffectStoreLayer as le, makeEventPublisher as ue, makeOutboxFacade as de, makeSchemaRegistry as fe, publishEvent as pe, resolveRowFilterScopeFor as me, runProvidedEffect as w, runWithDeadlockRetry as he, setSystemStoreHandle as T, wrapStoreWithMixinBehaviour as E } from "@voltro/runtime";
5
5
  import { installEnvSnapshot as ge } from "@voltro/env";
6
6
  import _e from "node:net";
7
7
  import { describe as ve } from "vitest";
@@ -9,39 +9,39 @@ import { collectPublicApiRoutes as ye, dispatchSharedPath as be, restRoutesToHtt
9
9
  import { createLogger as Se } from "@voltro/logger";
10
10
  import { CurrentWorkflowRunId as Ce, inMemoryWorkflowEngineLayer as we, makeInMemoryRecorder as Te } from "@voltro/workflow";
11
11
  //#region src/fixtureRow.ts
12
- var Ee = new Set(e), E = 0, De = () => ++E, Oe = (e, t) => {
12
+ var Ee = new Set(e), D = 0, O = () => ++D, De = (e, t) => {
13
13
  if (t.oneOf && t.oneOf.length > 0) return t.oneOf[0];
14
14
  if (t.enumValues && t.enumValues.length > 0) return t.enumValues[0];
15
15
  let n = t.unique === !0;
16
16
  switch (t.type) {
17
17
  case "text":
18
- case "reference":
19
18
  case "id":
20
- case "enum": return n ? `${e}-${++E}` : e;
19
+ case "enum": return n ? `${e}-${++D}` : e;
20
+ case "reference": return t.referenceStorage === "numeric" ? n ? ++D : 0 : n ? `${e}-${++D}` : e;
21
21
  case "integer":
22
- case "real": return n ? ++E : 0;
22
+ case "real": return n ? ++D : 0;
23
23
  case "decimal":
24
- case "bigint": return n ? String(++E) : "0";
24
+ case "bigint": return n ? String(++D) : "0";
25
25
  case "boolean": return !1;
26
26
  case "timestamp":
27
27
  case "date": return /* @__PURE__ */ new Date(0);
28
28
  default: throw Error(`fixtureRow: column '${e}' is a required '${t.type}' with no default, and fixtureRow can't synthesize a safe placeholder for that type. Pass it explicitly: fixtureRow(table, { ${e}: … }).`);
29
29
  }
30
- }, D = (e, t = {}) => {
30
+ }, k = (e, t = {}) => {
31
31
  let n = {};
32
- for (let r of s(e, t)) Ee.has(r) || (n[r] = Oe(r, e.fields[r]));
32
+ for (let r of s(e, t)) Ee.has(r) || (n[r] = De(r, e.fields[r]));
33
33
  return {
34
34
  ...n,
35
35
  ...t
36
36
  };
37
- }, ke = (e) => {
37
+ }, Oe = (e) => {
38
38
  let t = {};
39
- for (let [n, r] of Object.entries(e)) t[n] = typeof r == "function" ? r(De()) : r;
39
+ for (let [n, r] of Object.entries(e)) t[n] = typeof r == "function" ? r(O()) : r;
40
40
  return t;
41
- }, Ae = (e) => {
41
+ }, ke = (e) => {
42
42
  for (let [t, n] of Object.entries(e.fields)) if (n.type === "id") return t;
43
43
  return "id";
44
- }, je = async (e, t, n, r, i) => {
44
+ }, Ae = async (e, t, n, r, i) => {
45
45
  let a = {};
46
46
  for (let o of s(t, n)) {
47
47
  let n = t.fields[o];
@@ -49,29 +49,29 @@ var Ee = new Set(e), E = 0, De = () => ++E, Oe = (e, t) => {
49
49
  let s = n.references();
50
50
  if (i.includes(s.tableName)) throw Error(`defineFactory: creating '${t.tableName}' requires '${s.tableName}' via '${o}', and that reference is cyclic (${[...i, s.tableName].join(" → ")}). Insert one side first and pass the id: create(store, { ${o}: existing.id }).`);
51
51
  let c = r[o];
52
- a[o] = (c === void 0 ? await O(e, s, {}, {}, [...i, s.tableName]) : await c.create(e, {}))[Ae(s)];
52
+ a[o] = (c === void 0 ? await A(e, s, {}, {}, [...i, s.tableName]) : await c.create(e, {}))[n.referenceColumn ?? ke(s)];
53
53
  }
54
54
  return a;
55
- }, O = async (e, t, n, r, i) => {
55
+ }, A = async (e, t, n, r, i) => {
56
56
  let a = {
57
- ...await je(e, t, n, r, i),
57
+ ...await Ae(e, t, n, r, i),
58
58
  ...n
59
59
  };
60
- return e.insert(t.tableName, D(t, a));
61
- }, k = (e, t) => typeof e == "function" ? e(t) : e ?? {}, Me = (e, t = {}) => {
60
+ return e.insert(t.tableName, k(t, a));
61
+ }, j = (e, t) => typeof e == "function" ? e(t) : e ?? {}, je = (e, t = {}) => {
62
62
  let n = t.traits ?? {}, r = t.associations ?? {}, i = (t) => {
63
63
  let a = (e = {}) => ({
64
- ...ke(t),
64
+ ...Oe(t),
65
65
  ...e
66
- }), o = (t = {}) => D(e, a(t));
66
+ }), o = (t = {}) => k(e, a(t));
67
67
  return {
68
68
  table: e,
69
69
  build: o,
70
- buildList: (e, t) => Array.from({ length: e }, (e, n) => o(k(t, n))),
71
- create: (t, n = {}) => O(t, e, a(n), r, [e.tableName]),
70
+ buildList: (e, t) => Array.from({ length: e }, (e, n) => o(j(t, n))),
71
+ create: (t, n = {}) => A(t, e, a(n), r, [e.tableName]),
72
72
  createList: async (t, n, i) => {
73
73
  let o = [];
74
- for (let s = 0; s < n; s++) o.push(await O(t, e, a(k(i, s)), r, [e.tableName]));
74
+ for (let s = 0; s < n; s++) o.push(await A(t, e, a(j(i, s)), r, [e.tableName]));
75
75
  return o;
76
76
  },
77
77
  with: (...r) => {
@@ -93,19 +93,19 @@ var Ee = new Set(e), E = 0, De = () => ++E, Oe = (e, t) => {
93
93
  };
94
94
  };
95
95
  return i(t.defaults ?? {});
96
- }, A = Symbol.for("voltro/testing/mockClock.installed"), j = () => globalThis, Ne = (e, t) => new Proxy(e, {
96
+ }, M = Symbol.for("voltro/testing/mockClock.installed"), N = () => globalThis, Me = (e, t) => new Proxy(e, {
97
97
  construct: (e, n, r) => Reflect.construct(e, n.length === 0 ? [t()] : n, r),
98
98
  apply: () => new e(t()).toString(),
99
99
  get: (e, n, r) => n === "now" ? () => t() : Reflect.get(e, n, r)
100
- }), M = (e) => {
100
+ }), P = (e) => {
101
101
  if (typeof e == "number") return e;
102
102
  let t = e instanceof Date ? e.getTime() : Date.parse(e);
103
103
  if (Number.isNaN(t)) throw Error(`mockClock: cannot read an instant from '${String(e)}'`);
104
104
  return t;
105
- }, N = class {
105
+ }, F = class {
106
106
  currentMs;
107
107
  constructor(e = "2026-01-01T00:00:00Z") {
108
- this.currentMs = M(e);
108
+ this.currentMs = P(e);
109
109
  }
110
110
  now() {
111
111
  return this.currentMs;
@@ -114,47 +114,47 @@ var Ee = new Set(e), E = 0, De = () => ++E, Oe = (e, t) => {
114
114
  return new Date(this.currentMs);
115
115
  }
116
116
  advance(e) {
117
- this.currentMs += typeof e == "number" ? e : Le(e);
117
+ this.currentMs += typeof e == "number" ? e : Ie(e);
118
118
  }
119
119
  set(e) {
120
- this.currentMs = M(e);
120
+ this.currentMs = P(e);
121
121
  }
122
122
  get installed() {
123
- return j()[A]?.clock === this;
123
+ return N()[M]?.clock === this;
124
124
  }
125
125
  install() {
126
- let e = j(), t = e[A];
126
+ let e = N(), t = e[M];
127
127
  if (t !== void 0) throw Error("mockClock: global time is already faked" + (t.clock === this ? " by this same clock" : " by another MockClock") + ". Uninstall it before installing again — a nested install restores the outer fake on the way out, which leaves the realm frozen with nothing pointing at why. `withFrozenTime` always restores, including on a throw.");
128
128
  let n = globalThis.Date;
129
- return e[A] = {
129
+ return e[M] = {
130
130
  realDate: n,
131
131
  clock: this
132
- }, globalThis.Date = Ne(n, () => this.currentMs), () => {
132
+ }, globalThis.Date = Me(n, () => this.currentMs), () => {
133
133
  this.uninstall();
134
134
  };
135
135
  }
136
136
  uninstall() {
137
- let e = j(), t = e[A];
138
- t !== void 0 && t.clock === this && (globalThis.Date = t.realDate, delete e[A]);
137
+ let e = N(), t = e[M];
138
+ t !== void 0 && t.clock === this && (globalThis.Date = t.realDate, delete e[M]);
139
139
  }
140
- }, Pe = (e) => typeof e == "object" && !!e && typeof e.then == "function", Fe = (e, t) => {
141
- let n = e instanceof N ? e : new N(e);
140
+ }, Ne = (e) => typeof e == "object" && !!e && typeof e.then == "function", Pe = (e, t) => {
141
+ let n = e instanceof F ? e : new F(e);
142
142
  n.install();
143
143
  let r = !1;
144
144
  try {
145
145
  let e = t(n);
146
- return Pe(e) ? (r = !0, e.then((e) => (n.uninstall(), e), (e) => {
146
+ return Ne(e) ? (r = !0, e.then((e) => (n.uninstall(), e), (e) => {
147
147
  throw n.uninstall(), e;
148
148
  })) : e;
149
149
  } finally {
150
150
  r || n.uninstall();
151
151
  }
152
- }, Ie = (e) => u.acquireRelease(u.sync(() => {
153
- let t = e instanceof N ? e : new N(e);
152
+ }, Fe = (e) => u.acquireRelease(u.sync(() => {
153
+ let t = e instanceof F ? e : new F(e);
154
154
  return t.install(), t;
155
155
  }), (e) => u.sync(() => {
156
156
  e.uninstall();
157
- })), Le = (e) => {
157
+ })), Ie = (e) => {
158
158
  let t = /^\s*(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)\s*$/.exec(e);
159
159
  if (!t) throw Error(`mockClock: cannot parse duration '${e}'`);
160
160
  let n = Number(t[1]), r = t[2];
@@ -166,7 +166,7 @@ var Ee = new Set(e), E = 0, De = () => ++E, Oe = (e, t) => {
166
166
  case "d": return n * 864e5;
167
167
  default: throw Error(`mockClock: unknown unit '${r}'`);
168
168
  }
169
- }, P = class {
169
+ }, I = class {
170
170
  emitted = [];
171
171
  async emit(e, t) {
172
172
  let n = typeof e == "string" ? e : e.id ?? e.event ?? "(unnamed event)";
@@ -185,8 +185,8 @@ var Ee = new Set(e), E = 0, De = () => ++E, Oe = (e, t) => {
185
185
  clear() {
186
186
  this.emitted.length = 0;
187
187
  }
188
- }, F = (e = {}) => {
189
- let t = e.tenantId ?? null, n = new te({
188
+ }, L = (e = {}) => {
189
+ let t = e.tenantId ?? null, n = new x({
190
190
  origin: e.origin ?? "test",
191
191
  ...e.ringSize === void 0 ? {} : { ringSize: e.ringSize }
192
192
  });
@@ -255,7 +255,7 @@ var Ee = new Set(e), E = 0, De = () => ++E, Oe = (e, t) => {
255
255
  n.clear();
256
256
  }
257
257
  };
258
- }, I = class {
258
+ }, R = class {
259
259
  queue;
260
260
  calls = [];
261
261
  constructor(e) {
@@ -270,27 +270,31 @@ var Ee = new Set(e), E = 0, De = () => ++E, Oe = (e, t) => {
270
270
  remaining() {
271
271
  return this.queue.length;
272
272
  }
273
- }, Re = (e) => e, L = "00000000000000000000000000000000", R = /* @__PURE__ */ new WeakMap(), z = /* @__PURE__ */ new WeakMap(), B = /* @__PURE__ */ new WeakMap(), V = /* @__PURE__ */ new WeakMap(), H = /* @__PURE__ */ new WeakMap(), U = (e, t) => {
274
- let n = V.get(e) ?? [], r = t === "mutation" ? "interceptMutation" : t === "query" ? "interceptQuery" : "interceptAction", i = [];
273
+ }, Le = (e) => e, z = "00000000000000000000000000000000", B = /* @__PURE__ */ new WeakMap(), V = /* @__PURE__ */ new WeakMap(), Re = (e, t) => {
274
+ let n = V.get(e);
275
+ if (n === void 0) throw Error("withRpcConnection requires a context created by makeTestContext");
276
+ return n(t);
277
+ }, H = /* @__PURE__ */ new WeakMap(), U = /* @__PURE__ */ new WeakMap(), W = /* @__PURE__ */ new WeakMap(), G = /* @__PURE__ */ new WeakMap(), K = (e, t) => {
278
+ let n = W.get(e) ?? [], r = t === "mutation" ? "interceptMutation" : t === "query" ? "interceptQuery" : "interceptAction", i = [];
275
279
  for (let e of n) {
276
280
  let t = e[r];
277
281
  typeof t == "function" && i.push(t);
278
282
  }
279
283
  return v(i);
280
- }, W = (e) => {
281
- let t = (V.get(e) ?? []).map((e) => e.services).filter((e) => e !== void 0), n = H.get(e) ?? [], r = [...t, ...n];
284
+ }, q = (e) => {
285
+ let t = (W.get(e) ?? []).map((e) => e.services).filter((e) => e !== void 0), n = G.get(e) ?? [], r = [...t, ...n];
282
286
  if (r.length !== 0) return f.mergeAll(...r);
283
- }, G = (e) => {
284
- let t = z.get(e);
287
+ }, J = (e) => {
288
+ let t = H.get(e);
285
289
  t !== void 0 && (t.length = 0);
286
- }, K = async (e) => {
287
- let t = z.get(e);
290
+ }, Y = async (e) => {
291
+ let t = H.get(e);
288
292
  if (t === void 0 || t.length === 0) return;
289
293
  let n = [...t];
290
294
  t.length = 0;
291
295
  for (let e of n) await e();
292
- }, ze = (e) => B.get(e) ?? [], q = async (e, t) => {
293
- let n = R.get(e);
296
+ }, ze = (e) => U.get(e) ?? [], X = async (e, t) => {
297
+ let n = B.get(e);
294
298
  return n === void 0 ? e.store.transactional(async (n) => t({
295
299
  ...e,
296
300
  store: n
@@ -363,28 +367,28 @@ var Ee = new Set(e), E = 0, De = () => ++E, Oe = (e, t) => {
363
367
  }
364
368
  };
365
369
  }, He = async (e, t) => {
366
- let n = oe();
367
- w(e);
370
+ let n = ae();
371
+ T(e);
368
372
  try {
369
373
  return await t();
370
374
  } finally {
371
- n === void 0 ? ae() : w(n);
375
+ n === void 0 ? ie() : T(n);
372
376
  }
373
- }, J = /* @__PURE__ */ new Set(), Ue = (e) => {
374
- J.has(e) || (J.add(e), console.warn(e));
377
+ }, Z = /* @__PURE__ */ new Set(), Ue = (e) => {
378
+ Z.has(e) || (Z.add(e), console.warn(e));
375
379
  }, We = (e, t, n) => {
376
380
  let r = Ge, i;
377
381
  return () => {
378
- let a = t ?? S();
379
- return t !== void 0 && S() !== void 0 && t !== S() && Ue("[voltro:testing] this test passes `rowFilter:` while a DIFFERENT filter is registered via `setRowFilter`. The option wins, so this test does not exercise your app's registration. Drop `rowFilter:` to run the real process-global one."), (i === void 0 || a !== r) && (r = a, i = He(n, () => C(me(a, e, (e) => {
382
+ let a = t ?? C();
383
+ return t !== void 0 && C() !== void 0 && t !== C() && Ue("[voltro:testing] this test passes `rowFilter:` while a DIFFERENT filter is registered via `setRowFilter`. The option wins, so this test does not exercise your app's registration. Drop `rowFilter:` to run the real process-global one."), (i === void 0 || a !== r) && (r = a, i = He(n, () => w(me(a, e, (e) => {
380
384
  console.error("[voltro:testing] row filter failed to load — reads refused for this subject", e);
381
385
  })))), i;
382
386
  };
383
- }, Ge = Symbol("unresolved"), Y = (e, t) => new Proxy(e, { get: (e, n) => {
384
- if (n === "query") return async (n) => e.query(ie(await t(), n));
387
+ }, Ge = Symbol("unresolved"), Ke = (e, t) => new Proxy(e, { get: (e, n) => {
388
+ if (n === "query") return async (n) => e.query(re(await t(), n));
385
389
  let r = Reflect.get(e, n, e);
386
390
  return typeof r == "function" ? r.bind(e) : r;
387
- } }), Ke = (e = {}) => {
391
+ } }), qe = (e = {}) => {
388
392
  if (ge({
389
393
  ...process.env,
390
394
  ...e.env ?? {}
@@ -392,78 +396,90 @@ var Ee = new Set(e), E = 0, De = () => ++E, Oe = (e, t) => {
392
396
  o();
393
397
  for (let t of e.relations) c(t);
394
398
  }
395
- let n = e.tables ?? t(), r = fe(n), i = new ne(e.store ?? {}), a = {
399
+ let n = e.tables ?? t(), r = fe(n), i = new te(e.store ?? {}), a = {
396
400
  dataStore: i,
397
401
  schemaRegistry: r
398
402
  };
399
- w(a);
400
- let s = new N(e.clockStart), l = new P(), u = F(), d = new I(e.llmResponses ?? []), f = Be(() => s.now()), p = Ve(() => s.now()), m = (t, n = i, o) => {
401
- let c = {
403
+ T(a);
404
+ let s = new F(e.clockStart), l = new I(), u = L(), d = new R(e.llmResponses ?? []), f = Be(() => s.now()), p = Ve(() => s.now()), m = (t, n, o = i, c) => {
405
+ let h = {
402
406
  subject: t,
403
- traceId: L
404
- }, h = ue({
407
+ traceId: z,
408
+ ...n === void 0 ? {} : { connection: n }
409
+ }, g = c?.queued ?? [], _ = c?.nudges ?? [], v = ue({
405
410
  bus: u.bus,
406
- tenantId: t.tenantId ?? null
407
- }), g = o?.queued ?? [], _ = o?.nudges ?? [], v = ce({ store: n }), y = We(t, e.rowFilter, a), b = {
411
+ tenantId: t.tenantId ?? null,
412
+ ...o === i ? {} : { deferToCommit: (e) => {
413
+ g.push(async () => {
414
+ e();
415
+ });
416
+ } }
417
+ }), y = ce({ store: o }), b = We(t, e.rowFilter, a), x = {
408
418
  clock: s,
409
419
  webhooks: l,
410
- events: h,
420
+ events: v,
411
421
  eventBus: u,
412
422
  llm: d,
413
423
  ...e.ai === void 0 ? {} : { ai: e.ai },
414
- request: c,
415
- validation: re,
416
- access: se(t),
424
+ request: h,
425
+ ...n === void 0 ? {} : { connectionState: se(n, o === i ? void 0 : (e) => {
426
+ g.push(e);
427
+ }) },
428
+ validation: ne,
429
+ access: oe(t),
417
430
  cache: f,
418
431
  kv: p,
419
- store: T(Y(n, y), {
432
+ store: E(Ke(o, b), {
420
433
  subject: t,
421
434
  schemaRegistry: r,
422
- rowFilter: x
435
+ rowFilter: S
423
436
  }),
424
- storeForTenant: (e) => T(Y(n, y), {
437
+ storeForTenant: (e) => E(Ke(o, b), {
425
438
  subject: ee(t, e),
426
439
  schemaRegistry: r,
427
- rowFilter: x
440
+ rowFilter: S
428
441
  }),
429
442
  outbox: de({
430
- store: n,
443
+ store: o,
431
444
  subject: t,
432
- traceId: L,
445
+ traceId: z,
433
446
  nudge: () => {
434
- _.push(L);
447
+ _.push(z);
435
448
  },
436
449
  afterCommit: (e) => {
437
450
  g.push(e);
438
451
  }
439
452
  }),
440
- load: v.load,
441
- loadMany: v.loadMany,
442
- withSubject: (e, t) => Promise.resolve(t(m(e, n, {
453
+ load: y.load,
454
+ loadMany: y.loadMany,
455
+ withSubject: (e, t) => Promise.resolve(t(m(e, n, o, {
443
456
  queued: g,
444
457
  nudges: _
445
458
  }))),
446
459
  withTenant: (e, r) => Promise.resolve(r(m({
447
460
  ...t,
448
461
  tenantId: e
449
- }, n, {
462
+ }, n, o, {
450
463
  queued: g,
451
464
  nudges: _
452
465
  })))
453
466
  };
454
- return z.set(b, g), B.set(b, _), V.set(b, e.plugins ?? []), H.set(b, e.layers ?? []), R.set(b, (e) => n.transactional((n) => e(m(t, n, {
467
+ return H.set(x, g), V.set(x, (e) => m(t, e, o, {
468
+ queued: g,
469
+ nudges: _
470
+ })), U.set(x, _), W.set(x, e.plugins ?? []), G.set(x, e.layers ?? []), B.set(x, (e) => o.transactional((r) => e(m(t, n, r, {
455
471
  queued: g,
456
472
  nudges: _
457
- })))), b;
473
+ })))), x;
458
474
  };
459
- return m(e.subject ?? h(null));
460
- }, qe = (e, t) => {
461
- let n = W(t);
475
+ return m(e.subject ?? h(null), e.connection);
476
+ }, Je = (e, t) => {
477
+ let n = q(t);
462
478
  return (n === void 0 ? e : u.provide(e, n)).pipe(u.provide(le(t.store)), u.provideService(m, t.request.subject));
463
- }, X = async (e, t, n, r) => {
479
+ }, Ye = async (e, t, n, r) => {
464
480
  let i = e.input, a = await p.decodeUnknownPromise(i)(n), o = e.kind, s = async (e) => {
465
481
  let n = t(a, e);
466
- return u.isEffect(n) ? C(qe(n, e)) : n;
482
+ return u.isEffect(n) ? w(Je(n, e)) : n;
467
483
  }, c = async () => {
468
484
  let t = e.guards;
469
485
  if (t !== void 0 && t.length > 0) {
@@ -471,9 +487,9 @@ var Ee = new Set(e), E = 0, De = () => ++E, Oe = (e, t) => {
471
487
  if (e !== null) throw e;
472
488
  }
473
489
  if (o !== "mutation") return s(r);
474
- let n = await he(async () => q(r, async (e) => (G(r), s(e))), { delay: () => Promise.resolve() });
475
- return await K(r), n;
476
- }, f = o === "mutation" || o === "query" || o === "action" ? o : void 0, m = f === void 0 ? void 0 : U(r, f);
490
+ let n = await he(async () => X(r, async (e) => (J(r), s(e))), { delay: () => Promise.resolve() });
491
+ return await Y(r), n;
492
+ }, f = o === "mutation" || o === "query" || o === "action" ? o : void 0, m = f === void 0 ? void 0 : K(r, f);
477
493
  if (m === void 0 || f === void 0) return await c();
478
494
  let h = m(u.tryPromise({
479
495
  try: () => c(),
@@ -488,12 +504,12 @@ var Ee = new Set(e), E = 0, De = () => ++E, Oe = (e, t) => {
488
504
  }), _ = await u.runPromiseExit(h);
489
505
  if (d.isSuccess(_)) return _.value;
490
506
  throw l.squash(_.cause);
491
- }, Z = (e) => new Promise((t) => {
507
+ }, Xe = (e) => new Promise((t) => {
492
508
  let n = new _e.Socket(), r = (e) => {
493
509
  clearTimeout(i), n.destroy(), t(e);
494
510
  }, i = setTimeout(() => r(!1), e.timeoutMs ?? 750);
495
511
  n.once("connect", () => r(!0)), n.once("error", () => r(!1)), n.connect(e.port, e.host);
496
- }), Q = async (e, t, n, r) => {
512
+ }), Ze = async (e, t, n, r) => {
497
513
  if (typeof t != "string" || typeof n != "function" || typeof r != "function") throw TypeError(`describeIfAvailable('${e}', ...): expected (label, dependency, probe, suite) — got dependency: ${typeof t}, probe: ${typeof n}, suite: ${typeof r}. Three arguments means the suite body is being used as the probe: it would run at file scope, unconditionally, and the skip would silently do nothing.`);
498
514
  let i = !1;
499
515
  try {
@@ -502,20 +518,20 @@ var Ee = new Set(e), E = 0, De = () => ++E, Oe = (e, t) => {
502
518
  i = !1;
503
519
  }
504
520
  ve.skipIf(!i)(i ? e : `${e} [SKIPPED: needs ${t}]`, r);
505
- }, Je = async (e, t, n) => {
506
- await Q(e, `${t.name} at ${t.host}:${t.port}`, () => Z(t), n);
507
- }, Ye = async (e, t) => {
521
+ }, Qe = async (e, t, n) => {
522
+ await Ze(e, `${t.name} at ${t.host}:${t.port}`, () => Xe(t), n);
523
+ }, $e = async (e, t) => {
508
524
  try {
509
525
  let n = await t();
510
526
  n !== void 0 && n !== "" && console.log(`[engine] ${e}: ${n}`);
511
527
  } catch {}
512
- }, Xe = "test-tenant", $ = (e, t, n) => ({
528
+ }, et = "test-tenant", Q = (e, t, n) => ({
513
529
  type: e,
514
530
  id: t,
515
531
  tenantId: n.tenantId ?? "test-tenant",
516
532
  scopes: n.scopes ?? [],
517
533
  ...n.metadata === void 0 ? {} : { metadata: n.metadata }
518
- }), Ze = (e, t = {}) => $("user", e, t), Qe = (e, t = {}) => $("apiKey", e, t), $e = (e, t = {}) => $("serviceAccount", e, t), et = (e = null) => h(e), tt = (e = "job:test", t) => t === void 0 ? b(e) : b(e, t), nt = (e) => {
534
+ }), tt = (e, t = {}) => Q("user", e, t), nt = (e, t = {}) => Q("apiKey", e, t), rt = (e, t = {}) => Q("serviceAccount", e, t), it = (e = null) => h(e), at = (e = "job:test", t) => t === void 0 ? b(e) : b(e, t), ot = (e) => {
519
535
  let t = e.indexOf("?");
520
536
  return t === -1 ? {
521
537
  path: e,
@@ -524,23 +540,24 @@ var Ee = new Set(e), E = 0, De = () => ++E, Oe = (e, t) => {
524
540
  path: e.slice(0, t),
525
541
  query: e.slice(t + 1)
526
542
  };
527
- }, rt = (e, t) => {
543
+ }, st = (e, t) => {
528
544
  let n = e.split("/").filter((e) => e.length > 0), r = t.split("/").filter((e) => e.length > 0);
529
545
  return n.length === r.length && n.every((e, t) => e.startsWith(":") || e === r[t]);
530
- }, it = (e) => {
546
+ }, $ = (e) => {
531
547
  let t = {};
532
548
  for (let [n, r] of Object.entries(e)) t[n.toLowerCase()] = r;
533
549
  return t;
534
- }, at = (e) => e === void 0 ? /* @__PURE__ */ new Uint8Array() : new TextEncoder().encode(JSON.stringify(e)), ot = (e) => e === void 0 ? "" : typeof e == "string" ? e : new TextDecoder().decode(e), st = (e) => {
550
+ }, ct = (e) => e === void 0 ? /* @__PURE__ */ new Uint8Array() : new TextEncoder().encode(JSON.stringify(e)), lt = (e) => e === void 0 ? "" : typeof e == "string" ? e : new TextDecoder().decode(e), ut = (e) => {
535
551
  let { ctx: t } = e, n = ye((e.publicApi ?? []).map((e) => ({
536
552
  descriptor: e.descriptor,
537
- invoke: (n, r) => t.withSubject(r.subject, (t) => X(e.descriptor, e.handler, n, t))
553
+ invoke: (n, r) => t.withSubject(r.subject, (t) => Ye(e.descriptor, e.handler, n, t))
538
554
  }))), r = [...e.restRoutes ?? [], ...n], i = _(e.strategies ?? [], {
539
555
  ...e.anonymousTenantRequired === void 0 ? {} : { anonymousTenantRequired: e.anonymousTenantRequired },
540
556
  getStore: () => t.store
541
557
  }), a = (n, o) => {
542
558
  let s = {
543
559
  store: t.store,
560
+ buildContext: () => t,
544
561
  resolveSubject: n === void 0 ? async (e) => (await i({
545
562
  headers: e,
546
563
  clientId: -1
@@ -550,7 +567,7 @@ var Ee = new Set(e), E = 0, De = () => ++E, Oe = (e, t) => {
550
567
  pattern: r[t].path,
551
568
  route: e
552
569
  })), l = async (e, n, i = {}) => {
553
- let { path: a, query: s } = nt(n), l = c.filter((e) => rt(e.pattern, a)).map((e) => e.route);
570
+ let { path: a, query: s } = ot(n), l = c.filter((e) => st(e.pattern, a)).map((e) => e.route);
554
571
  if (l.length === 0) return {
555
572
  status: 404,
556
573
  headers: {},
@@ -566,15 +583,15 @@ var Ee = new Set(e), E = 0, De = () => ++E, Oe = (e, t) => {
566
583
  path: a,
567
584
  query: s,
568
585
  headers: {
569
- ...it(o),
570
- ...it(i.headers ?? {})
586
+ ...$(o),
587
+ ...$(i.headers ?? {})
571
588
  },
572
- rawBody: at(i.body),
589
+ rawBody: ct(i.body),
573
590
  store: t.store
574
591
  }, d = await be(l, u), f = {};
575
592
  for (let [e, t] of Object.entries(d.headers ?? {})) f[e.toLowerCase()] = t;
576
593
  d.contentType !== void 0 && (f["content-type"] = d.contentType);
577
- let p = ot(d.body), m = (d.contentType ?? "").includes("json");
594
+ let p = lt(d.body), m = (d.contentType ?? "").includes("json");
578
595
  return {
579
596
  status: d.status,
580
597
  headers: f,
@@ -608,7 +625,7 @@ var Ee = new Set(e), E = 0, De = () => ++E, Oe = (e, t) => {
608
625
  };
609
626
  };
610
627
  return a(void 0, {});
611
- }, ct = (e = {}) => {
628
+ }, dt = (e = {}) => {
612
629
  let t = e.id ?? "test", n = e.store ?? new Proxy({}, { get: (e, t) => {
613
630
  throw Error(`makeSubscribeContext: this subscriber reached ctx.store.${String(t)} and the test passed no store. Pass \`{ store: makeTestContext().store }\` so the subscriber and the code under test share one — an empty stand-in would let a read of the wrong table pass.`);
614
631
  } });
@@ -618,7 +635,7 @@ var Ee = new Set(e), E = 0, De = () => ++E, Oe = (e, t) => {
618
635
  store: n,
619
636
  ...e.publish === void 0 ? {} : { publish: e.publish }
620
637
  };
621
- }, lt = (e) => {
638
+ }, ft = (e) => {
622
639
  let t = /* @__PURE__ */ new Map();
623
640
  for (let n of e) {
624
641
  let e = t.get(n.stepName);
@@ -644,13 +661,13 @@ var Ee = new Set(e), E = 0, De = () => ++E, Oe = (e, t) => {
644
661
  });
645
662
  }
646
663
  return n.sort((e, t) => e._startedAt - t._startedAt), n.map(({ _startedAt: e, ...t }) => t);
647
- }, ut = (e) => {
664
+ }, pt = (e) => {
648
665
  let t = e.workflows ?? [], n = /* @__PURE__ */ new Map(), r = 0;
649
666
  return {
650
667
  start: async (i, a) => {
651
668
  let o = t.find((e) => e.workflow.name === i);
652
669
  if (o === void 0) throw Error(`makeWorkflowRunner: no workflow named '${i}' — pass it in makeWorkflowRunner({ ctx, workflows: [{ workflow, execute }] }).`);
653
- let s = `wfrun_test_${++r}`, c = Te(), l = o.workflow.toLayer(o.execute).pipe(f.provideMerge(we)), d = /* @__PURE__ */ new Date(), p = o.workflow.execute(a).pipe(u.locally(Ce, s), u.provide(c.layer), u.provide(l), u.either), m = await u.runPromise(p), h = lt(c.readSteps()), g = /* @__PURE__ */ new Date(), _;
670
+ let s = `wfrun_test_${++r}`, c = Te(), l = o.workflow.toLayer(o.execute).pipe(f.provideMerge(we)), d = /* @__PURE__ */ new Date(), p = o.workflow.execute(a).pipe(u.locally(Ce, s), u.provide(c.layer), u.provide(l), u.either), m = await u.runPromise(p), h = ft(c.readSteps()), g = /* @__PURE__ */ new Date(), _;
654
671
  if (m._tag === "Right") _ = {
655
672
  status: "succeeded",
656
673
  output: m.right,
@@ -690,6 +707,6 @@ var Ee = new Set(e), E = 0, De = () => ++E, Oe = (e, t) => {
690
707
  },
691
708
  inspect: async (e) => n.get(e) ?? null
692
709
  };
693
- }, dt = 1;
710
+ }, mt = 1;
694
711
  //#endregion
695
- export { N as MockClock, I as MockLLM, P as MockWebhooks, dt as TESTING_PRESET_VERSION, Xe as TEST_TENANT_ID, et as anonymous, Qe as apiKey, n as changeDelete, r as changeInsert, i as changeSoftDelete, a as changeUpdate, Me as defineFactory, Q as describeIfAvailable, Je as describeIfReachable, D as fixtureRow, Ie as frozenTime, X as invoke, Z as isTcpReachable, ct as makeSubscribeContext, st as makeTestApp, Ke as makeTestContext, ut as makeWorkflowRunner, Re as mockStore, De as nextSequence, ze as outboxNudgesOf, Ye as reportEngineVersion, G as resetAfterCommit, U as rpcInterceptorFor, K as runAfterCommit, q as runInStoreTransaction, $e as serviceAccount, W as serviceLayerFor, tt as system, F as testEventBus, Ze as user, Fe as withFrozenTime };
712
+ export { F as MockClock, R as MockLLM, I as MockWebhooks, mt as TESTING_PRESET_VERSION, et as TEST_TENANT_ID, it as anonymous, nt as apiKey, n as changeDelete, r as changeInsert, i as changeSoftDelete, a as changeUpdate, je as defineFactory, Ze as describeIfAvailable, Qe as describeIfReachable, k as fixtureRow, Fe as frozenTime, Ye as invoke, Xe as isTcpReachable, dt as makeSubscribeContext, ut as makeTestApp, qe as makeTestContext, pt as makeWorkflowRunner, Le as mockStore, O as nextSequence, ze as outboxNudgesOf, $e as reportEngineVersion, J as resetAfterCommit, K as rpcInterceptorFor, Y as runAfterCommit, X as runInStoreTransaction, rt as serviceAccount, q as serviceLayerFor, at as system, L as testEventBus, tt as user, Pe as withFrozenTime, Re as withRpcConnection };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/testing",
3
- "version": "0.65.0",
3
+ "version": "0.66.0",
4
4
  "description": "Test utilities for Voltro apps — deterministic clock, subject and row factories, a handler-level invoke and a request-level app harness, queued LLM responses, scoped subject/tenant runners, and a cross-dialect parity harness.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -43,15 +43,15 @@
43
43
  "node": ">=24.0.0"
44
44
  },
45
45
  "dependencies": {
46
- "@voltro/database": "0.65.0",
47
- "@voltro/env": "0.65.0",
48
- "@voltro/logger": "0.65.0",
49
- "@voltro/protocol": "0.65.0",
50
- "@voltro/runtime": "0.65.0",
51
- "@voltro/workflow": "0.65.0"
46
+ "@voltro/database": "0.66.0",
47
+ "@voltro/env": "0.66.0",
48
+ "@voltro/logger": "0.66.0",
49
+ "@voltro/protocol": "0.66.0",
50
+ "@voltro/runtime": "0.66.0",
51
+ "@voltro/workflow": "0.66.0"
52
52
  },
53
53
  "peerDependencies": {
54
- "@voltro/client": "0.65.0",
54
+ "@voltro/client": "0.66.0",
55
55
  "effect": "^3.22.0",
56
56
  "react": "^19.0.0",
57
57
  "@effect/sql": "^0.52.0"