@voltro/testing 0.1.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 ADDED
@@ -0,0 +1,237 @@
1
+ import { Effect as e, Layer as t, Schema as n } from "effect";
2
+ import { allRegisteredTables as r } from "@voltro/database";
3
+ import { installEnvSnapshot as i } from "@voltro/env";
4
+ import { InMemoryDataStore as a, makeSchemaRegistry as o, wrapStoreWithMixinBehaviour as s } from "@voltro/runtime";
5
+ import { anonymousSubject as c } from "@voltro/protocol";
6
+ import { CurrentWorkflowRunId as l, inMemoryWorkflowEngineLayer as u, makeInMemoryRecorder as d } from "@voltro/workflow";
7
+ //#region src/invoke.ts
8
+ var f = async (e, t, r, i) => {
9
+ let a = e.input;
10
+ return t(await n.decodeUnknownPromise(a)(r), i);
11
+ }, p = class {
12
+ currentMs;
13
+ constructor(e = /* @__PURE__ */ new Date("2026-01-01T00:00:00Z")) {
14
+ this.currentMs = typeof e == "number" ? e : e.getTime();
15
+ }
16
+ now() {
17
+ return this.currentMs;
18
+ }
19
+ date() {
20
+ return new Date(this.currentMs);
21
+ }
22
+ advance(e) {
23
+ this.currentMs += typeof e == "number" ? e : m(e);
24
+ }
25
+ }, m = (e) => {
26
+ let t = /^\s*(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)\s*$/.exec(e);
27
+ if (!t) throw Error(`mockClock: cannot parse duration '${e}'`);
28
+ let n = Number(t[1]), r = t[2];
29
+ switch (r) {
30
+ case "ms": return n;
31
+ case "s": return n * 1e3;
32
+ case "m": return n * 6e4;
33
+ case "h": return n * 36e5;
34
+ case "d": return n * 864e5;
35
+ default: throw Error(`mockClock: unknown unit '${r}'`);
36
+ }
37
+ }, h = class {
38
+ sent = [];
39
+ send(e) {
40
+ this.sent.push({
41
+ ...e,
42
+ sentAt: /* @__PURE__ */ new Date()
43
+ });
44
+ }
45
+ lastTo(e) {
46
+ for (let t = this.sent.length - 1; t >= 0; t--) if (this.sent[t].to === e) return this.sent[t];
47
+ }
48
+ clear() {
49
+ this.sent.length = 0;
50
+ }
51
+ }, g = class {
52
+ queue;
53
+ calls = [];
54
+ constructor(e) {
55
+ this.queue = [...e];
56
+ }
57
+ next(e) {
58
+ this.calls.push(e);
59
+ let t = this.queue.shift();
60
+ if (!t) throw Error("MockLLM: no more responses queued");
61
+ return t;
62
+ }
63
+ remaining() {
64
+ return this.queue.length;
65
+ }
66
+ }, _ = (e) => e, v = "00000000000000000000000000000000", y = (e) => {
67
+ let t = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map(), r = (n) => {
68
+ let r = t.get(n);
69
+ return r === void 0 ? !1 : r.expiresAt !== null && r.expiresAt <= e() ? (t.delete(n), !1) : !0;
70
+ }, i = (r, i, a, o) => {
71
+ if (t.set(r, {
72
+ value: i,
73
+ expiresAt: a === void 0 ? null : e() + a
74
+ }), o) for (let e of o) {
75
+ let t = n.get(e) ?? /* @__PURE__ */ new Set();
76
+ t.add(r), n.set(e, t);
77
+ }
78
+ };
79
+ return {
80
+ get: async (e) => r(e) ? t.get(e).value : void 0,
81
+ set: async (e, t, n) => {
82
+ i(e, t, n?.ttlMs);
83
+ },
84
+ setWithTags: async (e, t, n) => {
85
+ i(e, t, n.ttlMs, n.tags);
86
+ },
87
+ remove: async (e) => t.delete(e),
88
+ has: async (e) => r(e),
89
+ clear: async () => {
90
+ t.clear(), n.clear();
91
+ },
92
+ invalidateTag: async (e) => {
93
+ let r = n.get(e);
94
+ if (r === void 0) return 0;
95
+ let i = 0;
96
+ for (let e of r) t.delete(e) && i++;
97
+ return n.delete(e), i;
98
+ },
99
+ wrap: async (e, n, a) => {
100
+ if (r(e)) return t.get(e).value;
101
+ let o = await a();
102
+ return i(e, o, n.ttlMs, n.tags), o;
103
+ }
104
+ };
105
+ }, b = (e) => {
106
+ let t = /* @__PURE__ */ new Map(), n = (n) => {
107
+ let r = t.get(n);
108
+ return r === void 0 ? !1 : r.expiresAt !== null && r.expiresAt <= e() ? (t.delete(n), !1) : !0;
109
+ };
110
+ return {
111
+ get: async (e) => n(e) ? t.get(e).value : void 0,
112
+ getOrElse: async (e, r) => n(e) ? t.get(e).value : r(),
113
+ set: async (n, r, i) => {
114
+ t.set(n, {
115
+ value: r,
116
+ expiresAt: i?.ttlMs === void 0 ? null : e() + i.ttlMs
117
+ });
118
+ },
119
+ delete: async (e) => {
120
+ let r = n(e);
121
+ return t.delete(e), r;
122
+ },
123
+ has: async (e) => n(e),
124
+ list: async (e) => {
125
+ let r = [];
126
+ for (let i of t.keys()) n(i) && (e === "" || i.startsWith(e)) && r.push(i);
127
+ return r;
128
+ },
129
+ clear: async () => {
130
+ t.clear();
131
+ }
132
+ };
133
+ }, x = (e = {}) => {
134
+ i({
135
+ ...process.env,
136
+ ...e.env ?? {}
137
+ });
138
+ let t = o(e.tables ?? r()), n = new a(e.store ?? {}), l = new p(e.clockStart), u = new h(), d = new g(e.llmResponses ?? []), f = y(() => l.now()), m = b(() => l.now()), _ = (r) => {
139
+ let i = {
140
+ subject: r,
141
+ traceId: v
142
+ };
143
+ return {
144
+ clock: l,
145
+ email: u,
146
+ llm: d,
147
+ ...e.ai === void 0 ? {} : { ai: e.ai },
148
+ request: i,
149
+ cache: f,
150
+ kv: m,
151
+ store: s(n, {
152
+ subject: r,
153
+ schemaRegistry: t
154
+ }),
155
+ withSubject: (e, t) => Promise.resolve(t(_(e))),
156
+ withTenant: (e, t) => Promise.resolve(t(_({
157
+ ...r,
158
+ tenantId: e
159
+ })))
160
+ };
161
+ };
162
+ return _(e.subject ?? c(null));
163
+ }, S = (e) => {
164
+ let t = /* @__PURE__ */ new Map();
165
+ for (let n of e) {
166
+ let e = t.get(n.stepName);
167
+ e === void 0 ? t.set(n.stepName, [n]) : e.push(n);
168
+ }
169
+ let n = [];
170
+ for (let [e, r] of t) {
171
+ r.sort((e, t) => e.attempt - t.attempt);
172
+ let t = r[r.length - 1], i = t.errorTag !== null || t.errorMessage !== null ? {
173
+ tag: t.errorTag,
174
+ message: t.errorMessage ?? ""
175
+ } : null;
176
+ n.push({
177
+ name: e,
178
+ attempt: t.attempt,
179
+ attempts: r.length,
180
+ status: t.status,
181
+ input: t.input,
182
+ output: t.output,
183
+ error: i,
184
+ durationMs: t.durationMs,
185
+ _startedAt: r[0].startedAt.getTime()
186
+ });
187
+ }
188
+ return n.sort((e, t) => e._startedAt - t._startedAt), n.map(({ _startedAt: e, ...t }) => t);
189
+ }, C = (n) => {
190
+ let r = n.workflows ?? [], i = /* @__PURE__ */ new Map(), a = 0;
191
+ return {
192
+ start: async (o, s) => {
193
+ let c = r.find((e) => e.workflow.name === o);
194
+ if (c === void 0) throw Error(`makeWorkflowRunner: no workflow named '${o}' — pass it in makeWorkflowRunner({ ctx, workflows: [{ workflow, execute }] }).`);
195
+ let f = `wfrun_test_${++a}`, p = d(), m = c.workflow.toLayer(c.execute).pipe(t.provideMerge(u)), h = /* @__PURE__ */ new Date(), g = c.workflow.execute(s).pipe(e.locally(l, f), e.provide(p.layer), e.provide(m), e.either), _ = await e.runPromise(g), v = S(p.readSteps()), y = /* @__PURE__ */ new Date(), b;
196
+ if (_._tag === "Right") b = {
197
+ status: "succeeded",
198
+ output: _.right,
199
+ error: null,
200
+ steps: v,
201
+ runId: f
202
+ };
203
+ else {
204
+ let e = _.left;
205
+ b = {
206
+ status: "failed",
207
+ output: null,
208
+ error: {
209
+ tag: e && typeof e == "object" && "_tag" in e ? String(e._tag) : null,
210
+ message: e && typeof e == "object" && "message" in e && typeof e.message == "string" ? e.message : String(_.left)
211
+ },
212
+ steps: v,
213
+ runId: f
214
+ };
215
+ }
216
+ return i.set(f, {
217
+ id: f,
218
+ executionId: f,
219
+ name: o,
220
+ status: b.status,
221
+ input: s,
222
+ output: b.output,
223
+ subject: n.ctx.request.subject,
224
+ source: "test-runner",
225
+ steps: v,
226
+ startedAt: h,
227
+ finishedAt: y,
228
+ traceId: null,
229
+ parentExecutionId: null,
230
+ parentClosePolicy: null
231
+ }), b;
232
+ },
233
+ inspect: async (e) => i.get(e) ?? null
234
+ };
235
+ }, w = 1;
236
+ //#endregion
237
+ export { p as MockClock, h as MockEmail, g as MockLLM, w as TESTING_PRESET_VERSION, f as invoke, x as makeTestContext, C as makeWorkflowRunner, _ as mockStore };
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@voltro/testing",
3
+ "version": "0.1.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.",
5
+ "keywords": [
6
+ "voltro",
7
+ "typescript",
8
+ "framework"
9
+ ],
10
+ "license": "SEE LICENSE IN LICENSE",
11
+ "homepage": "https://voltro.dev",
12
+ "bugs": {
13
+ "email": "support@voltro.dev"
14
+ },
15
+ "author": {
16
+ "name": "Voltro UG",
17
+ "url": "https://voltro.dev"
18
+ },
19
+ "type": "module",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js",
24
+ "default": "./dist/index.js"
25
+ },
26
+ "./dialect": {
27
+ "types": "./dist/dialect.d.ts",
28
+ "import": "./dist/dialect.js",
29
+ "default": "./dist/dialect.js"
30
+ }
31
+ },
32
+ "main": "./dist/index.js",
33
+ "module": "./dist/index.js",
34
+ "types": "./dist/index.d.ts",
35
+ "sideEffects": false,
36
+ "engines": {
37
+ "node": ">=24.0.0"
38
+ },
39
+ "dependencies": {
40
+ "@voltro/database": "0.1.0",
41
+ "@voltro/env": "0.1.0",
42
+ "@voltro/protocol": "0.1.0",
43
+ "@voltro/runtime": "0.1.0",
44
+ "@voltro/workflow": "0.1.0"
45
+ },
46
+ "peerDependencies": {
47
+ "effect": "^3.21.4"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ }
52
+ }