@voltro/testing 0.85.0 → 0.87.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/client.d.ts CHANGED
@@ -6,6 +6,7 @@ import { FormBinding } from '@voltro/client';
6
6
  import { FormStateSnapshot } from '@voltro/client';
7
7
  import { ReactElement } from 'react';
8
8
  import { ReactNode } from 'react';
9
+ import { RecoveryOptions } from '@voltro/client';
9
10
  import { UseFormBindingOptions } from '@voltro/client';
10
11
 
11
12
  /** The snapshot shape `useConnectionStatus` subscribes to. */
@@ -177,6 +178,10 @@ export declare interface VoltroTestClientConfig {
177
178
  readonly mutations?: Readonly<Record<string, Handler>>;
178
179
  /** Action handlers per tag. */
179
180
  readonly actions?: Readonly<Record<string, Handler>>;
181
+ /** The client's recovery options, as an app passes them — `replayWindowMs` is how
182
+ * long an action waits for a disconnected transport before it is refused
183
+ * (default 10 s, at least 250 ms). */
184
+ readonly recovery?: RecoveryOptions;
180
185
  }
181
186
 
182
187
  export { }
package/dist/client.js CHANGED
@@ -1,85 +1,47 @@
1
- import { Effect as e } from "effect";
2
- import { act as t, createElement as n } from "react";
3
- import { FrameworkRuntimesContext as r, RpcErrorBus as i, useFormBinding as a } from "@voltro/client";
4
- //#region src/formBindingHarness.tsx
5
- var o = async (e, r = {}) => {
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: i } = await import("react-dom/client"), o = r.apiName ?? "app", s = c({
8
- apiName: o,
9
- ...r.client ?? {},
10
- mutations: {
11
- [e]: r.mutation ?? (() => ({ ok: !0 })),
12
- ...r.client?.mutations ?? {}
13
- }
14
- }), l, u = () => (l = a(o, e, r.binding ?? {}), null), d = i(globalThis.document.createElement("div"));
15
- return await t(async () => {
16
- d.render(n(s.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: s,
38
- unmount: () => {
39
- t(() => {
40
- d.unmount();
41
- });
42
- }
43
- };
44
- }, s = {
45
- runPromise: e.runPromise,
46
- runPromiseExit: e.runPromiseExit,
47
- runFork: e.runFork,
48
- dispose: () => e.void
49
- }, c = (t = {}) => {
50
- let a = t.apiName ?? "app", o = /* @__PURE__ */ new Map(), c = /* @__PURE__ */ new Map(), l = /* @__PURE__ */ new Map(), u = [];
51
- for (let [e, n] of Object.entries(t.subscriptions ?? {})) o.set(e, {
1
+ import { renderFormBinding as e } from "./formBindingHarness.js";
2
+ import { Effect as t } from "effect";
3
+ import { createElement as n } from "react";
4
+ import { FrameworkRuntimesContext as r, INITIAL_RECOVERY_SNAPSHOT as i, RpcErrorBus as a, resolveRecoveryOptions as o } from "@voltro/client";
5
+ //#region src/client.tsx
6
+ var s = {
7
+ runPromise: t.runPromise,
8
+ runPromiseExit: t.runPromiseExit,
9
+ runFork: t.runFork,
10
+ dispose: () => t.void
11
+ }, c = (e = {}) => {
12
+ let c = e.apiName ?? "app", l = /* @__PURE__ */ new Map(), u = /* @__PURE__ */ new Map(), d = /* @__PURE__ */ new Map(), f = [];
13
+ for (let [t, n] of Object.entries(e.subscriptions ?? {})) l.set(t, {
52
14
  data: n,
53
15
  error: void 0,
54
16
  revision: 0,
55
17
  pendingPatches: 0,
56
18
  emittedAt: void 0
57
19
  });
58
- let d = (e) => o.get(e) ?? {
20
+ let p = (e) => l.get(e) ?? {
59
21
  data: void 0,
60
22
  error: void 0,
61
23
  revision: -1,
62
24
  pendingPatches: 0,
63
25
  emittedAt: void 0
64
- }, f = (e) => {
65
- l.delete(e);
66
- for (let t of c.get(e) ?? []) t();
67
- }, p = (e) => String(e[0] ?? ""), m = {
26
+ }, m = (e) => {
27
+ d.delete(e);
28
+ for (let t of u.get(e) ?? []) t();
29
+ }, h = (e) => String(e[0] ?? ""), g = {
68
30
  peek: (e) => {
69
- let t = p(e), n = l.get(t);
31
+ let t = h(e), n = d.get(t);
70
32
  if (n) return n;
71
- let r = d(t), i = {
33
+ let r = p(t), i = {
72
34
  data: r.data,
73
35
  revision: r.revision,
74
36
  emittedAt: r.emittedAt,
75
37
  error: r.error,
76
38
  pendingPatches: r.pendingPatches
77
39
  };
78
- return l.set(t, i), i;
40
+ return d.set(t, i), i;
79
41
  },
80
42
  subscribe: (e, t, n, r) => {
81
- let i = p(e), a = c.get(i) ?? /* @__PURE__ */ new Set();
82
- return a.add(r), c.set(i, a), { unsubscribe: () => {
43
+ let i = h(e), a = u.get(i) ?? /* @__PURE__ */ new Set();
44
+ return a.add(r), u.set(i, a), { unsubscribe: () => {
83
45
  a.delete(r);
84
46
  } };
85
47
  },
@@ -92,9 +54,9 @@ var o = async (e, r = {}) => {
92
54
  update: () => {},
93
55
  forTag: () => ({ patchedCount: 0 }),
94
56
  refreshAll: () => {
95
- h += 1;
57
+ _ += 1;
96
58
  }
97
- }, h = 0, g = {
59
+ }, _ = 0, v = {
98
60
  rawTransport: "connected",
99
61
  status: "connected",
100
62
  failureCount: 0,
@@ -102,83 +64,109 @@ var o = async (e, r = {}) => {
102
64
  failedSubscriptions: [],
103
65
  pendingMutationReplays: 0,
104
66
  mutationReplay: "unknown"
105
- }, _ = /* @__PURE__ */ new Set(), v = {
106
- subscribe: (e) => (_.add(e), () => {
107
- _.delete(e);
67
+ }, y = /* @__PURE__ */ new Set(), b = o(e.recovery), x = new a(), S = !0, C = {
68
+ options: b,
69
+ subscribe: (e) => (y.add(e), () => {
70
+ y.delete(e);
108
71
  }),
109
- getSnapshot: () => g,
72
+ getSnapshot: () => v,
73
+ getServerSnapshot: () => i,
74
+ getPreloadSeedPermission: () => S,
75
+ invalidatePreloadSeeds: () => {
76
+ if (S) {
77
+ S = !1;
78
+ for (let e of y) e();
79
+ }
80
+ },
81
+ onVisibleError: (e) => x.on(e),
82
+ waitForProtocolReady: (e, t) => {
83
+ if (t?.aborted) return Promise.resolve(!1);
84
+ if (v.rawTransport === "connected") return Promise.resolve(!0);
85
+ let n = b.replayWindowMs - (Date.now() - e);
86
+ return n <= 0 ? Promise.resolve(!1) : new Promise((e) => {
87
+ let r = (n) => {
88
+ y.delete(i), clearTimeout(o), t?.removeEventListener("abort", a), e(n);
89
+ }, i = () => {
90
+ v.rawTransport === "connected" && r(!0);
91
+ }, a = () => r(!1), o = setTimeout(() => r(!1), n);
92
+ y.add(i), t?.addEventListener("abort", a, { once: !0 }), t?.aborted && a();
93
+ });
94
+ },
95
+ reserveMutationReplay: () => void 0,
96
+ waitForMutationReplaySupport: async () => !1,
97
+ replayWindowRemaining: () => 0,
110
98
  reportExternalSuccess: () => {},
111
99
  subjectSignal: new AbortController().signal
112
- }, y = {}, b = (t, n, r) => {
113
- let i = t.split("."), a = y;
100
+ }, w = {}, T = (e, n, r) => {
101
+ let i = e.split("."), a = w;
114
102
  for (let e of i.slice(0, -1)) a[e] = a[e] ?? {}, a = a[e];
115
- a[i[i.length - 1]] = (i) => e.tryPromise({
116
- try: async () => (u.push({
103
+ a[i[i.length - 1]] = (i) => t.tryPromise({
104
+ try: async () => (f.push({
117
105
  kind: n,
118
- tag: t,
106
+ tag: e,
119
107
  input: i
120
108
  }), await r(i)),
121
109
  catch: (e) => e
122
110
  });
123
111
  };
124
- for (let [e, n] of Object.entries(t.mutations ?? {})) b(e, "mutation", n);
125
- for (let [e, n] of Object.entries(t.actions ?? {})) b(e, "action", n);
126
- let x = {
112
+ for (let [t, n] of Object.entries(e.mutations ?? {})) T(t, "mutation", n);
113
+ for (let [t, n] of Object.entries(e.actions ?? {})) T(t, "action", n);
114
+ let E = {
127
115
  runtime: s,
128
- cache: m,
129
- recovery: v,
130
- client: y,
116
+ cache: g,
117
+ recovery: C,
118
+ client: w,
131
119
  descriptors: {},
132
120
  inspectBaseUrl: "http://test.local",
133
- errorBus: new i()
134
- }, S = {
121
+ errorBus: x
122
+ }, D = {
135
123
  get: (e) => {
136
- if (e !== a) throw Error(`makeVoltroTestClient: component asked for api '${e}' but the harness was built for '${a}'. Pass { apiName: '${e}' }.`);
137
- return x;
124
+ if (e !== c) throw Error(`makeVoltroTestClient: component asked for api '${e}' but the harness was built for '${c}'. Pass { apiName: '${e}' }.`);
125
+ return E;
138
126
  },
139
- names: [a]
127
+ names: [c]
140
128
  };
141
129
  return {
142
- Provider: ({ children: e }) => n(r.Provider, { value: S }, e),
130
+ Provider: ({ children: e }) => n(r.Provider, { value: D }, e),
143
131
  setSubscription: (e, t, n) => {
144
- let r = d(e);
145
- o.set(e, {
132
+ let r = p(e);
133
+ l.set(e, {
146
134
  data: t,
147
135
  error: void 0,
148
136
  revision: r.revision + 1,
149
137
  pendingPatches: n?.pendingPatches ?? 0,
150
138
  emittedAt: n?.emittedAt
151
- }), f(e);
139
+ }), m(e);
152
140
  },
153
141
  failSubscription: (e, t) => {
154
- o.set(e, {
142
+ l.set(e, {
155
143
  data: void 0,
156
144
  error: t,
157
145
  revision: -1,
158
146
  pendingPatches: 0,
159
147
  emittedAt: void 0
160
- }), f(e);
148
+ }), m(e);
161
149
  },
162
150
  resetSubscription: (e) => {
163
- o.delete(e), f(e);
151
+ l.delete(e), m(e);
164
152
  },
165
- calls: u,
153
+ calls: f,
166
154
  setConnectionStatus: (e, t) => {
167
- g = {
168
- ...g,
155
+ v = {
156
+ ...v,
169
157
  ...t,
170
158
  status: e,
171
159
  rawTransport: t?.rawTransport ?? (e === "connected" ? "connected" : "disconnected")
172
160
  };
173
- for (let e of _) e();
161
+ for (let e of y) e();
174
162
  },
175
163
  get refreshes() {
176
- return h;
164
+ return _;
177
165
  },
178
166
  emitRpcError: (e) => {
179
- x.errorBus.emit(e);
167
+ E.errorBus.emit(e);
180
168
  }
181
169
  };
182
170
  };
183
171
  //#endregion
184
- export { s as FAKE_RUNTIME, c as makeVoltroTestClient, o as renderFormBinding };
172
+ export { s as FAKE_RUNTIME, c as makeVoltroTestClient, e as renderFormBinding };
package/dist/dialect.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { applySchema } from '@voltro/database/sql';
2
2
  import { DataStore } from '@voltro/database';
3
+ import { DialectId } from '@voltro/database';
3
4
  import { Effect } from 'effect';
4
5
  import { SqlClient } from 'effect/sql';
5
6
  import { SqlDialect } from '@voltro/database';
@@ -48,6 +49,22 @@ export declare interface DialectFixtureSession {
48
49
  readonly dispose: () => Promise<void>;
49
50
  }
50
51
 
52
+ export declare interface HotSqlFixture {
53
+ readonly dialect: DialectId;
54
+ readonly name: string;
55
+ readonly make: () => Promise<HotSqlFixtureSession>;
56
+ }
57
+
58
+ /** A real SQL fixture owns a fresh physical namespace and every connection it opens. */
59
+ export declare interface HotSqlFixtureSession {
60
+ readonly store: DataStore;
61
+ /** Open another independent SQL connection/pool to the same owned tables. */
62
+ readonly connect: () => Promise<DataStore>;
63
+ /** Re-apply the declared tables; SQL fixtures must keep DDL idempotent. */
64
+ readonly migrate: () => Promise<void>;
65
+ readonly dispose: () => Promise<void>;
66
+ }
67
+
51
68
  /**
52
69
  * Register the dialect's parity suite with vitest. Calls
53
70
  * `describe(...)` so the dialect's name appears in test output.
@@ -64,4 +81,7 @@ export declare interface DialectFixtureSession {
64
81
  */
65
82
  export declare const runDialectParity: (fixture: DialectFixture) => void;
66
83
 
84
+ /** Identical HotStore assertions for every live SQL dialect; no fixture opt-outs. */
85
+ export declare const runHotStoreSqlParity: (fixture: HotSqlFixture) => void;
86
+
67
87
  export { }