@voltro/testing 0.33.0 → 0.35.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
@@ -1,14 +1,15 @@
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
- import { SubjectService as m, anonymousSubject as h, checkGuardsEffect as g, composeRpcInterceptors as _, eventRoute as v, tenantScopedSubject as ee } from "@voltro/protocol";
4
- import { EventBus as y, InMemoryDataStore as te, applyRowFilterToDescriptor as ne, clearSystemStoreHandle as b, getRowFilter as x, getSystemStoreHandle as S, makeAppAccess as C, makeDataLoader as w, makeEffectStoreLayer as T, makeOutboxFacade as re, makeSchemaRegistry as ie, publishEvent as ae, resolveRowFilterScopeFor as oe, runProvidedEffect as E, runWithDeadlockRetry as se, setSystemStoreHandle as D, wrapStoreWithMixinBehaviour as O } from "@voltro/runtime";
5
- import { installEnvSnapshot as ce } from "@voltro/env";
6
- import le from "node:net";
7
- import { describe as ue } from "vitest";
8
- import { createLogger as de } from "@voltro/logger";
9
- import { CurrentWorkflowRunId as fe, inMemoryWorkflowEngineLayer as k, makeInMemoryRecorder as A } from "@voltro/workflow";
3
+ import { SubjectService as m, anonymousSubject as h, checkGuardsEffect as g, composeAuthStrategies as _, composeRpcInterceptors as v, eventRoute as ee, systemSubject as y, tenantScopedSubject as te } from "@voltro/protocol";
4
+ import { EventBus as ne, InMemoryDataStore as re, NO_ROW_FILTER as b, applyRowFilterToDescriptor as ie, clearSystemStoreHandle as ae, getRowFilter as x, getSystemStoreHandle as oe, makeAppAccess as se, makeDataLoader as ce, makeEffectStoreLayer as le, makeOutboxFacade as ue, makeSchemaRegistry as de, publishEvent as fe, resolveRowFilterScopeFor as pe, runProvidedEffect as S, runWithDeadlockRetry as me, setSystemStoreHandle as C, wrapStoreWithMixinBehaviour as w } from "@voltro/runtime";
5
+ import { installEnvSnapshot as he } from "@voltro/env";
6
+ import ge from "node:net";
7
+ import { describe as _e } from "vitest";
8
+ import { collectPublicApiRoutes as ve, dispatchSharedPath as ye, restRoutesToHttpRoutes as be } from "@voltro/protocol/rest";
9
+ import { createLogger as xe } from "@voltro/logger";
10
+ import { CurrentWorkflowRunId as Se, inMemoryWorkflowEngineLayer as Ce, makeInMemoryRecorder as we } from "@voltro/workflow";
10
11
  //#region src/fixtureRow.ts
