@voltro/testing 0.84.0 → 0.86.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.
@@ -0,0 +1,65 @@
1
+ import { fixtureRow as e, nextSequence as t } from "./fixtureRow.js";
2
+ import { missingRequiredColumns as n } from "@voltro/database";
3
+ //#region src/factory.ts
4
+ var r = (e) => {
5
+ let n = {};
6
+ for (let [r, i] of Object.entries(e)) n[r] = typeof i == "function" ? i(t()) : i;
7
+ return n;
8
+ }, i = (e) => {
9
+ for (let [t, n] of Object.entries(e.fields)) if (n.type === "id") return t;
10
+ return "id";
11
+ }, a = async (e, t, r, a, s) => {
12
+ let c = {};
13
+ for (let l of n(t, r)) {
14
+ let n = t.fields[l];
15
+ if (n === void 0 || n.type !== "reference" || n.references === void 0) continue;
16
+ let r = n.references();
17
+ if (s.includes(r.tableName)) throw Error(`defineFactory: creating '${t.tableName}' requires '${r.tableName}' via '${l}', and that reference is cyclic (${[...s, r.tableName].join(" → ")}). Insert one side first and pass the id: create(store, { ${l}: existing.id }).`);
18
+ let u = a[l];
19
+ c[l] = (u === void 0 ? await o(e, r, {}, {}, [...s, r.tableName]) : await u.create(e, {}))[n.referenceColumn ?? i(r)];
20
+ }
21
+ return c;
22
+ }, o = async (t, n, r, i, o) => {
23
+ let s = {
24
+ ...await a(t, n, r, i, o),
25
+ ...r
26
+ };
27
+ return t.insert(n.tableName, e(n, s));
28
+ }, s = (e, t) => typeof e == "function" ? e(t) : e ?? {}, c = (t, n = {}) => {
29
+ let i = n.traits ?? {}, a = n.associations ?? {}, c = (n) => {
30
+ let l = (e = {}) => ({
31
+ ...r(n),
32
+ ...e
33
+ }), u = (n = {}) => e(t, l(n));
34
+ return {
35
+ table: t,
36
+ build: u,
37
+ buildList: (e, t) => Array.from({ length: e }, (e, n) => u(s(t, n))),
38
+ create: (e, n = {}) => o(e, t, l(n), a, [t.tableName]),
39
+ createList: async (e, n, r) => {
40
+ let i = [];
41
+ for (let c = 0; c < n; c++) i.push(await o(e, t, l(s(r, c)), a, [t.tableName]));
42
+ return i;
43
+ },
44
+ with: (...e) => {
45
+ let r = n;
46
+ for (let n of e) {
47
+ let e = i[n];
48
+ if (e === void 0) throw Error(`defineFactory: '${t.tableName}' has no trait '${n}'. Declared: ${Object.keys(i).join(", ") || "(none)"}.`);
49
+ r = {
50
+ ...r,
51
+ ...e
52
+ };
53
+ }
54
+ return c(r);
55
+ },
56
+ extend: (e) => c({
57
+ ...n,
58
+ ...e
59
+ })
60
+ };
61
+ };
62
+ return c(n.defaults ?? {});
63
+ };
64
+ //#endregion
65
+ export { c as defineFactory };
@@ -0,0 +1,30 @@
1
+ import { AUTO_FILLED_COLUMNS as e, missingRequiredColumns as t } from "@voltro/database";
2
+ //#region src/fixtureRow.ts
3
+ var n = new Set(e), r = 0, i = () => ++r, a = (e, t) => {
4
+ if (t.oneOf && t.oneOf.length > 0) return t.oneOf[0];
5
+ if (t.enumValues && t.enumValues.length > 0) return t.enumValues[0];
6
+ let n = t.unique === !0;
7
+ switch (t.type) {
8
+ case "text":
9
+ case "id":
10
+ case "enum": return n ? `${e}-${++r}` : e;
11
+ case "reference": return t.referenceStorage === "numeric" ? n ? ++r : 0 : n ? `${e}-${++r}` : e;
12
+ case "integer":
13
+ case "real": return n ? ++r : 0;
14
+ case "decimal":
15
+ case "bigint": return n ? String(++r) : "0";
16
+ case "boolean": return !1;
17
+ case "timestamp":
18
+ case "date": return /* @__PURE__ */ new Date(0);
19
+ 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}: … }).`);
20
+ }
21
+ }, o = (e, r = {}) => {
22
+ let i = {};
23
+ for (let o of t(e, r)) n.has(o) || (i[o] = a(o, e.fields[o]));
24
+ return {
25
+ ...i,
26
+ ...r
27
+ };
28
+ };
29
+ //#endregion
30
+ export { o as fixtureRow, i as nextSequence };
@@ -0,0 +1,46 @@
1
+ import { makeVoltroTestClient as e } from "./client.js";
2
+ import { act as t, createElement as n } from "react";
3
+ import { useFormBinding as r } from "@voltro/client";
4
+ //#region src/formBindingHarness.tsx
5
+ var i = async (i, a = {}) => {
6
+ if (globalThis.document === void 0) throw Error("renderFormBinding needs a DOM to render hooks — add `// @vitest-environment jsdom` to the spec.");
7
+ let { createRoot: o } = await import("react-dom/client"), s = a.apiName ?? "app", c = e({
8
+ apiName: s,
9
+ ...a.client ?? {},
10
+ mutations: {
11
+ [i]: a.mutation ?? (() => ({ ok: !0 })),
12
+ ...a.client?.mutations ?? {}
13
+ }
14
+ }), l, u = () => (l = r(s, i, a.binding ?? {}), null), d = o(globalThis.document.createElement("div"));
15
+ return await t(async () => {
16
+ d.render(n(c.Provider, null, n(u)));
17
+ }), {
18
+ binding: () => l,
19
+ fill: (e, n) => {
20
+ t(() => {
21
+ l.field(e).setValue(n);
22
+ });
23
+ },
24
+ blur: (e) => {
25
+ t(() => {
26
+ l.field(e).onBlur();
27
+ });
28
+ },
29
+ submit: async () => {
30
+ let e;
31
+ return await t(async () => {
32
+ e = await l.submit();
33
+ }), e;
34
+ },
35
+ errors: () => l.errors,
36
+ state: () => l.state,
37
+ client: c,
38
+ unmount: () => {
39
+ t(() => {
40
+ d.unmount();
41
+ });
42
+ }
43
+ };
44
+ };
45
+ //#endregion
46
+ export { i as renderFormBinding };
@@ -0,0 +1,22 @@
1
+ import { Result as e, Schema as t } from "effect";
2
+ //#region src/hydrateFixtureRows.ts
3
+ var n = t.decodeUnknownResult(t.DateFromString), r = /^\d{4}-\d{2}-\d{2}$/, i = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/, a = (t, a) => {
4
+ if (typeof t != "string" || !i.test(t) && !(a === "date" && r.test(t))) return !1;
5
+ let o = t.slice(0, 10), s = n(o);
6
+ return e.isSuccess(s) && Number.isFinite(s.success.getTime()) && s.success.toISOString().slice(0, 10) === o;
7
+ }, o = (t, r) => {
8
+ let i = Object.entries(t.fields).filter(([, e]) => e.type === "timestamp" || e.type === "date");
9
+ return r.map((r, o) => {
10
+ let s = { ...r };
11
+ for (let [c, l] of i) {
12
+ let i = r[c];
13
+ if (i == null || i instanceof Date && Number.isFinite(i.getTime())) continue;
14
+ let u = n(i);
15
+ if (!a(i, l.type) || e.isFailure(u) || !Number.isFinite(u.success.getTime())) throw TypeError(`hydrateFixtureRows: table '${t.tableName}', row ${o}, column '${c}' requires a valid Date or ISO date-time with Z or an offset${l.type === "date" ? " (YYYY-MM-DD is also accepted as UTC midnight)" : ""}.`);
16
+ s[c] = u.success;
17
+ }
18
+ return s;
19
+ });
20
+ };
21
+ //#endregion
22
+ export { o as hydrateFixtureRows };
package/dist/index.d.ts CHANGED
@@ -13,7 +13,6 @@ import { EventPublisher } from '@voltro/runtime';
13
13
  import { InspectedStep } from '@voltro/workflow';
