@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/CHANGELOG.md +1968 -0
- package/README.md +1 -1
- package/dist/client.d.ts +49 -2
- package/dist/client.js +55 -45
- package/dist/dialect.js +192 -154
- package/dist/index.d.ts +378 -21
- package/dist/index.js +313 -113
- package/package.json +9 -9
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,
|
|
4
|
-
import { EventBus as
|
|
5
|
-
import { installEnvSnapshot as
|
|
6
|
-
import
|
|
7
|
-
import { describe as
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
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
|
|
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}-${++
|
|
20
|
+
case "enum": return n ? `${e}-${++T}` : e;
|
|
20
21
|
case "integer":
|
|
21
|
-
case "real": return n ? ++
|
|
22
|
+
case "real": return n ? ++T : 0;
|
|
22
23
|
case "decimal":
|
|
23
|
-
case "bigint": return n ? String(++
|
|
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
|
-
},
|
|
30
|
+
}, D = (e, t = {}) => {
|
|
30
31
|
let n = {};
|
|
31
|
-
for (let r of s(e, t))
|
|
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
|
-
},
|
|
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 =
|
|
39
|
-
this.currentMs =
|
|
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 :
|
|
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
|
-
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
111
|
-
let n =
|
|
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
|
|
117
|
-
},
|
|
118
|
-
let t =
|
|
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
|
-
},
|
|
121
|
-
let t =
|
|
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
|
-
},
|
|
127
|
-
let n =
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
200
|
-
let n =
|
|
201
|
-
|
|
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 ?
|
|
301
|
+
n === void 0 ? ae() : C(n);
|
|
206
302
|
}
|
|
207
|
-
},
|
|
208
|
-
|
|
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 =
|
|
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
|
-
},
|
|
216
|
-
if (n === "query") return async (n) => e.query(
|
|
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
|
-
} }),
|
|
220
|
-
if (
|
|
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 =
|
|
325
|
+
let n = de(e.tables ?? t()), r = new re(e.store ?? {}), i = {
|
|
228
326
|
dataStore: r,
|
|
229
327
|
schemaRegistry: n
|
|
230
328
|
};
|
|
231
|
-
|
|
232
|
-
let a = new
|
|
233
|
-
let
|
|
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:
|
|
236
|
-
},
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
llm: u,
|
|
336
|
+
webhooks: s,
|
|
337
|
+
llm: l,
|
|
241
338
|
...e.ai === void 0 ? {} : { ai: e.ai },
|
|
242
|
-
request:
|
|
243
|
-
access:
|
|
244
|
-
cache:
|
|
245
|
-
kv:
|
|
246
|
-
store:
|
|
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) =>
|
|
251
|
-
subject:
|
|
252
|
-
schemaRegistry: n
|
|
348
|
+
storeForTenant: (e) => w(J(o, _), {
|
|
349
|
+
subject: te(t, e),
|
|
350
|
+
schemaRegistry: n,
|
|
351
|
+
rowFilter: b
|
|
253
352
|
}),
|
|
254
|
-
outbox:
|
|
353
|
+
outbox: ue({
|
|
255
354
|
store: o,
|
|
256
355
|
subject: t,
|
|
257
|
-
traceId:
|
|
356
|
+
traceId: I,
|
|
258
357
|
nudge: () => {
|
|
259
|
-
|
|
358
|
+
h.push(I);
|
|
260
359
|
},
|
|
261
360
|
afterCommit: (e) => {
|
|
262
|
-
|
|
361
|
+
m.push(e);
|
|
263
362
|
}
|
|
264
363
|
}),
|
|
265
|
-
load:
|
|
266
|
-
loadMany:
|
|
267
|
-
withSubject: (e, t) => Promise.resolve(t(
|
|
268
|
-
queued:
|
|
269
|
-
nudges:
|
|
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(
|
|
370
|
+
withTenant: (e, n) => Promise.resolve(n(f({
|
|
272
371
|
...t,
|
|
273
372
|
tenantId: e
|
|
274
373
|
}, o, {
|
|
275
|
-
queued:
|
|
276
|
-
nudges:
|
|
374
|
+
queued: m,
|
|
375
|
+
nudges: h
|
|
277
376
|
})))
|
|
278
377
|
};
|
|
279
|
-
return
|
|
280
|
-
queued:
|
|
281
|
-
nudges:
|
|
282
|
-
})))),
|
|
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
|
|
285
|
-
},
|
|
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) ?
|
|
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
|
|
297
|
-
return await
|
|
298
|
-
}, f = o === "mutation" || o === "query" || o === "action" ? o : void 0, m = f === void 0 ? void 0 :
|
|
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
|
-
},
|
|
314
|
-
let n = new
|
|
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
|
-
}),
|
|
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
|
-
|
|
327
|
-
},
|
|
328
|
-
await
|
|
329
|
-
},
|
|
330
|
-
|
|
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(
|
|
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:
|
|
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
|
-
},
|
|
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 ??
|
|
605
|
+
log: e.log ?? xe({ scope: `subscribe:${t}` }),
|
|
406
606
|
store: n,
|
|
407
607
|
...e.publish === void 0 ? {} : { publish: e.publish }
|
|
408
608
|
};
|
|
409
|
-
},
|
|
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
|
-
},
|
|
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 =
|
|
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
|
-
},
|
|
681
|
+
}, ct = 1;
|
|
482
682
|
//#endregion
|
|
483
|
-
export {
|
|
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.
|
|
4
|
-
"description": "Test utilities for Voltro apps — deterministic clock,
|
|
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.
|
|
47
|
-
"@voltro/env": "0.
|
|
48
|
-
"@voltro/logger": "0.
|
|
49
|
-
"@voltro/protocol": "0.
|
|
50
|
-
"@voltro/runtime": "0.
|
|
51
|
-
"@voltro/workflow": "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.
|
|
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"
|