11
- var j = new Set(e), M = 0, N = (e, t) => {
12
+ var Te = new Set(e), T = 0, E = () => ++T, Ee = (e, t) => {
12
13
  if (t.oneOf && t.oneOf.length > 0) return t.oneOf[0];
13
14
  if (t.enumValues && t.enumValues.length > 0) return t.enumValues[0];
14
15
  let n = t.unique === !0;
@@ -16,27 +17,95 @@ var j = new Set(e), M = 0, N = (e, t) => {
16
17
  case "text":
17
18
  case "reference":
18
19
  case "id":
19
- case "enum": return n ? `${e}-${++M}` : e;
20
+ case "enum": return n ? `${e}-${++T}` : e;
20
21
  case "integer":
21
- case "real": return n ? ++M : 0;
22
+ case "real": return n ? ++T : 0;
22
23
  case "decimal":
23
- case "bigint": return n ? String(++M) : "0";
24
+ case "bigint": return n ? String(++T) : "0";
24
25
  case "boolean": return !1;
25
26
  case "timestamp":
26
27
  case "date": return /* @__PURE__ */ new Date(0);
27
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}: … }).`);
28
29
  }
29
- }, P = (e, t = {}) => {
30
+ }, D = (e, t = {}) => {
30
31
  let n = {};
31
- for (let r of s(e, t)) j.has(r) || (n[r] = N(r, e.fields[r]));
32
+ for (let r of s(e, t)) Te.has(r) || (n[r] = Ee(r, e.fields[r]));
32
33
  return {
33
34
  ...n,
34
35
  ...t
35
36
  };
36
- }, F = class {
37
+ }, De = (e) => {
38
+ let t = {};
39
+ for (let [n, r] of Object.entries(e)) t[n] = typeof r == "function" ? r(E()) : r;
40
+ return t;
41
+ }, Oe = (e) => {
42
+ for (let [t, n] of Object.entries(e.fields)) if (n.type === "id") return t;
43
+ return "id";
44
+ }, ke = async (e, t, n, r, i) => {
45
+ let a = {};
46
+ for (let o of s(t, n)) {
47
+ let n = t.fields[o];
48
+ if (n === void 0 || n.type !== "reference" || n.references === void 0) continue;
49
+ let s = n.references();
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
+ let c = r[o];
52
+ a[o] = (c === void 0 ? await O(e, s, {}, {}, [...i, s.tableName]) : await c.create(e, {}))[Oe(s)];
53
+ }
54
+ return a;
55
+ }, O = async (e, t, n, r, i) => {
56
+ let a = {
57
+ ...await ke(e, t, n, r, i),
58
+ ...n
59
+ };
60
+ return e.insert(t.tableName, D(t, a));
61
+ }, k = (e, t) => typeof e == "function" ? e(t) : e ?? {}, Ae = (e, t = {}) => {
62
+ let n = t.traits ?? {}, r = t.associations ?? {}, i = (t) => {
63
+ let a = (e = {}) => ({
64
+ ...De(t),
65
+ ...e
66
+ }), o = (t = {}) => D(e, a(t));
67
+ return {
68
+ table: e,
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]),
72
+ createList: async (t, n, i) => {
73
+ let o = [];
74
+ for (let s = 0; s < n; s++) o.push(await O(t, e, a(k(i, s)), r, [e.tableName]));
75
+ return o;
76
+ },
77
+ with: (...r) => {
78
+ let a = t;
79
+ for (let t of r) {
80
+ let r = n[t];
81
+ if (r === void 0) throw Error(`defineFactory: '${e.tableName}' has no trait '${t}'. Declared: ${Object.keys(n).join(", ") || "(none)"}.`);
82
+ a = {
83
+ ...a,
84
+ ...r
85
+ };
86
+ }
87
+ return i(a);
88
+ },
89
+ extend: (e) => i({
90
+ ...t,
91
+ ...e
92
+ })
93
+ };
94
+ };
95
+ return i(t.defaults ?? {});
96
+ }, A = Symbol.for("voltro/testing/mockClock.installed"), j = () => globalThis, je = (e, t) => new Proxy(e, {
97
+ construct: (e, n, r) => Reflect.construct(e, n.length === 0 ? [t()] : n, r),
98
+ apply: () => new e(t()).toString(),
99
+ get: (e, n, r) => n === "now" ? () => t() : Reflect.get(e, n, r)
100
+ }), M = (e) => {
101
+ if (typeof e == "number") return e;
102
+ let t = e instanceof Date ? e.getTime() : Date.parse(e);
103
+ if (Number.isNaN(t)) throw Error(`mockClock: cannot read an instant from '${String(e)}'`);
104
+ return t;
105
+ }, N = class {
37
106
  currentMs;
38
- constructor(e = /* @__PURE__ */ new Date("2026-01-01T00:00:00Z")) {
39
- this.currentMs = typeof e == "number" ? e : e.getTime();
107
+ constructor(e = "2026-01-01T00:00:00Z") {
108
+ this.currentMs = M(e);
40
109
  }
41
110
  now() {
42
111
  return this.currentMs;
@@ -45,9 +114,47 @@ var j = new Set(e), M = 0, N = (e, t) => {
45
114
  return new Date(this.currentMs);
46
115
  }
47
116
  advance(e) {
48
- this.currentMs += typeof e == "number" ? e : I(e);
117
+ this.currentMs += typeof e == "number" ? e : Fe(e);
118
+ }
119
+ set(e) {
120
+ this.currentMs = M(e);
121
+ }
122
+ get installed() {
123
+ return j()[A]?.clock === this;
49
124
  }
50
- }, I = (e) => {
125
+ install() {
126
+ let e = j(), t = e[A];
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
+ let n = globalThis.Date;
129
+ return e[A] = {
130
+ realDate: n,
131
+ clock: this
132
+ }, globalThis.Date = je(n, () => this.currentMs), () => {
133
+ this.uninstall();
134
+ };
135
+ }
136
+ uninstall() {
137
+ let e = j(), t = e[A];
138
+ t === void 0 || t.clock !== this || (globalThis.Date = t.realDate, delete e[A]);
139
+ }
140
+ }, Me = (e) => typeof e == "object" && !!e && typeof e.then == "function", Ne = (e, t) => {
141
+ let n = e instanceof N ? e : new N(e);
142
+ n.install();
143
+ let r = !1;
144
+ try {
145
+ let e = t(n);
146
+ return Me(e) ? (r = !0, e.then((e) => (n.uninstall(), e), (e) => {
147
+ throw n.uninstall(), e;
148
+ })) : e;
149
+ } finally {
150
+ r || n.uninstall();
151
+ }
152
+ }, Pe = (e) => u.acquireRelease(u.sync(() => {
153
+ let t = e instanceof N ? e : new N(e);
154
+ return t.install(), t;
155
+ }), (e) => u.sync(() => {
156
+ e.uninstall();
157
+ })), Fe = (e) => {
51
158
  let t = /^\s*(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)\s*$/.exec(e);
52
159
  if (!t) throw Error(`mockClock: cannot parse duration '${e}'`);
53
160
  let n = Number(t[1]), r = t[2];
@@ -59,21 +166,7 @@ var j = new Set(e), M = 0, N = (e, t) => {
59
166
  case "d": return n * 864e5;
60
167
  default: throw Error(`mockClock: unknown unit '${r}'`);
61
168
  }
62
- }, L = class {
63
- sent = [];
64
- send(e) {
65
- this.sent.push({
66
- ...e,
67
- sentAt: /* @__PURE__ */ new Date()
68
- });
69
- }
70
- lastTo(e) {
71
- for (let t = this.sent.length - 1; t >= 0; t--) if (this.sent[t].to === e) return this.sent[t];
72
- }
73
- clear() {
74
- this.sent.length = 0;
75
- }
76
- }, R = class {
169
+ }, P = class {
77
170
  emitted = [];
78
171
  async emit(e, t) {
79
172
  let n = typeof e == "string" ? e : e.id ?? e.event ?? "(unnamed event)";
@@ -92,7 +185,7 @@ var j = new Set(e), M = 0, N = (e, t) => {
92
185
  clear() {
93
186
  this.emitted.length = 0;
94
187
  }
95
- }, z = class {
188
+ }, F = class {
96
189
  queue;
97
190
  calls = [];
98
191
  constructor(e) {
@@ -107,29 +200,32 @@ var j = new Set(e), M = 0, N = (e, t) => {
107
200
  remaining() {
108
201
  return this.queue.length;
109
202
  }
110
- }, B = (e) => e, V = "00000000000000000000000000000000", H = /* @__PURE__ */ new WeakMap(), U = /* @__PURE__ */ new WeakMap(), W = /* @__PURE__ */ new WeakMap(), G = /* @__PURE__ */ new WeakMap(), K = (e, t) => {
111
- let n = G.get(e) ?? [], r = t === "mutation" ? "interceptMutation" : t === "query" ? "interceptQuery" : "interceptAction", i = [];
203
+ }, Ie = (e) => e, I = "00000000000000000000000000000000", L = /* @__PURE__ */ new WeakMap(), R = /* @__PURE__ */ new WeakMap(), z = /* @__PURE__ */ new WeakMap(), B = /* @__PURE__ */ new WeakMap(), V = /* @__PURE__ */ new WeakMap(), H = (e, t) => {
204
+ let n = B.get(e) ?? [], r = t === "mutation" ? "interceptMutation" : t === "query" ? "interceptQuery" : "interceptAction", i = [];
112
205
  for (let e of n) {
113
206
  let t = e[r];
114
207
  typeof t == "function" && i.push(t);
115
208
  }
116
- return _(i);
117
- }, q = (e) => {
118
- let t = U.get(e);
209
+ return v(i);
210
+ }, U = (e) => {
211
+ let t = (B.get(e) ?? []).map((e) => e.services).filter((e) => e !== void 0), n = V.get(e) ?? [], r = [...t, ...n];
212
+ if (r.length !== 0) return f.mergeAll(...r);
213
+ }, W = (e) => {
214
+ let t = R.get(e);
119
215
  t !== void 0 && (t.length = 0);
120
- }, J = async (e) => {
121
- let t = U.get(e);
216
+ }, G = async (e) => {
217
+ let t = R.get(e);
122
218
  if (t === void 0 || t.length === 0) return;
123
219
  let n = [...t];
124
220
  t.length = 0;
125
221
  for (let e of n) await e();
126
- }, pe = (e) => W.get(e) ?? [], Y = async (e, t) => {
127
- let n = H.get(e);
222
+ }, Le = (e) => z.get(e) ?? [], K = async (e, t) => {
223
+ let n = L.get(e);
128
224
  return n === void 0 ? e.store.transactional(async (n) => t({
129
225
  ...e,
130
226
  store: n
131
227
  })) : n(t);
132
- }, me = (e) => {
228
+ }, Re = (e) => {
133
229
  let t = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map(), r = (n) => {
134
230
  let r = t.get(n);
135
231
  return r === void 0 ? !1 : r.expiresAt !== null && r.expiresAt <= e() ? (t.delete(n), !1) : !0;
@@ -168,7 +264,7 @@ var j = new Set(e), M = 0, N = (e, t) => {
168
264
  return i(e, o, n.ttlMs, n.tags), o;
169
265
  }
170
266
  };
171
- }, he = (e) => {
267
+ }, ze = (e) => {
172
268
  let t = /* @__PURE__ */ new Map(), n = (n) => {
173
269
  let r = t.get(n);
174
270
  return r === void 0 ? !1 : r.expiresAt !== null && r.expiresAt <= e() ? (t.delete(n), !1) : !0;
@@ -196,96 +292,102 @@ var j = new Set(e), M = 0, N = (e, t) => {
196
292
  t.clear();
197
293
  }
198
294
  };
199
- }, ge = async (e, t) => {
200
- let n = S();
201
- D(e);
295
+ }, Be = async (e, t) => {
296
+ let n = oe();
297
+ C(e);
202
298
  try {
203
299
  return await t();
204
300
  } finally {
205
- n === void 0 ? b() : D(n);
301
+ n === void 0 ? ae() : C(n);
206
302
  }
207
- }, _e = (e, t, n) => {
208
- let r = ve, i;
303
+ }, q = /* @__PURE__ */ new Set(), Ve = (e) => {
304
+ q.has(e) || (q.add(e), console.warn(e));
305
+ }, He = (e, t, n) => {
306
+ let r = Ue, i;
209
307
  return () => {
210
308
  let a = t ?? x();
211
- return (i === void 0 || a !== r) && (r = a, i = ge(n, () => E(oe(a, e, (e) => {
309
+ return t !== void 0 && x() !== void 0 && t !== x() && Ve("[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 = Be(n, () => S(pe(a, e, (e) => {
212
310
  console.error("[voltro:testing] row filter failed to load — reads refused for this subject", e);
213
311
  })))), i;
214
312
  };
215
- }, ve = Symbol("unresolved"), X = (e, t) => new Proxy(e, { get: (e, n) => {
216
- if (n === "query") return async (n) => e.query(ne(await t(), n));
313
+ }, Ue = Symbol("unresolved"), J = (e, t) => new Proxy(e, { get: (e, n) => {
314
+ if (n === "query") return async (n) => e.query(ie(await t(), n));
217
315
  let r = Reflect.get(e, n, e);
218
316
  return typeof r == "function" ? r.bind(e) : r;
219
- } }), ye = (e = {}) => {
220
- if (ce({
317
+ } }), We = (e = {}) => {
318
+ if (he({
221
319
  ...process.env,
222
320
  ...e.env ?? {}
223
321
  }), e.relations !== void 0) {
224
322
  o();
225
323
  for (let t of e.relations) c(t);
226
324
  }
227
- let n = ie(e.tables ?? t()), r = new te(e.store ?? {}), i = {
325
+ let n = de(e.tables ?? t()), r = new re(e.store ?? {}), i = {
228
326
  dataStore: r,
229
327
  schemaRegistry: n
230
328
  };
231
- D(i);
232
- let a = new F(e.clockStart), s = new L(), l = new R(), u = new z(e.llmResponses ?? []), d = me(() => a.now()), f = he(() => a.now()), p = (t, o = r, c) => {
233
- let m = {
329
+ C(i);
330
+ let a = new N(e.clockStart), s = new P(), l = new F(e.llmResponses ?? []), u = Re(() => a.now()), d = ze(() => a.now()), f = (t, o = r, c) => {
331
+ let p = {
234
332
  subject: t,
235
- traceId: V
236
- }, h = c?.queued ?? [], g = c?.nudges ?? [], _ = w({ store: o }), v = _e(t, e.rowFilter, i), y = {
333
+ traceId: I
334
+ }, m = c?.queued ?? [], h = c?.nudges ?? [], g = ce({ store: o }), _ = He(t, e.rowFilter, i), v = {
237
335
  clock: a,
238
- email: s,
239
- webhooks: l,
240
- llm: u,
336
+ webhooks: s,
337
+ llm: l,
241
338
  ...e.ai === void 0 ? {} : { ai: e.ai },
242
- request: m,
243
- access: C(t),
244
- cache: d,
245
- kv: f,
246
- store: O(X(o, v), {
339
+ request: p,
340
+ access: se(t),
341
+ cache: u,
342
+ kv: d,
343
+ store: w(J(o, _), {
247
344
  subject: t,
248
- schemaRegistry: n
345
+ schemaRegistry: n,
346
+ rowFilter: b
249
347
  }),
250
- storeForTenant: (e) => O(X(o, v), {
251
- subject: ee(t, e),
252
- schemaRegistry: n
348
+ storeForTenant: (e) => w(J(o, _), {
349
+ subject: te(t, e),
350
+ schemaRegistry: n,
351
+ rowFilter: b
253
352
  }),
254
- outbox: re({
353
+ outbox: ue({
255
354
  store: o,
256
355
  subject: t,
257
- traceId: V,
356
+ traceId: I,
258
357
  nudge: () => {
259
- g.push(V);
358
+ h.push(I);
260
359
  },
261
360
  afterCommit: (e) => {
262
- h.push(e);
361
+ m.push(e);
263
362
  }
264
363
  }),
265
- load: _.load,
266
- loadMany: _.loadMany,
267
- withSubject: (e, t) => Promise.resolve(t(p(e, o, {
268
- queued: h,
269
- nudges: g
364
+ load: g.load,
365
+ loadMany: g.loadMany,
366
+ withSubject: (e, t) => Promise.resolve(t(f(e, o, {
367
+ queued: m,
368
+ nudges: h
270
369
  }))),
271
- withTenant: (e, n) => Promise.resolve(n(p({
370
+ withTenant: (e, n) => Promise.resolve(n(f({
272
371
  ...t,
273
372
  tenantId: e
274
373
  }, o, {
275
- queued: h,
276
- nudges: g
374
+ queued: m,
375
+ nudges: h
277
376
  })))
278
377
  };
279
- return U.set(y, h), W.set(y, g), G.set(y, e.plugins ?? []), H.set(y, (e) => o.transactional((n) => e(p(t, n, {
280
- queued: h,
281
- nudges: g
282
- })))), y;
378
+ return R.set(v, m), z.set(v, h), B.set(v, e.plugins ?? []), V.set(v, e.layers ?? []), L.set(v, (e) => o.transactional((n) => e(f(t, n, {
379
+ queued: m,
380
+ nudges: h
381
+ })))), v;
283
382
  };
284
- return p(e.subject ?? h(null));
285
- }, be = (e, t) => e.pipe(u.provide(T(t.store)), u.provideService(m, t.request.subject)), xe = async (e, t, n, r) => {
383
+ return f(e.subject ?? h(null));
384
+ }, Ge = (e, t) => {
385
+ let n = U(t);
386
+ return (n === void 0 ? e : u.provide(e, n)).pipe(u.provide(le(t.store)), u.provideService(m, t.request.subject));
387
+ }, Y = async (e, t, n, r) => {
286
388
  let i = e.input, a = await p.decodeUnknownPromise(i)(n), o = e.kind, s = async (e) => {
287
389
  let n = t(a, e);
288
- return u.isEffect(n) ? E(be(n, e)) : n;
390
+ return u.isEffect(n) ? S(Ge(n, e)) : n;
289
391
  }, c = async () => {
290
392
  let t = e.guards;
291
393
  if (t !== void 0 && t.length > 0) {
@@ -293,9 +395,9 @@ var j = new Set(e), M = 0, N = (e, t) => {
293
395
  if (e !== null) throw e;
294
396
  }
295
397
  if (o !== "mutation") return s(r);
296
- let n = await se(async () => (q(r), Y(r, async (e) => s(e))), { delay: () => Promise.resolve() });
297
- return await J(r), n;
298
- }, f = o === "mutation" || o === "query" || o === "action" ? o : void 0, m = f === void 0 ? void 0 : K(r, f);
398
+ let n = await me(async () => (W(r), K(r, async (e) => s(e))), { delay: () => Promise.resolve() });
399
+ return await G(r), n;
400
+ }, f = o === "mutation" || o === "query" || o === "action" ? o : void 0, m = f === void 0 ? void 0 : H(r, f);
299
401
  if (m === void 0 || f === void 0) return await c();
300
402
  let h = m(u.tryPromise({
301
403
  try: () => c(),
@@ -310,12 +412,12 @@ var j = new Set(e), M = 0, N = (e, t) => {
310
412
  }), _ = await u.runPromiseExit(h);
311
413
  if (d.isSuccess(_)) return _.value;
312
414
  throw l.squash(_.cause);
313
- }, Z = (e) => new Promise((t) => {
314
- let n = new le.Socket(), r = (e) => {
415
+ }, X = (e) => new Promise((t) => {
416
+ let n = new ge.Socket(), r = (e) => {
315
417
  clearTimeout(i), n.destroy(), t(e);
316
418
  }, i = setTimeout(() => r(!1), e.timeoutMs ?? 750);
317
419
  n.once("connect", () => r(!0)), n.once("error", () => r(!1)), n.connect(e.port, e.host);
318
- }), Q = async (e, t, n, r) => {
420
+ }), Z = async (e, t, n, r) => {
319
421
  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.`);