14
14
  import { InspectedWorkflow } from '@voltro/workflow';
15
15
  import { Layer } from 'effect';
16
- import { ParseResult } from 'effect';
17
16
  import { ProcedureDescriptor } from '@voltro/protocol';
18
17
  import { PublicApiDescriptor } from '@voltro/protocol/rest';
19
18
  import { PublicApiProfileConfig } from '@voltro/protocol';
@@ -27,6 +26,7 @@ import { RpcConnection } from '@voltro/runtime';
27
26
  import { RpcInterceptor } from '@voltro/protocol';
28
27
  import { RpcKind } from '@voltro/protocol';
29
28
  import { Schema } from 'effect';
29
+ import { SchemaError } from 'effect/Schema';
30
30
  import { Scope } from 'effect';
31
31
  import { ServiceApi } from '@voltro/protocol';
32
32
  import { Subject } from '@voltro/protocol';
@@ -678,8 +678,6 @@ export declare const nextSequence: () => number;
678
678
  /** Delivery nudges emitted through `ctx.outbox` on this context. */
679
679
  export declare const outboxNudgesOf: (ctx: TestContext) => ReadonlyArray<string>;
680
680
 
681
- export { ParseResult }
682
-
683
681
  /** The plugins a context was built with — for the REST harness, which hands
684
682
  * their cross-cutting errors to the `publicApi` projection the way both boot
685
683
  * paths do. */
@@ -809,6 +807,8 @@ export declare const runAfterCommit: (ctx: TestContext) => Promise<void>;
809
807
  */