320
422
  let i = !1;
321
423
  try {
@@ -323,17 +425,115 @@ var j = new Set(e), M = 0, N = (e, t) => {
323
425
  } catch {
324
426
  i = !1;
325
427
  }
326
- ue.skipIf(!i)(i ? e : `${e} [SKIPPED: needs ${t}]`, r);
327
- }, Se = async (e, t, n) => {
328
- await Q(e, `${t.name} at ${t.host}:${t.port}`, () => Z(t), n);
329
- }, Ce = (e = {}) => {
330
- let t = e.tenantId ?? null, n = new y({
428
+ _e.skipIf(!i)(i ? e : `${e} [SKIPPED: needs ${t}]`, r);
429
+ }, Ke = async (e, t, n) => {
430
+ await Z(e, `${t.name} at ${t.host}:${t.port}`, () => X(t), n);
431
+ }, qe = "test-tenant", Q = (e, t, n) => ({
432
+ type: e,
433
+ id: t,
434
+ tenantId: n.tenantId ?? "test-tenant",
435
+ scopes: n.scopes ?? [],
436
+ ...n.metadata === void 0 ? {} : { metadata: n.metadata }
437
+ }), Je = (e, t = {}) => Q("user", e, t), Ye = (e, t = {}) => Q("apiKey", e, t), Xe = (e, t = {}) => Q("serviceAccount", e, t), Ze = (e = null) => h(e), Qe = (e = "job:test", t) => t === void 0 ? y(e) : y(e, t), $e = (e) => {
438
+ let t = e.indexOf("?");
439
+ return t === -1 ? {
440
+ path: e,
441
+ query: ""
442
+ } : {
443
+ path: e.slice(0, t),
444
+ query: e.slice(t + 1)
445
+ };
446
+ }, et = (e, t) => {
447
+ let n = e.split("/").filter((e) => e.length > 0), r = t.split("/").filter((e) => e.length > 0);
448
+ return n.length === r.length && n.every((e, t) => e.startsWith(":") || e === r[t]);
449
+ }, $ = (e) => {
450
+ let t = {};
451
+ for (let [n, r] of Object.entries(e)) t[n.toLowerCase()] = r;
452
+ return t;
453
+ }, tt = (e) => e === void 0 ? /* @__PURE__ */ new Uint8Array() : new TextEncoder().encode(JSON.stringify(e)), nt = (e) => e === void 0 ? "" : typeof e == "string" ? e : new TextDecoder().decode(e), rt = (e) => {
454
+ let { ctx: t } = e, n = ve((e.publicApi ?? []).map((e) => ({
455
+ descriptor: e.descriptor,
456
+ invoke: (n, r) => t.withSubject(r.subject, (t) => Y(e.descriptor, e.handler, n, t))
457
+ }))), r = [...e.restRoutes ?? [], ...n], i = _(e.strategies ?? [], {
458
+ ...e.anonymousTenantRequired === void 0 ? {} : { anonymousTenantRequired: e.anonymousTenantRequired },
459
+ getStore: () => t.store
460
+ }), a = (n, o) => {
461
+ let s = {
462
+ store: t.store,
463
+ resolveSubject: n === void 0 ? async (e) => (await i({
464
+ headers: e,
465
+ clientId: -1
466
+ })).subject : () => n,
467
+ ...e.idempotency === void 0 ? {} : { idempotency: e.idempotency }
468
+ }, c = be(r, s).map((e, t) => ({
469
+ pattern: r[t].path,
470
+ route: e
471
+ })), l = async (e, n, i = {}) => {
472
+ let { path: a, query: s } = $e(n), l = c.filter((e) => et(e.pattern, a)).map((e) => e.route);
473
+ if (l.length === 0) return {
474
+ status: 404,
475
+ headers: {},
476
+ text: "",
477
+ body: {
478
+ error: "No route mounted for this path",
479
+ mounted: r.map((e) => e.path)
480
+ },
481
+ stream: !1
482
+ };
483
+ let u = await ye(l, {
484
+ method: e.toUpperCase(),
485
+ path: a,
486
+ query: s,
487
+ headers: {
488
+ ...$(o),
489
+ ...$(i.headers ?? {})
490
+ },
491
+ rawBody: tt(i.body),
492
+ store: t.store
493
+ }), d = $(u.headers ?? {});
494
+ u.contentType !== void 0 && (d["content-type"] = u.contentType);
495
+ let f = nt(u.body), p = (u.contentType ?? "").includes("json");
496
+ return {
497
+ status: u.status,
498
+ headers: d,
499
+ text: f,
500
+ body: p && f !== "" ? JSON.parse(f) : void 0,
501
+ stream: u.stream !== void 0
502
+ };
503
+ };
504
+ return {
505
+ actingAs: (e) => a(e, o),
506
+ withHeaders: (e) => a(n, {
507
+ ...o,
508
+ ...e
509
+ }),
510
+ get: (e, t) => l("GET", e, t ?? {}),
511
+ delete: (e, t) => l("DELETE", e, t ?? {}),
512
+ post: (e, t, n) => l("POST", e, {
513
+ ...n ?? {},
514
+ body: t
515
+ }),
516
+ put: (e, t, n) => l("PUT", e, {
517
+ ...n ?? {},
518
+ body: t
519
+ }),
520
+ patch: (e, t, n) => l("PATCH", e, {
521
+ ...n ?? {},
522
+ body: t
523
+ }),
524
+ request: (e, t, n) => l(e, t, n ?? {}),
525
+ paths: r.map((e) => e.path)
526
+ };
527
+ };
528
+ return a(void 0, {});
529
+ }, it = (e = {}) => {
530
+ let t = e.tenantId ?? null, n = new ne({
331
531
  origin: e.origin ?? "test",
332
532
  ...e.ringSize === void 0 ? {} : { ringSize: e.ringSize }
333
533
  });
334
534
  return {
335
535
  publish: async (e, r, i) => {
336
- let a = await u.runPromise(u.either(ae({
536
+ let a = await u.runPromise(u.either(fe({
337
537
  bus: n,
338
538
  tenantId: t
339
539
  }, e, r, i)));
@@ -382,7 +582,7 @@ var j = new Set(e), M = 0, N = (e, t) => {
382
582
  },
383
583
  skipSerials: (e, r, i, a) => {
384
584
  let o = {
385
- route: v(a?.tenantId ?? t, e.name, r),
585
+ route: ee(a?.tenantId ?? t, e.name, r),
386
586
  event: e.name,
387
587
  origin: "test-gap",
388
588
  n: i + 1,
@@ -396,17 +596,17 @@ var j = new Set(e), M = 0, N = (e, t) => {
396
596
  n.clear();
397
597
  }
398
598
  };
399
- }, $ = (e = {}) => {
599
+ }, at = (e = {}) => {
400
600
  let t = e.id ?? "test", n = e.store ?? new Proxy({}, { get: (e, t) => {
401
601
  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.`);
402
602
  } });
403
603
  return {
404
604
  id: t,
405
- log: e.log ?? de({ scope: `subscribe:${t}` }),
605
+ log: e.log ?? xe({ scope: `subscribe:${t}` }),
406
606
  store: n,
407
607
  ...e.publish === void 0 ? {} : { publish: e.publish }
408
608
  };
409
- }, we = (e) => {
609
+ }, ot = (e) => {
410
610
  let t = /* @__PURE__ */ new Map();
411
611
  for (let n of e) {
412
612
  let e = t.get(n.stepName);
@@ -432,13 +632,13 @@ var j = new Set(e), M = 0, N = (e, t) => {
432
632
  });
433
633
  }
434
634
  return n.sort((e, t) => e._startedAt - t._startedAt), n.map(({ _startedAt: e, ...t }) => t);
435
- }, Te = (e) => {
635
+ }, st = (e) => {
436
636
  let t = e.workflows ?? [], n = /* @__PURE__ */ new Map(), r = 0;
437
637
  return {
438
638
  start: async (i, a) => {
439
639
  let o = t.find((e) => e.workflow.name === i);
440
640
  if (o === void 0) throw Error(`makeWorkflowRunner: no workflow named '${i}' — pass it in makeWorkflowRunner({ ctx, workflows: [{ workflow, execute }] }).`);
441
- let s = `wfrun_test_${++r}`, c = A(), l = o.workflow.toLayer(o.execute).pipe(f.provideMerge(k)), d = /* @__PURE__ */ new Date(), p = o.workflow.execute(a).pipe(u.locally(fe, s), u.provide(c.layer), u.provide(l), u.either), m = await u.runPromise(p), h = we(c.readSteps()), g = /* @__PURE__ */ new Date(), _;
641
+ let s = `wfrun_test_${++r}`, c = we(), l = o.workflow.toLayer(o.execute).pipe(f.provideMerge(Ce)), d = /* @__PURE__ */ new Date(), p = o.workflow.execute(a).pipe(u.locally(Se, s), u.provide(c.layer), u.provide(l), u.either), m = await u.runPromise(p), h = ot(c.readSteps()), g = /* @__PURE__ */ new Date(), _;
442
642
  if (m._tag === "Right") _ = {
443
643
  status: "succeeded",
444
644
  output: m.right,
@@ -478,6 +678,6 @@ var j = new Set(e), M = 0, N = (e, t) => {
478
678
  },
479
679
  inspect: async (e) => n.get(e) ?? null
480
680
  };
481
- }, Ee = 1;
681
+ }, ct = 1;
482
682
  //#endregion
483
- export { F as MockClock, L as MockEmail, z as MockLLM, R as MockWebhooks, Ee as TESTING_PRESET_VERSION, n as changeDelete, r as changeInsert, i as changeSoftDelete, a as changeUpdate, Q as describeIfAvailable, Se as describeIfReachable, P as fixtureRow, xe as invoke, Z as isTcpReachable, $ as makeSubscribeContext, ye as makeTestContext, Te as makeWorkflowRunner, B as mockStore, pe as outboxNudgesOf, q as resetAfterCommit, K as rpcInterceptorFor, J as runAfterCommit, Y as runInStoreTransaction, Ce as testEventBus };
683
+ export { N as MockClock, F as MockLLM, P as MockWebhooks, ct as TESTING_PRESET_VERSION, qe as TEST_TENANT_ID, Ze as anonymous, Ye as apiKey, n as changeDelete, r as changeInsert, i as changeSoftDelete, a as changeUpdate, Ae as defineFactory, Z as describeIfAvailable, Ke as describeIfReachable, D as fixtureRow, Pe as frozenTime, Y as invoke, X as isTcpReachable, at as makeSubscribeContext, rt as makeTestApp, We as makeTestContext, st as makeWorkflowRunner, Ie as mockStore, E as nextSequence, Le as outboxNudgesOf, W as resetAfterCommit, H as rpcInterceptorFor, G as runAfterCommit, K as runInStoreTransaction, Xe as serviceAccount, U as serviceLayerFor, Qe as system, it as testEventBus, Je as user, Ne as withFrozenTime };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@voltro/testing",
3
- "version": "0.33.0",
4
- "description": "Test utilities for Voltro apps — deterministic clock, captured emails, queued LLM responses, scoped subject/tenant runners, and a cross-dialect parity harness.",
3
+ "version": "0.35.0",
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",
7
7
  "typescript",
@@ -43,15 +43,15 @@
43
43
  "node": ">=24.0.0"
44
44
  },
45
45
  "dependencies": {
46
- "@voltro/database": "0.33.0",
47
- "@voltro/env": "0.33.0",
48
- "@voltro/logger": "0.33.0",
49
- "@voltro/protocol": "0.33.0",
50
- "@voltro/runtime": "0.33.0",
51
- "@voltro/workflow": "0.33.0"
46
+ "@voltro/database": "0.35.0",
47
+ "@voltro/env": "0.35.0",
48
+ "@voltro/logger": "0.35.0",
49
+ "@voltro/protocol": "0.35.0",
50
+ "@voltro/runtime": "0.35.0",
51
+ "@voltro/workflow": "0.35.0"
52
52
  },
53
53
  "peerDependencies": {
54
- "@voltro/client": "0.33.0",
54
+ "@voltro/client": "0.35.0",
55
55
  "effect": "^3.22.0",
56
56
  "react": "^19.0.0",
57
57
  "@effect/sql": "^0.52.0"