810
808
  export declare const runInStoreTransaction: <T>(ctx: TestContext, work: (txCtx: TestContext) => Promise<T>) => Promise<T>;
811
809
 
810
+ export { SchemaError }
811
+
812
812
  /** A machine subject scoped to ONE tenant (a per-tenant integration, a
813
813
  * `storeForTenant` view). NOT the cross-tenant `system()` below. */
814
814
  export declare const serviceAccount: (id: string, options?: TestSubjectOptions) => Subject;
@@ -1020,11 +1020,11 @@ export declare interface TestEventBus {
1020
1020
  * descriptor fails here, which is the point: a harness that skipped validation
1021
1021
  * would let a test pass on a payload production rejects.
1022
1022
  */
1023
- publish<Name extends string, Key extends Schema.Schema.Any, Payload extends Schema.Schema.Any>(descriptor: EventDescriptor<Name, Key, Payload>, key: Schema.Schema.Type<Key>, payload: Schema.Schema.Type<Payload>): Promise<{
1023
+ publish<Name extends string, Key extends Schema.Top, Payload extends Schema.Top>(descriptor: EventDescriptor<Name, Key, Payload>, key: Schema.Schema.Type<Key>, payload: Schema.Schema.Type<Payload>): Promise<{
1024
1024
  readonly n: number;
1025
1025
  }>;
1026
1026
  /** Publish and ASSERT it succeeded — the common case, one line. */
1027
- subscribe<Name extends string, Key extends Schema.Schema.Any, Payload extends Schema.Schema.Any>(descriptor: EventDescriptor<Name, Key, Payload>, key: Schema.Schema.Type<Key>, options?: {
1027
+ subscribe<Name extends string, Key extends Schema.Top, Payload extends Schema.Top>(descriptor: EventDescriptor<Name, Key, Payload>, key: Schema.Schema.Type<Key>, options?: {
1028
1028
  readonly tenantId?: string | null;
1029
1029
  readonly resume?: ReadonlyArray<{
1030
1030
  origin: string;
@@ -1040,7 +1040,7 @@ export declare interface TestEventBus {
1040
1040
  * is owed messages the ring never held — exactly the shape a slow consumer
1041
1041
  * produces, deterministically.
1042
1042
  */
1043
- skipSerials<Name extends string, Key extends Schema.Schema.Any, Payload extends Schema.Schema.Any>(descriptor: EventDescriptor<Name, Key, Payload>, key: Schema.Schema.Type<Key>, count: number, options?: {
1043
+ skipSerials<Name extends string, Key extends Schema.Top, Payload extends Schema.Top>(descriptor: EventDescriptor<Name, Key, Payload>, key: Schema.Schema.Type<Key>, count: number, options?: {
1044
1044
  readonly tenantId?: string | null;
1045
1045
  }): void;
1046
1046
  /** The underlying bus, for a test that needs something not exposed here. */
@@ -1094,13 +1094,13 @@ export declare interface TestResponse {
1094
1094
  readonly headers: Readonly<Record<string, string | ReadonlyArray<string>>>;
1095
1095
  /** The raw response body as text (empty string when there was none). */
1096
1096
  readonly text: string;
1097
- /** The body parsed as JSON when the route answered with a JSON content-type,
1098
- * otherwise `undefined`. Every framework REST response is JSON; a streaming
1099
- * (SSE) route answers with neither — see `stream`. */
1097
+ /** Byte-exact buffered body, including a consumed finite byte stream. SSE remains unconsumed. */
1098
+ readonly bytes?: Uint8Array;
1099
+ /** Parsed JSON when the response has a JSON content-type; otherwise
1100
+ * `undefined`. SSE responses remain unconsumed. */
1100
1101
  readonly body: unknown;
1101
- /** `true` for a streaming (SSE) route: the response has no buffered body, so
1102
- * `text` is empty and `body` is `undefined`. Stated rather than silently
1103
- * looking like an empty 200. */
1102
+ /** True for SSE and byte streams. Finite byte streams populate `bytes` and
1103
+ * `text`; SSE remains unconsumed, with empty text and undefined body. */
1104
1104
  readonly stream: boolean;
1105
1105
  }
1106
1106
 
@@ -1161,7 +1161,7 @@ export declare const withFrozenTime: <A>(at: Date | number | string | MockClock,
1161
1161
  export declare const withRpcConnection: (ctx: TestContext, connection: RpcConnection | undefined) => TestContext;
1162
1162
 
1163
1163
  /** A workflow + its execute function, as the framework pairs them via
1164
- * `workflow.toLayer(execute)`. Structural (no `@effect/workflow` generic
1164
+ * `workflow.toLayer(execute)`. Structural (no `effect/workflow` generic
1165
1165
  * signature) so callers can pass the value from `workflow({ name, ... })`
1166
1166
  * plus its `(payload, executionId) => Effect` execute directly — type-erased
1167
1167
  * over the workflow's payload/success/error/requirement shapes. */