@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/CHANGELOG.md +5987 -0
- package/README.md +11 -0
- package/THIRD-PARTY-NOTICES.md +864 -253
- package/dist/client.d.ts +5 -0
- package/dist/client.js +85 -97
- package/dist/dialect.d.ts +20 -0
- package/dist/dialect.js +1051 -653
- package/dist/factory.js +65 -0
- package/dist/fieldKeys.js +73 -0
- package/dist/fieldKeysSql.js +14 -0
- package/dist/fixtureRow.js +30 -0
- package/dist/formBindingHarness.js +46 -0
- package/dist/hotStoreSqlParity.js +132 -0
- package/dist/hydrateFixtureRows.js +22 -0
- package/dist/index.d.ts +125 -7
- package/dist/index.js +22 -933
- package/dist/invoke.js +82 -0
- package/dist/mockClock.js +78 -0
- package/dist/mockLLM.js +19 -0
- package/dist/mockWebhooks.js +26 -0
- package/dist/platform.js +1 -5
- package/dist/reachable.js +27 -0
- package/dist/serviceApiMock.js +53 -0
- package/dist/subject.js +11 -0
- package/dist/subscribeContext.js +16 -0
- package/dist/system.d.ts +99 -0
- package/dist/system.js +94 -0
- package/dist/testApps.js +111 -0
- package/dist/testContext.js +269 -0
- package/dist/testEventBus.js +73 -0
- package/dist/testRequest.js +121 -0
- package/dist/withConnections.js +26 -0
- package/dist/workflowRunner.js +85 -0
- package/package.json +16 -9
package/dist/dialect.js
CHANGED
|
@@ -1,81 +1,83 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { runHotStoreSqlParity as e } from "./hotStoreSqlParity.js";
|
|
2
|
+
import { _voltroMigrationPlansTable as t, array as n, avg as r, boolean as i, bulkInsertLimitsFor as a, ciphertextKeyId as o, column as s, count as c, countDistinct as l, dbEnum as u, decimal as d, dropped as f, eq as p, fieldBinding as m, id as h, integer as g, json as _, max as v, min as ee, real as y, reference as te, registerTable as b, registerWriteRecorder as x, runWithWriteAttribution as ne, sum as S, table as C, text as w, timestamp as T, unregisterTable as E, unregisterWriteRecorder as re } from "@voltro/database";
|
|
3
|
+
import { Effect as D } from "effect";
|
|
4
|
+
import { FIELD_KEYS_TABLE as O, FIELD_KEY_HOLDERS_TABLE as k, FieldKeyringRefusal as A, _voltroFieldKeyHoldersTable as j, _voltroFieldKeysTable as M, _voltroUndoLogTable as ie, applyInverses as N, deriveKey as ae, getFieldCipher as oe, heartbeatFieldKeys as se, makeAesCipher as ce, makeFieldKeyring as P, makeSchemaRegistry as F, readFieldKeys as I, releaseFieldKeyHolder as le, setFieldCipher as L, synthesizeInverse as R, verifyFieldKeyring as z, wrapStoreWithMixinBehaviour as B } from "@voltro/runtime";
|
|
5
|
+
import { afterEach as ue, beforeEach as de, describe as V, expect as H, it as U } from "vitest";
|
|
6
|
+
import { randomUUID as W } from "node:crypto";
|
|
7
|
+
import { SqlClient as G } from "effect/sql";
|
|
8
|
+
import { applyPlan as K, applySchema as q, declaredSnapshot as fe, fingerprintSchema as pe, introspectSchema as J, planMigrations as Y } from "@voltro/database/sql";
|
|
7
9
|
//#region src/dialectParity.ts
|
|
8
|
-
var
|
|
10
|
+
var X = (e) => ({
|
|
9
11
|
table: e,
|
|
10
12
|
predicate: void 0,
|
|
11
13
|
order: [],
|
|
12
14
|
take: void 0,
|
|
13
15
|
skip: void 0,
|
|
14
16
|
projection: void 0
|
|
15
|
-
}),
|
|
16
|
-
id:
|
|
17
|
-
title:
|
|
18
|
-
count:
|
|
19
|
-
}),
|
|
20
|
-
id:
|
|
21
|
-
title:
|
|
22
|
-
}),
|
|
23
|
-
id:
|
|
24
|
-
title:
|
|
25
|
-
count:
|
|
26
|
-
flag:
|
|
27
|
-
}),
|
|
28
|
-
id:
|
|
29
|
-
title:
|
|
30
|
-
at:
|
|
31
|
-
n:
|
|
32
|
-
}),
|
|
33
|
-
id:
|
|
34
|
-
teamId:
|
|
35
|
-
year:
|
|
36
|
-
hours:
|
|
37
|
-
}).unique("byTeamYear", ["teamId", "year"]),
|
|
38
|
-
id:
|
|
39
|
-
title:
|
|
40
|
-
metadata:
|
|
41
|
-
labels:
|
|
17
|
+
}), me = C("voltro_parity_a", {
|
|
18
|
+
id: h(),
|
|
19
|
+
title: w(),
|
|
20
|
+
count: g()
|
|
21
|
+
}), he = C("voltro_parity_b", {
|
|
22
|
+
id: h(),
|
|
23
|
+
title: w()
|
|
24
|
+
}), ge = C("voltro_parity_c", {
|
|
25
|
+
id: h(),
|
|
26
|
+
title: w(),
|
|
27
|
+
count: g(),
|
|
28
|
+
flag: i().default(!1)
|
|
29
|
+
}), _e = C("voltro_parity_d", {
|
|
30
|
+
id: h(),
|
|
31
|
+
title: w(),
|
|
32
|
+
at: T().nullable().onUpdate("now"),
|
|
33
|
+
n: g().nullable()
|
|
34
|
+
}), ve = C("voltro_parity_upsert", {
|
|
35
|
+
id: h(),
|
|
36
|
+
teamId: w(),
|
|
37
|
+
year: g(),
|
|
38
|
+
hours: g().nullable()
|
|
39
|
+
}).unique("byTeamYear", ["teamId", "year"]), Z = C("voltro_parity_values", {
|
|
40
|
+
id: h(),
|
|
41
|
+
title: w().onUpdate(() => "automatic"),
|
|
42
|
+
metadata: _().nullable(),
|
|
43
|
+
labels: n(w()).nullable()
|
|
42
44
|
});
|
|
43
|
-
|
|
44
|
-
var
|
|
45
|
-
id:
|
|
46
|
-
key:
|
|
47
|
-
value:
|
|
45
|
+
b(Z);
|
|
46
|
+
var Q = C("voltro_parity_json_names", {
|
|
47
|
+
id: h(),
|
|
48
|
+
key: _(),
|
|
49
|
+
value: _()
|
|
48
50
|
});
|
|
49
|
-
|
|
50
|
-
var
|
|
51
|
-
userId:
|
|
52
|
-
orgId:
|
|
53
|
-
role:
|
|
54
|
-
}).primaryKey(["userId", "orgId"]),
|
|
55
|
-
id:
|
|
56
|
-
title:
|
|
57
|
-
count:
|
|
58
|
-
flag:
|
|
59
|
-
}),
|
|
60
|
-
let t =
|
|
51
|
+
b(Q);
|
|
52
|
+
var ye = C("voltro_parity_composite", {
|
|
53
|
+
userId: w(),
|
|
54
|
+
orgId: w(),
|
|
55
|
+
role: w()
|
|
56
|
+
}).primaryKey(["userId", "orgId"]), be = C("voltro_parity_bulk", {
|
|
57
|
+
id: h(),
|
|
58
|
+
title: w(),
|
|
59
|
+
count: g(),
|
|
60
|
+
flag: i().default(!1)
|
|
61
|
+
}), xe = (e) => {
|
|
62
|
+
let t = a(e);
|
|
61
63
|
if (t === void 0) return 0;
|
|
62
|
-
let n = Object.keys(
|
|
64
|
+
let n = Object.keys(be.fields).length;
|
|
63
65
|
return Math.max(1, Math.min(Math.floor(t.maxBindParameters / n), t.maxRowsPerStatement ?? Infinity));
|
|
64
|
-
},
|
|
65
|
-
|
|
66
|
-
let
|
|
67
|
-
|
|
68
|
-
await
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
66
|
+
}, $ = (e) => {
|
|
67
|
+
V(`dialect parity: ${e.name ?? e.dialect.id}`, () => {
|
|
68
|
+
let a = null;
|
|
69
|
+
de(async () => {
|
|
70
|
+
await e.setup(), a = await e.make(), await a.migrate([
|
|
71
|
+
me,
|
|
72
|
+
he,
|
|
73
|
+
ge,
|
|
74
|
+
_e,
|
|
75
|
+
be,
|
|
76
|
+
ve,
|
|
77
|
+
ye,
|
|
78
|
+
Z,
|
|
79
|
+
Q,
|
|
80
|
+
ie
|
|
79
81
|
]);
|
|
80
82
|
for (let e of [
|
|
81
83
|
"voltro_parity_a",
|
|
@@ -87,487 +89,883 @@ var K = h("voltro_parity_composite", {
|
|
|
87
89
|
"voltro_parity_json_names",
|
|
88
90
|
"_voltro_undo_log"
|
|
89
91
|
]) {
|
|
90
|
-
let t = await
|
|
91
|
-
for (let n of t) await
|
|
92
|
+
let t = await a.store.query(X(e));
|
|
93
|
+
for (let n of t) await a.store.delete(e, n.id);
|
|
92
94
|
}
|
|
93
|
-
for (let e of await
|
|
95
|
+
for (let e of await a.store.query(X("voltro_parity_composite"))) await a.store.delete("voltro_parity_composite", {
|
|
94
96
|
userId: e.userId,
|
|
95
97
|
orgId: e.orgId
|
|
96
98
|
});
|
|
97
|
-
}),
|
|
98
|
-
|
|
99
|
-
}),
|
|
100
|
-
let
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
}), ue(async () => {
|
|
100
|
+
E("voltro_parity_evolve"), await a?.dispose(), await e.teardown(), a = null;
|
|
101
|
+
}), V.each(["direct", "transactional"])("numeric and audit write contracts (%s)", (t) => {
|
|
102
|
+
let n = (e, n) => t === "transactional" ? e.transactional(n) : n(e), u = (e) => a.run(D.gen(function* () {
|
|
103
|
+
yield* (yield* G.SqlClient).unsafe(`DROP TABLE IF EXISTS ${e}`);
|
|
104
|
+
}));
|
|
105
|
+
U("aggregates return the types their declarations name", async () => {
|
|
106
|
+
let e = `voltro_aggtypes_${W().replaceAll("-", "").slice(0, 12)}`, t = C(e, {
|
|
107
|
+
id: h(),
|
|
108
|
+
title: w().maxLength(100),
|
|
109
|
+
entries: g(),
|
|
110
|
+
score: y(),
|
|
111
|
+
amount: d(10, 2)
|
|
112
|
+
}).nonReactive();
|
|
113
|
+
b(t);
|
|
114
|
+
try {
|
|
115
|
+
await a.migrate([t]);
|
|
116
|
+
let i = {
|
|
117
|
+
title: s("title"),
|
|
118
|
+
n: c(),
|
|
119
|
+
d: l("entries"),
|
|
120
|
+
total: S("entries"),
|
|
121
|
+
money: S("amount"),
|
|
122
|
+
mean: r("score"),
|
|
123
|
+
most: v("entries"),
|
|
124
|
+
least: ee("amount")
|
|
125
|
+
}, o = await n(a.store, (t) => t.query({
|
|
126
|
+
...X(e),
|
|
127
|
+
aggregations: Object.entries({
|
|
128
|
+
n: c(),
|
|
129
|
+
total: S("entries")
|
|
130
|
+
}).map(([e, t]) => ({
|
|
131
|
+
...t,
|
|
132
|
+
alias: e
|
|
133
|
+
}))
|
|
134
|
+
}));
|
|
135
|
+
H(o).toEqual([{
|
|
136
|
+
n: 0,
|
|
137
|
+
total: null
|
|
138
|
+
}]), await n(a.store, async (t) => {
|
|
139
|
+
await t.insert(e, {
|
|
140
|
+
id: "agg_1",
|
|
141
|
+
title: "a",
|
|
142
|
+
entries: 3,
|
|
143
|
+
score: 1.5,
|
|
144
|
+
amount: "2.25"
|
|
145
|
+
}), await t.insert(e, {
|
|
146
|
+
id: "agg_2",
|
|
147
|
+
title: "a",
|
|
148
|
+
entries: 4,
|
|
149
|
+
score: 2.5,
|
|
150
|
+
amount: "3.25"
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
let u = await n(a.store, (t) => t.query({
|
|
154
|
+
...X(e),
|
|
155
|
+
groupBy: ["title"],
|
|
156
|
+
aggregations: Object.entries(i).map(([e, t]) => ({
|
|
157
|
+
...t,
|
|
158
|
+
alias: e
|
|
159
|
+
}))
|
|
160
|
+
}));
|
|
161
|
+
H(u).toHaveLength(1);
|
|
162
|
+
let d = u[0];
|
|
163
|
+
H(d).toMatchObject({
|
|
164
|
+
title: "a",
|
|
165
|
+
n: 2,
|
|
166
|
+
d: 2,
|
|
167
|
+
total: 7,
|
|
168
|
+
mean: 2,
|
|
169
|
+
most: 4
|
|
170
|
+
});
|
|
171
|
+
for (let e of [
|
|
172
|
+
"n",
|
|
173
|
+
"d",
|
|
174
|
+
"total",
|
|
175
|
+
"mean",
|
|
176
|
+
"most"
|
|
177
|
+
]) H(typeof d[e], e).toBe("number");
|
|
178
|
+
H(d.money).toBe("5.50"), H(d.least).toBe("2.25");
|
|
179
|
+
} finally {
|
|
180
|
+
E(t.tableName), await u(e);
|
|
181
|
+
}
|
|
182
|
+
}), U("insertMany { returning: 'none' } writes every row, resolves to [] and keeps change events", async () => {
|
|
183
|
+
let t = W().replaceAll("-", "").slice(0, 12), r = `voltro_quiet_${t}`, o = `voltro_loud_${t}`, s = {
|
|
184
|
+
id: h(),
|
|
185
|
+
title: w(),
|
|
186
|
+
count: g(),
|
|
187
|
+
flag: i().default(!1)
|
|
188
|
+
}, c = C(r, s).nonReactive(), l = C(o, s);
|
|
189
|
+
b(c), b(l);
|
|
190
|
+
try {
|
|
191
|
+
await a.migrate([c, l]);
|
|
192
|
+
let i = xe(e.dialect.id) + 5, s = (e, t) => Array.from({ length: t }, (t, n) => ({
|
|
193
|
+
id: `${e.slice(7, 12)}_${String(n).padStart(6, "0")}`,
|
|
194
|
+
title: `row ${n}`,
|
|
195
|
+
count: n,
|
|
196
|
+
flag: n % 2 == 0
|
|
197
|
+
}));
|
|
198
|
+
H(await n(a.store, (e) => e.insertMany(r, s(r, i), { returning: "none" }))).toEqual([]);
|
|
199
|
+
let u = await a.store.query(X(r));
|
|
200
|
+
H(u).toHaveLength(i), H(u.find((e) => e.id === `${r.slice(7, 12)}_000007`)).toMatchObject({
|
|
201
|
+
count: 7,
|
|
202
|
+
flag: !1
|
|
203
|
+
});
|
|
204
|
+
let d = [], f = a.store.onChange((e) => {
|
|
205
|
+
e.table === o && e.op === "insert" && d.push(e.new);
|
|
206
|
+
});
|
|
207
|
+
try {
|
|
208
|
+
H(await n(a.store, (e) => e.insertMany(o, s(o, 3), { returning: "none" }))).toEqual([]);
|
|
209
|
+
for (let e = 0; d.length < 3 && e < 5e3; e += 50) await new Promise((e) => setTimeout(e, 50));
|
|
210
|
+
H(d.map((e) => e?.count).sort()).toEqual([
|
|
211
|
+
0,
|
|
212
|
+
1,
|
|
213
|
+
2
|
|
214
|
+
]);
|
|
215
|
+
} finally {
|
|
216
|
+
f();
|
|
217
|
+
}
|
|
218
|
+
let p = await n(a.store, (e) => e.insertMany(o, [{
|
|
219
|
+
id: `${t}_ctl`,
|
|
220
|
+
title: "control",
|
|
221
|
+
count: 9
|
|
222
|
+
}]));
|
|
223
|
+
H(p).toHaveLength(1), H(p[0]).toMatchObject({
|
|
224
|
+
id: `${t}_ctl`,
|
|
225
|
+
count: 9
|
|
226
|
+
});
|
|
227
|
+
} finally {
|
|
228
|
+
E(r), E(o), await u(r), await u(o);
|
|
229
|
+
}
|
|
230
|
+
}, 12e4), U(".encrypted() values name their key, old values still read, and a key change keeps both readable", async () => {
|
|
231
|
+
let e = `voltro_keyed_${W().replaceAll("-", "").slice(0, 12)}`, t = C(e, {
|
|
232
|
+
id: h({ prefix: "sec" }),
|
|
233
|
+
token: w().encrypted()
|
|
234
|
+
}).nonReactive();
|
|
235
|
+
b(t);
|
|
236
|
+
let r = oe(), i = "a".repeat(64), s = "b".repeat(64);
|
|
237
|
+
try {
|
|
238
|
+
await a.migrate([t]);
|
|
239
|
+
let r = F([{
|
|
240
|
+
tableName: t.tableName,
|
|
241
|
+
fields: t.fields
|
|
242
|
+
}]), c = B(a.store, {
|
|
243
|
+
subject: {
|
|
244
|
+
type: "user",
|
|
245
|
+
id: "keyed",
|
|
246
|
+
tenantId: "org_keyed"
|
|
247
|
+
},
|
|
248
|
+
schemaRegistry: r
|
|
249
|
+
}), l = async (t) => (await a.store.query({
|
|
250
|
+
...X(e),
|
|
251
|
+
predicate: p("id", t)
|
|
252
|
+
}))[0]?.token;
|
|
253
|
+
L(P({
|
|
254
|
+
active: "k2026a",
|
|
255
|
+
keys: [{
|
|
256
|
+
id: "k2026a",
|
|
257
|
+
secret: i
|
|
258
|
+
}]
|
|
259
|
+
}));
|
|
260
|
+
let u = await n(c, (t) => t.insert(e, {
|
|
261
|
+
id: "sec_first",
|
|
262
|
+
token: "pat-first"
|
|
263
|
+
}));
|
|
264
|
+
H(u.token).toBe("pat-first"), H(o(await l("sec_first"))).toBe("k2026a");
|
|
265
|
+
let d = ce(ae(i)).encrypt(JSON.stringify("pat-legacy"), m(e, "token", "sec_legacy"));
|
|
266
|
+
await a.store.insert(e, {
|
|
267
|
+
id: "sec_legacy",
|
|
268
|
+
token: d
|
|
269
|
+
}), H(o(await l("sec_legacy"))).toBeUndefined(), L(P({
|
|
270
|
+
active: "k2027",
|
|
271
|
+
keys: [{
|
|
272
|
+
id: "k2027",
|
|
273
|
+
secret: s
|
|
274
|
+
}, {
|
|
275
|
+
id: "k2026a",
|
|
276
|
+
secret: i
|
|
277
|
+
}]
|
|
278
|
+
}));
|
|
279
|
+
let f = await n(c, (t) => t.query(X(e)));
|
|
280
|
+
H(Object.fromEntries(f.map((e) => [e.id, e.token]))).toEqual({
|
|
281
|
+
sec_first: "pat-first",
|
|
282
|
+
sec_legacy: "pat-legacy"
|
|
283
|
+
}), await n(c, (t) => t.update(e, "sec_first", { token: "pat-rotated" })), H(o(await l("sec_first"))).toBe("k2027"), H((await n(c, (t) => t.query(X(e)))).find((e) => e.id === "sec_first")?.token).toBe("pat-rotated");
|
|
284
|
+
} finally {
|
|
285
|
+
L(r), E(e), await u(e);
|
|
286
|
+
}
|
|
287
|
+
}), U("the boot check refuses a wrong key, a key under a registered id and a missing key; racing activations leave one active key", async () => {
|
|
288
|
+
let e = `voltro_keycheck_${W().replaceAll("-", "").slice(0, 12)}`, t = C(e, {
|
|
289
|
+
id: h({ prefix: "sec" }),
|
|
290
|
+
token: w().encrypted()
|
|
291
|
+
}).nonReactive();
|
|
292
|
+
b(t), b(M), b(j);
|
|
293
|
+
let r = oe(), i = "c".repeat(64), o = "d".repeat(64), s = "e".repeat(64), c = (e, t) => P({
|
|
294
|
+
active: e,
|
|
295
|
+
keys: t.map(([e, t]) => ({
|
|
296
|
+
id: e,
|
|
297
|
+
secret: t
|
|
298
|
+
}))
|
|
299
|
+
}), l = async (e) => {
|
|
300
|
+
let t = await e.then(() => void 0, (e) => e);
|
|
301
|
+
return H(t).toBeInstanceOf(A), t.message;
|
|
302
|
+
};
|
|
303
|
+
try {
|
|
304
|
+
await u(O), await u(k), await a.migrate([
|
|
305
|
+
t,
|
|
306
|
+
M,
|
|
307
|
+
j
|
|
308
|
+
]);
|
|
309
|
+
let r = { holderId: "parity-app" }, d = F([{
|
|
310
|
+
tableName: t.tableName,
|
|
311
|
+
fields: t.fields
|
|
312
|
+
}]), f = B(a.store, {
|
|
313
|
+
subject: {
|
|
314
|
+
type: "user",
|
|
315
|
+
id: "keycheck",
|
|
316
|
+
tenantId: "org_keycheck"
|
|
317
|
+
},
|
|
318
|
+
schemaRegistry: d
|
|
319
|
+
});
|
|
320
|
+
L(c("ka", [["ka", i]]));
|
|
321
|
+
for (let t of [
|
|
322
|
+
"sec_1",
|
|
323
|
+
"sec_2",
|
|
324
|
+
"sec_3"
|
|
325
|
+
]) await n(f, (n) => n.insert(e, {
|
|
326
|
+
id: t,
|
|
327
|
+
token: `pat-${t}`
|
|
328
|
+
}));
|
|
329
|
+
H(await l(z(a.store, c("ka", [["ka", o]]), [t], r))).toContain(`${e}.token`), H(await I(a.store)).toEqual([]), H(await z(a.store, c("ka", [["ka", i]]), [t], r)).toMatchObject({
|
|
330
|
+
active: "ka",
|
|
331
|
+
registered: ["ka"]
|
|
332
|
+
}), H(await l(z(a.store, c("ka", [["ka", o]]), [], r))).toContain("is not the key this database registered as \"ka\""), await z(a.store, c("ka", [
|
|
333
|
+
["ka", i],
|
|
334
|
+
["kb", o],
|
|
335
|
+
["kc", s]
|
|
336
|
+
]), [t], r), await Promise.all([z(a.store, c("kb", [
|
|
337
|
+
["kb", o],
|
|
338
|
+
["ka", i],
|
|
339
|
+
["kc", s]
|
|
340
|
+
]), [t], { holderId: "parity-b" }), z(a.store, c("kc", [
|
|
341
|
+
["kc", s],
|
|
342
|
+
["ka", i],
|
|
343
|
+
["kb", o]
|
|
344
|
+
]), [t], { holderId: "parity-c" })]);
|
|
345
|
+
let p = await I(a.store);
|
|
346
|
+
H(p.filter((e) => e.state === "active"), JSON.stringify(p)).toHaveLength(1);
|
|
347
|
+
let m = p.find((e) => e.state === "active").keyId;
|
|
348
|
+
H(await l(z(a.store, c("ka", [["ka", i]]), [t], r))).toContain(`"${m}" (active)`), L(c(m, [
|
|
349
|
+
["ka", i],
|
|
350
|
+
["kb", o],
|
|
351
|
+
["kc", s]
|
|
352
|
+
])), H((await n(f, (t) => t.query(X(e)))).map((e) => e.token).sort()).toEqual([
|
|
353
|
+
"pat-sec_1",
|
|
354
|
+
"pat-sec_2",
|
|
355
|
+
"pat-sec_3"
|
|
356
|
+
]);
|
|
357
|
+
} finally {
|
|
358
|
+
L(r), E(e), E(O), E(k), await u(e), await u(O), await u(k);
|
|
359
|
+
}
|
|
360
|
+
}), U("a requested key waits for a live process that lacks it; a boot racing the activation is waited for or refused", async () => {
|
|
361
|
+
b(M), b(j);
|
|
362
|
+
let e = oe(), t = "f".repeat(64), n = "1".repeat(64), r = (e, t) => P({
|
|
363
|
+
active: e,
|
|
364
|
+
keys: t.map(([e, t]) => ({
|
|
365
|
+
id: e,
|
|
366
|
+
secret: t
|
|
367
|
+
}))
|
|
368
|
+
}), i = Date.parse("2026-10-30T10:00:00Z"), s = (e) => ({
|
|
369
|
+
holderId: e,
|
|
370
|
+
leaseMs: 3e4,
|
|
371
|
+
now: () => i
|
|
372
|
+
}), c = async () => {
|
|
373
|
+
await u(O), await u(k), await a.migrate([M, j]);
|
|
374
|
+
};
|
|
375
|
+
try {
|
|
376
|
+
await c();
|
|
377
|
+
let e = r("ka", [["ka", t]]);
|
|
378
|
+
await z(a.store, e, [], s("old"));
|
|
379
|
+
let l = r("kb", [["kb", n], ["ka", t]]);
|
|
380
|
+
H(await z(a.store, l, [], s("new"))).toMatchObject({
|
|
381
|
+
active: "ka",
|
|
382
|
+
waiting: {
|
|
383
|
+
keyId: "kb",
|
|
384
|
+
holders: ["old"]
|
|
385
|
+
}
|
|
386
|
+
}), H(o(l.encrypt("x"))).toBe("ka"), i += 1e4, H((await se(a.store, e, s("old"))).active).toBe("ka"), H((await se(a.store, l, s("new"))).active).toBe("ka"), i += 31e3, H(await se(a.store, l, s("new"))).toMatchObject({
|
|
387
|
+
active: "kb",
|
|
388
|
+
activated: {
|
|
389
|
+
from: "ka",
|
|
390
|
+
to: "kb"
|
|
391
|
+
}
|
|
392
|
+
}), H((await I(a.store)).map((e) => [e.keyId, e.state]).sort()).toEqual([["ka", "decrypt-only"], ["kb", "active"]]), await le(a.store, "new");
|
|
393
|
+
for (let e = 0; e < 4; e++) {
|
|
394
|
+
await c(), await z(a.store, r("ka", [["ka", t]]), [], s("seed")), await le(a.store, "seed");
|
|
395
|
+
let [i, o] = await Promise.allSettled([z(a.store, r("ka", [["ka", t]]), [], s("late")), z(a.store, r("kb", [["kb", n], ["ka", t]]), [], s("rotating"))]);
|
|
396
|
+
H(o.status).toBe("fulfilled");
|
|
397
|
+
let l = (await I(a.store)).find((e) => e.state === "active")?.keyId;
|
|
398
|
+
i.status === "fulfilled" ? H(l, `round ${e}: a live process without kb, and kb active`).toBe("ka") : H(String(i.reason.message), `round ${e}`).toContain("\"kb\" (active), which this process does not hold");
|
|
399
|
+
}
|
|
400
|
+
} finally {
|
|
401
|
+
L(e), E(O), E(k), await u(O), await u(k);
|
|
402
|
+
}
|
|
403
|
+
}), U("returns numeric ids consistently and accepts them for tenant-scoped keyed writes", async () => {
|
|
404
|
+
let e = `voltro_numeric_${W().replaceAll("-", "").slice(0, 12)}`, t = C(e, {
|
|
405
|
+
id: h({ scheme: "numeric" }),
|
|
406
|
+
tenantId: w().maxLength(100),
|
|
407
|
+
title: w().maxLength(100),
|
|
408
|
+
metadata: _()
|
|
409
|
+
}).nonReactive();
|
|
410
|
+
b(t);
|
|
411
|
+
try {
|
|
412
|
+
await a.migrate([t]);
|
|
413
|
+
let r = F([{
|
|
414
|
+
tableName: t.tableName,
|
|
415
|
+
fields: t.fields,
|
|
416
|
+
appliedMixins: [{ id: "voltro/tenant" }]
|
|
417
|
+
}]), i = (e) => B(a.store, {
|
|
418
|
+
subject: {
|
|
419
|
+
type: "user",
|
|
420
|
+
id: "numeric_writer",
|
|
421
|
+
tenantId: e
|
|
422
|
+
},
|
|
423
|
+
schemaRegistry: r
|
|
424
|
+
});
|
|
425
|
+
await n(i("owner"), async (t) => {
|
|
426
|
+
for (let n of [
|
|
427
|
+
"update",
|
|
428
|
+
"patchJson",
|
|
429
|
+
"delete"
|
|
430
|
+
]) {
|
|
431
|
+
let r = await t.insert(e, {
|
|
432
|
+
title: n,
|
|
433
|
+
metadata: {}
|
|
434
|
+
});
|
|
435
|
+
H(typeof r.id).toBe("number"), H(Number.isSafeInteger(r.id)).toBe(!0), n === "update" ? H(await t.update(e, r.id, { title: "edited" })).toMatchObject({
|
|
436
|
+
id: r.id,
|
|
437
|
+
title: "edited"
|
|
438
|
+
}) : n === "patchJson" ? H(await t.patchJson(e, r.id, "metadata.seen", !0)).toMatchObject({
|
|
439
|
+
id: r.id,
|
|
440
|
+
metadata: { seen: !0 }
|
|
441
|
+
}) : H(await t.delete(e, r.id)).toBe(!0);
|
|
442
|
+
}
|
|
443
|
+
let n = await t.query(X(e));
|
|
444
|
+
H(n).toHaveLength(2), H(n.every((e) => typeof e.id == "number")).toBe(!0);
|
|
445
|
+
});
|
|
446
|
+
let [o] = await a.store.query(X(e));
|
|
447
|
+
await H(i("other").update(e, o.id, { title: "stolen" })).rejects.toMatchObject({ _tag: "TenantRowNotFound" });
|
|
448
|
+
} finally {
|
|
449
|
+
E(e), await u(e);
|
|
450
|
+
}
|
|
451
|
+
}), U("preserves creation stamps during default audited upserts and still stamps updates", async () => {
|
|
452
|
+
let e = `voltro_audit_${W().replaceAll("-", "").slice(0, 12)}`, t = C(e, {
|
|
453
|
+
id: h({ prefix: "audit" }),
|
|
454
|
+
externalId: w().maxLength(100).unique(),
|
|
455
|
+
value: w(),
|
|
456
|
+
createdAt: T(),
|
|
457
|
+
createdBy: w().nullable(),
|
|
458
|
+
updatedAt: T(),
|
|
459
|
+
updatedBy: w().nullable()
|
|
460
|
+
}).nonReactive();
|
|
461
|
+
b(t);
|
|
462
|
+
try {
|
|
463
|
+
await a.migrate([t]);
|
|
464
|
+
let r = F([{
|
|
465
|
+
tableName: t.tableName,
|
|
466
|
+
fields: t.fields,
|
|
467
|
+
appliedMixins: [{ id: "voltro/audit" }]
|
|
468
|
+
}]), i = B(a.store, {
|
|
469
|
+
subject: {
|
|
470
|
+
type: "user",
|
|
471
|
+
id: "editor",
|
|
472
|
+
tenantId: "audit_scope"
|
|
473
|
+
},
|
|
474
|
+
schemaRegistry: r
|
|
475
|
+
}), o = /* @__PURE__ */ new Date("2026-01-01T00:00:00Z");
|
|
476
|
+
await n(i, async (t) => {
|
|
477
|
+
for (let n of ["upsert", "upsertWithOutcome"]) {
|
|
478
|
+
await t.insert(e, {
|
|
479
|
+
externalId: n,
|
|
480
|
+
value: "before",
|
|
481
|
+
createdAt: o,
|
|
482
|
+
createdBy: "creator",
|
|
483
|
+
updatedAt: o
|
|
484
|
+
});
|
|
485
|
+
let r = n === "upsert" ? await t.upsert(e, {
|
|
486
|
+
externalId: n,
|
|
487
|
+
value: "after"
|
|
488
|
+
}, { conflictColumns: ["externalId"] }) : (await t.upsertWithOutcome(e, {
|
|
489
|
+
externalId: n,
|
|
490
|
+
value: "after"
|
|
491
|
+
}, { conflictColumns: ["externalId"] })).row;
|
|
492
|
+
H(r).toMatchObject({
|
|
493
|
+
value: "after",
|
|
494
|
+
createdBy: "creator",
|
|
495
|
+
updatedBy: "editor"
|
|
496
|
+
}), H(new Date(String(r.createdAt)).toISOString()).toBe(o.toISOString()), H(new Date(String(r.updatedAt)).getTime()).toBeGreaterThan(o.getTime());
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
} finally {
|
|
500
|
+
E(e), await u(e);
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
}), U("rename-table: the rows move, and the plan converges", async () => {
|
|
504
|
+
let n = "voltro_rename_src", r = "_voltro_rename_dst", i = a.run, o = async () => {
|
|
505
|
+
await i(D.gen(function* () {
|
|
506
|
+
let e = yield* G.SqlClient;
|
|
507
|
+
for (let t of [r, n]) yield* D.orElseSucceed(e.unsafe(`DROP TABLE IF EXISTS ${t}`), () => []);
|
|
104
508
|
})).catch(() => {});
|
|
105
|
-
}, s = () =>
|
|
106
|
-
let e = yield*
|
|
509
|
+
}, s = () => i(D.gen(function* () {
|
|
510
|
+
let e = yield* G.SqlClient, t = yield* J(e);
|
|
107
511
|
return {
|
|
108
|
-
...
|
|
109
|
-
tables:
|
|
512
|
+
...t,
|
|
513
|
+
tables: t.tables.filter((e) => e.name === n || e.name === r)
|
|
110
514
|
};
|
|
111
|
-
})),
|
|
112
|
-
declared: [
|
|
515
|
+
})), c = async () => Y({
|
|
516
|
+
declared: [u],
|
|
113
517
|
live: await s(),
|
|
114
|
-
dialect:
|
|
518
|
+
dialect: e.dialect.id
|
|
115
519
|
});
|
|
116
520
|
await o();
|
|
117
|
-
let
|
|
118
|
-
id:
|
|
119
|
-
title:
|
|
120
|
-
}),
|
|
121
|
-
id:
|
|
122
|
-
title:
|
|
123
|
-
}).renamedFrom(
|
|
124
|
-
await
|
|
521
|
+
let l = C(n, {
|
|
522
|
+
id: h({ prefix: "rn" }),
|
|
523
|
+
title: w()
|
|
524
|
+
}), u = C(r, {
|
|
525
|
+
id: h({ prefix: "rn" }),
|
|
526
|
+
title: w()
|
|
527
|
+
}).renamedFrom(n);
|
|
528
|
+
await a.migrate([l, t]), await a.store.insert(n, {
|
|
125
529
|
id: "rn_1",
|
|
126
530
|
title: "one"
|
|
127
|
-
}), await
|
|
531
|
+
}), await a.store.insert(n, {
|
|
128
532
|
id: "rn_2",
|
|
129
533
|
title: "two"
|
|
130
534
|
});
|
|
131
|
-
let
|
|
132
|
-
|
|
133
|
-
let e = yield*
|
|
134
|
-
return yield*
|
|
135
|
-
declared: [
|
|
535
|
+
let d = await c();
|
|
536
|
+
H(d.operations.some((e) => e.op.kind === "rename-table"), "the marker must fold").toBe(!0), H(d.operations.filter((e) => e.blocked !== void 0), "nothing may be blocked").toEqual([]), await i(D.gen(function* () {
|
|
537
|
+
let e = yield* G.SqlClient;
|
|
538
|
+
return yield* K(e, d, {
|
|
539
|
+
declared: [u],
|
|
136
540
|
appliedBy: "dialect-parity",
|
|
137
541
|
environment: "dev",
|
|
138
542
|
source: "auto-diff",
|
|
139
|
-
replan: () =>
|
|
543
|
+
replan: () => D.promise(c)
|
|
140
544
|
});
|
|
141
545
|
}));
|
|
142
|
-
let
|
|
143
|
-
|
|
144
|
-
}),
|
|
145
|
-
let
|
|
146
|
-
await
|
|
147
|
-
let e = yield*
|
|
148
|
-
yield*
|
|
546
|
+
let f = await a.store.query(X(r));
|
|
547
|
+
H(f.map((e) => e.id).sort(), "every row survived the rename").toEqual(["rn_1", "rn_2"]), H((await c()).operations, `${e.dialect.id}: the plan must converge`).toEqual([]), await o();
|
|
548
|
+
}), U("schema evolution: create → add column → add index → drop column, converging at each step", async () => {
|
|
549
|
+
let n = "voltro_parity_evolve", r = a.run, i = async () => {
|
|
550
|
+
await r(D.gen(function* () {
|
|
551
|
+
let e = yield* G.SqlClient;
|
|
552
|
+
yield* D.orElseSucceed(e.unsafe(`DROP TABLE IF EXISTS ${n}`), () => []), yield* D.orElseSucceed(e.unsafe("DROP TYPE IF EXISTS ev_status"), () => []), yield* D.orElseSucceed(e.unsafe("DELETE FROM _voltro_migration_ops"), () => []);
|
|
149
553
|
})).catch(() => {});
|
|
150
|
-
},
|
|
151
|
-
let e = yield*
|
|
554
|
+
}, o = () => r(D.gen(function* () {
|
|
555
|
+
let e = yield* G.SqlClient, t = yield* J(e);
|
|
152
556
|
return {
|
|
153
|
-
...
|
|
154
|
-
tables:
|
|
155
|
-
};
|
|
156
|
-
})), m = async (e) => L({
|
|
157
|
-
declared: e,
|
|
158
|
-
live: await p(),
|
|
159
|
-
dialect: n.dialect.id
|
|
160
|
-
}), y = async () => (await p()).tables[0]?.columns.map((e) => e.name) ?? [], x = async (e, r) => {
|
|
161
|
-
let i = await m(e);
|
|
162
|
-
r && k(i.operations.length, `${n.dialect.id}: nothing to apply — the step asserts nothing`).toBeGreaterThan(0), k(i.operations.filter((e) => e.blocked !== void 0), `${n.dialect.id}: nothing may be blocked`).toEqual([]);
|
|
163
|
-
let a = () => {
|
|
164
|
-
for (let t of e) v(t.tableName), f(t);
|
|
557
|
+
...t,
|
|
558
|
+
tables: t.tables.filter((e) => e.name === n)
|
|
165
559
|
};
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
560
|
+
})), s = async (t) => Y({
|
|
561
|
+
declared: t,
|
|
562
|
+
live: await o(),
|
|
563
|
+
dialect: e.dialect.id
|
|
564
|
+
}), c = async () => (await o()).tables[0]?.columns.map((e) => e.name) ?? [], l = async (t, i) => {
|
|
565
|
+
let a = await s(t);
|
|
566
|
+
i && H(a.operations.length, `${e.dialect.id}: nothing to apply — the step asserts nothing`).toBeGreaterThan(0), H(a.operations.filter((e) => e.blocked !== void 0), `${e.dialect.id}: nothing may be blocked`).toEqual([]), a.operations.length === 0 || (await r(D.gen(function* () {
|
|
567
|
+
let r = yield* G.SqlClient;
|
|
568
|
+
return yield* K(r, a, {
|
|
569
|
+
declared: t,
|
|
174
570
|
appliedBy: "dialect-parity",
|
|
175
571
|
environment: "dev",
|
|
176
572
|
source: "auto-diff",
|
|
177
|
-
replan: (r) =>
|
|
178
|
-
declared:
|
|
573
|
+
replan: (r) => D.map(J(r), (r) => Y({
|
|
574
|
+
declared: t,
|
|
179
575
|
live: {
|
|
180
576
|
...r,
|
|
181
|
-
tables: r.tables.filter((e) => e.name ===
|
|
577
|
+
tables: r.tables.filter((e) => e.name === n)
|
|
182
578
|
},
|
|
183
|
-
dialect:
|
|
579
|
+
dialect: e.dialect.id
|
|
184
580
|
}))
|
|
185
581
|
});
|
|
186
|
-
})),
|
|
582
|
+
})), H((await s(t)).operations, `${e.dialect.id}: the plan must converge`).toEqual([])), (() => {
|
|
583
|
+
for (let e of t) E(e.tableName), b(e);
|
|
584
|
+
})();
|
|
187
585
|
};
|
|
188
|
-
await
|
|
189
|
-
id:
|
|
190
|
-
name:
|
|
191
|
-
})], !0),
|
|
586
|
+
await i(), await a.migrate([t]), await l([C(n, {
|
|
587
|
+
id: h({ prefix: "ev" }),
|
|
588
|
+
name: w()
|
|
589
|
+
})], !0), H(await c(), `${e.dialect.id}: v1 columns`).toEqual(["id", "name"]), await a.store.insert(n, {
|
|
192
590
|
id: "ev_1",
|
|
193
591
|
name: "one"
|
|
194
|
-
}), await
|
|
592
|
+
}), await a.store.insert(n, {
|
|
195
593
|
id: "ev_2",
|
|
196
594
|
name: "two"
|
|
197
|
-
}), await
|
|
198
|
-
id:
|
|
199
|
-
name:
|
|
200
|
-
qty:
|
|
201
|
-
})], !0),
|
|
202
|
-
id:
|
|
203
|
-
name:
|
|
204
|
-
qty:
|
|
205
|
-
}).index(["qty"])], !0), await
|
|
206
|
-
id:
|
|
207
|
-
name:
|
|
208
|
-
qty:
|
|
209
|
-
})], !0),
|
|
210
|
-
let
|
|
211
|
-
id:
|
|
212
|
-
name:
|
|
213
|
-
amount:
|
|
595
|
+
}), await l([C(n, {
|
|
596
|
+
id: h({ prefix: "ev" }),
|
|
597
|
+
name: w(),
|
|
598
|
+
qty: g().nullable()
|
|
599
|
+
})], !0), H(await c()).toContain("qty"), await l([C(n, {
|
|
600
|
+
id: h({ prefix: "ev" }),
|
|
601
|
+
name: w(),
|
|
602
|
+
qty: g().nullable()
|
|
603
|
+
}).index(["qty"])], !0), await l([C(n, {
|
|
604
|
+
id: h({ prefix: "ev" }),
|
|
605
|
+
name: w(),
|
|
606
|
+
qty: f()
|
|
607
|
+
})], !0), H(await c(), `${e.dialect.id}: v4 columns`).toEqual(["id", "name"]);
|
|
608
|
+
let p = e.dialect.id === "sqlite" || e.dialect.id === "turso", m = C(n, {
|
|
609
|
+
id: h({ prefix: "ev" }),
|
|
610
|
+
name: w(),
|
|
611
|
+
amount: d(10, 2).nullable()
|
|
214
612
|
});
|
|
215
|
-
await
|
|
613
|
+
await l([m], !0), await a.store.insert(n, {
|
|
216
614
|
id: "ev_3",
|
|
217
615
|
name: "three",
|
|
218
616
|
amount: "12.34"
|
|
219
617
|
});
|
|
220
|
-
let
|
|
221
|
-
id:
|
|
222
|
-
name:
|
|
223
|
-
amount:
|
|
618
|
+
let _ = C(n, {
|
|
619
|
+
id: h({ prefix: "ev" }),
|
|
620
|
+
name: w(),
|
|
621
|
+
amount: d(12, 4).nullable()
|
|
224
622
|
});
|
|
225
|
-
if (
|
|
226
|
-
let e = await
|
|
227
|
-
|
|
623
|
+
if (p) {
|
|
624
|
+
let e = await o(), t = await s([_]);
|
|
625
|
+
H(t.operations).toHaveLength(1), H(t.operations[0]).toMatchObject({
|
|
228
626
|
op: {
|
|
229
627
|
kind: "alter-column-type",
|
|
230
|
-
table:
|
|
628
|
+
table: n,
|
|
231
629
|
column: "amount",
|
|
232
630
|
from: "decimal",
|
|
233
631
|
to: "decimal"
|
|
234
632
|
},
|
|
235
633
|
classification: "multi-step",
|
|
236
|
-
blocked: { fix:
|
|
237
|
-
}),
|
|
238
|
-
let
|
|
239
|
-
|
|
240
|
-
} else await
|
|
241
|
-
let
|
|
242
|
-
|
|
243
|
-
p:
|
|
244
|
-
s:
|
|
245
|
-
}, `${
|
|
246
|
-
p:
|
|
247
|
-
s:
|
|
634
|
+
blocked: { fix: H.stringContaining("preserves precision") }
|
|
635
|
+
}), H(await o(), "Planning conversion must leave the physical schema unchanged").toEqual(e);
|
|
636
|
+
let r = await a.store.query(X(n));
|
|
637
|
+
H(r.find((e) => e.id === "ev_3")?.amount).toBe("12.34"), H((await s([m])).operations, "The original precision must still converge").toEqual([]);
|
|
638
|
+
} else await l([_], !0);
|
|
639
|
+
let v = p ? 10 : 12, ee = p ? 2 : 4, y = () => d(v, ee).nullable(), x = (await o()).tables[0]?.columns.find((e) => e.name === "amount");
|
|
640
|
+
H({
|
|
641
|
+
p: x?.precision,
|
|
642
|
+
s: x?.scale
|
|
643
|
+
}, `${e.dialect.id}: the accepted size is live`).toEqual({
|
|
644
|
+
p: v,
|
|
645
|
+
s: ee
|
|
248
646
|
});
|
|
249
|
-
let
|
|
250
|
-
id:
|
|
251
|
-
name:
|
|
252
|
-
amount:
|
|
253
|
-
status:
|
|
647
|
+
let ne = u("ev_status", ["open", "closed"]), S = C(n, {
|
|
648
|
+
id: h({ prefix: "ev" }),
|
|
649
|
+
name: w(),
|
|
650
|
+
amount: y(),
|
|
651
|
+
status: ne.column().nullable()
|
|
254
652
|
});
|
|
255
|
-
await
|
|
653
|
+
await l([S], !0), H(await c(), `${e.dialect.id}: the enum column is live`).toContain("status"), await a.store.insert(n, {
|
|
256
654
|
id: "ev_4",
|
|
257
655
|
name: "four",
|
|
258
656
|
status: "closed"
|
|
259
657
|
});
|
|
260
|
-
let
|
|
261
|
-
name:
|
|
658
|
+
let re = (e) => pe({ tables: [{
|
|
659
|
+
name: n,
|
|
262
660
|
columns: e.filter((e) => e.name === "status"),
|
|
263
661
|
indexes: []
|
|
264
|
-
}] }),
|
|
265
|
-
|
|
266
|
-
let
|
|
267
|
-
|
|
268
|
-
let
|
|
662
|
+
}] }), O = (await o()).tables.find((e) => e.name === n)?.columns ?? [];
|
|
663
|
+
H(O.some((e) => e.name === "status"), `${e.dialect.id}: nothing to compare`).toBe(!0);
|
|
664
|
+
let k = fe([S], e.dialect.id).tables[0].columns, A = (e) => JSON.stringify(e.find((e) => e.name === "status"));
|
|
665
|
+
H(re(O), `${e.dialect.id}: the enum column must not part the two readers\n live: ${A(O)}\n declared: ${A(k)}`).toBe(re(k));
|
|
666
|
+
let j = u("ev_status", [
|
|
269
667
|
"open",
|
|
270
668
|
"closed",
|
|
271
669
|
"held"
|
|
272
|
-
]),
|
|
273
|
-
id:
|
|
274
|
-
name:
|
|
275
|
-
amount:
|
|
276
|
-
status:
|
|
670
|
+
]), M = C(n, {
|
|
671
|
+
id: h({ prefix: "ev" }),
|
|
672
|
+
name: w(),
|
|
673
|
+
amount: y(),
|
|
674
|
+
status: j.column().nullable()
|
|
277
675
|
});
|
|
278
|
-
await
|
|
676
|
+
await l([M], !0), await a.store.insert(n, {
|
|
279
677
|
id: "ev_5",
|
|
280
678
|
name: "five",
|
|
281
679
|
status: "held"
|
|
282
|
-
}), await
|
|
283
|
-
id:
|
|
284
|
-
name:
|
|
285
|
-
amount:
|
|
286
|
-
status:
|
|
287
|
-
parentId:
|
|
288
|
-
})], !0), await
|
|
680
|
+
}), await l([C(n, {
|
|
681
|
+
id: h({ prefix: "ev" }),
|
|
682
|
+
name: w(),
|
|
683
|
+
amount: y(),
|
|
684
|
+
status: j.column().nullable(),
|
|
685
|
+
parentId: te(() => M).nullable()
|
|
686
|
+
})], !0), await a.store.insert(n, {
|
|
289
687
|
id: "ev_6",
|
|
290
688
|
name: "six",
|
|
291
689
|
parentId: "ev_1"
|
|
292
|
-
}), await
|
|
293
|
-
id:
|
|
294
|
-
name:
|
|
295
|
-
amount:
|
|
296
|
-
status:
|
|
297
|
-
parentId:
|
|
298
|
-
})], !0),
|
|
299
|
-
id:
|
|
300
|
-
name:
|
|
301
|
-
amount:
|
|
302
|
-
status:
|
|
303
|
-
createdAt:
|
|
690
|
+
}), await l([C(n, {
|
|
691
|
+
id: h({ prefix: "ev" }),
|
|
692
|
+
name: w(),
|
|
693
|
+
amount: y(),
|
|
694
|
+
status: j.column().nullable(),
|
|
695
|
+
parentId: f()
|
|
696
|
+
})], !0), H(await c(), `${e.dialect.id}: v10 dropped the FK column`).not.toContain("parentId"), await l([C(n, {
|
|
697
|
+
id: h({ prefix: "ev" }),
|
|
698
|
+
name: w(),
|
|
699
|
+
amount: y(),
|
|
700
|
+
status: j.column().nullable(),
|
|
701
|
+
createdAt: T().nullable()
|
|
304
702
|
})], !0);
|
|
305
|
-
for (let [e,
|
|
703
|
+
for (let [e, t] of Object.entries({
|
|
306
704
|
ev_1: "2024-03-04T05:06:07Z",
|
|
307
705
|
ev_2: "2025-01-01T00:00:00Z",
|
|
308
706
|
ev_3: "2026-09-15T05:48:00Z"
|
|
309
|
-
})) await
|
|
310
|
-
await
|
|
311
|
-
id:
|
|
312
|
-
name:
|
|
313
|
-
amount:
|
|
314
|
-
status:
|
|
315
|
-
createdAt:
|
|
316
|
-
createdYear:
|
|
707
|
+
})) await a.store.update(n, e, { createdAt: new Date(t) });
|
|
708
|
+
await l([C(n, {
|
|
709
|
+
id: h({ prefix: "ev" }),
|
|
710
|
+
name: w(),
|
|
711
|
+
amount: y(),
|
|
712
|
+
status: j.column().nullable(),
|
|
713
|
+
createdAt: T().nullable(),
|
|
714
|
+
createdYear: g().backfill((e) => e.createdAt instanceof Date ? e.createdAt.getUTCFullYear() : -1)
|
|
317
715
|
})], !0);
|
|
318
|
-
let
|
|
319
|
-
|
|
716
|
+
let ie = Object.fromEntries((await a.store.query(X(n))).map((e) => [String(e.id), e.createdYear]));
|
|
717
|
+
H(ie, `${e.dialect.id}: the backfill saw each row's createdAt as a Date`).toMatchObject({
|
|
320
718
|
ev_1: 2024,
|
|
321
719
|
ev_2: 2025,
|
|
322
720
|
ev_3: 2026,
|
|
323
721
|
ev_4: -1,
|
|
324
722
|
ev_5: -1,
|
|
325
723
|
ev_6: -1
|
|
326
|
-
}), await
|
|
327
|
-
id:
|
|
328
|
-
name:
|
|
329
|
-
amount:
|
|
330
|
-
status:
|
|
331
|
-
createdAt:
|
|
332
|
-
createdYear:
|
|
333
|
-
receiptKey:
|
|
724
|
+
}), await l([C(n, {
|
|
725
|
+
id: h({ prefix: "ev" }),
|
|
726
|
+
name: w(),
|
|
727
|
+
amount: y(),
|
|
728
|
+
status: j.column().nullable(),
|
|
729
|
+
createdAt: T().nullable(),
|
|
730
|
+
createdYear: g().backfill((e) => e.createdAt instanceof Date ? e.createdAt.getUTCFullYear() : -1),
|
|
731
|
+
receiptKey: w().maxLength(191).unique().backfill((e) => `rk-${String(e.id)}`)
|
|
334
732
|
})], !0);
|
|
335
|
-
let
|
|
336
|
-
|
|
337
|
-
let
|
|
338
|
-
|
|
733
|
+
let N = (await o()).tables[0]?.columns.find((e) => e.name === "receiptKey");
|
|
734
|
+
H(N?.unique, `${e.dialect.id}: receiptKey carries its unique in the live schema`).toBe(!0);
|
|
735
|
+
let ae = await a.store.query(X(n));
|
|
736
|
+
H(ae.map((e) => e.id).sort(), `${e.dialect.id}: rows survived the evolution`).toEqual([
|
|
339
737
|
"ev_1",
|
|
340
738
|
"ev_2",
|
|
341
739
|
"ev_3",
|
|
342
740
|
"ev_4",
|
|
343
741
|
"ev_5",
|
|
344
742
|
"ev_6"
|
|
345
|
-
]),
|
|
346
|
-
}),
|
|
347
|
-
let
|
|
348
|
-
let e = yield*
|
|
349
|
-
yield*
|
|
350
|
-
})).catch(() => {}), o = () =>
|
|
351
|
-
let e = yield*
|
|
743
|
+
]), H(ae.find((e) => e.id === "ev_3")?.amount, "The exact decimal survives subsequent schema changes").toMatch(/^12\.340*$/), await i();
|
|
744
|
+
}, 6e4), U("the data half of a plan is atomic: a failing backfill rolls back every backfill of the plan", async () => {
|
|
745
|
+
let n = "voltro_parity_atomic", r = (e) => a.run(e), i = () => r(D.gen(function* () {
|
|
746
|
+
let e = yield* G.SqlClient;
|
|
747
|
+
yield* D.orElseSucceed(e.unsafe(`DROP TABLE IF EXISTS ${n}`), () => []), yield* D.orElseSucceed(e.unsafe("DELETE FROM _voltro_migration_ops"), () => []);
|
|
748
|
+
})).catch(() => {}), o = () => r(D.gen(function* () {
|
|
749
|
+
let e = yield* G.SqlClient, t = yield* J(e);
|
|
352
750
|
return {
|
|
353
|
-
...
|
|
354
|
-
tables:
|
|
751
|
+
...t,
|
|
752
|
+
tables: t.tables.filter((e) => e.name === n)
|
|
355
753
|
};
|
|
356
|
-
})), s = (
|
|
357
|
-
let r = yield*
|
|
358
|
-
declared:
|
|
359
|
-
live: yield*
|
|
360
|
-
dialect:
|
|
754
|
+
})), s = (t) => r(D.gen(function* () {
|
|
755
|
+
let r = yield* G.SqlClient, i = Y({
|
|
756
|
+
declared: t,
|
|
757
|
+
live: yield* D.promise(o),
|
|
758
|
+
dialect: e.dialect.id
|
|
361
759
|
});
|
|
362
|
-
return yield*
|
|
363
|
-
declared:
|
|
760
|
+
return yield* K(r, i, {
|
|
761
|
+
declared: t,
|
|
364
762
|
appliedBy: "dialect-parity",
|
|
365
763
|
environment: "dev",
|
|
366
764
|
source: "auto-diff",
|
|
367
|
-
replan: (r) =>
|
|
368
|
-
declared:
|
|
765
|
+
replan: (r) => D.map(J(r), (r) => Y({
|
|
766
|
+
declared: t,
|
|
369
767
|
live: {
|
|
370
768
|
...r,
|
|
371
|
-
tables: r.tables.filter((e) => e.name ===
|
|
769
|
+
tables: r.tables.filter((e) => e.name === n)
|
|
372
770
|
},
|
|
373
|
-
dialect:
|
|
771
|
+
dialect: e.dialect.id
|
|
374
772
|
}))
|
|
375
773
|
});
|
|
376
774
|
}));
|
|
377
|
-
await
|
|
378
|
-
id:
|
|
379
|
-
name:
|
|
775
|
+
await i(), await a.migrate([t]), await s([C(n, {
|
|
776
|
+
id: h({ prefix: "at" }),
|
|
777
|
+
name: w()
|
|
380
778
|
})]);
|
|
381
779
|
for (let e of [
|
|
382
780
|
"one",
|
|
383
781
|
"two",
|
|
384
782
|
"three"
|
|
385
|
-
]) await
|
|
783
|
+
]) await a.store.insert(n, {
|
|
386
784
|
id: `at_${e}`,
|
|
387
785
|
name: e
|
|
388
786
|
});
|
|
389
|
-
let
|
|
390
|
-
id:
|
|
391
|
-
name:
|
|
392
|
-
first:
|
|
393
|
-
second:
|
|
787
|
+
let c = C(n, {
|
|
788
|
+
id: h({ prefix: "at" }),
|
|
789
|
+
name: w(),
|
|
790
|
+
first: g().backfill(() => 1),
|
|
791
|
+
second: g().backfill((e) => {
|
|
394
792
|
throw Error(`no value for ${String(e.name)}`);
|
|
395
793
|
})
|
|
396
794
|
});
|
|
397
|
-
await
|
|
398
|
-
let
|
|
399
|
-
|
|
795
|
+
await H(s([c])).rejects.toThrow(/no value for/);
|
|
796
|
+
let l = (await o()).tables[0]?.columns.map((e) => e.name) ?? [], u = await a.store.query(X(n));
|
|
797
|
+
H(u, `${e.dialect.id}: the rows survived`).toHaveLength(3), l.includes("first") ? H(u.map((e) => e.first), `${e.dialect.id}: no backfill of the failed group landed`).toEqual([
|
|
400
798
|
null,
|
|
401
799
|
null,
|
|
402
800
|
null
|
|
403
|
-
]) :
|
|
404
|
-
let
|
|
405
|
-
id:
|
|
406
|
-
name:
|
|
407
|
-
first:
|
|
408
|
-
second:
|
|
801
|
+
]) : H(l, `${e.dialect.id}: the plan rolled back`).toEqual(["id", "name"]);
|
|
802
|
+
let d = C(n, {
|
|
803
|
+
id: h({ prefix: "at" }),
|
|
804
|
+
name: w(),
|
|
805
|
+
first: g().backfill(() => 1),
|
|
806
|
+
second: g().backfill((e) => String(e.name).length)
|
|
409
807
|
});
|
|
410
|
-
await s([
|
|
411
|
-
let
|
|
412
|
-
|
|
808
|
+
await s([d]);
|
|
809
|
+
let f = await a.store.query(X(n));
|
|
810
|
+
H(f.map((e) => [e.first, e.second]).sort(), `${e.dialect.id}: both backfills landed`).toEqual([
|
|
413
811
|
[1, 3],
|
|
414
812
|
[1, 3],
|
|
415
813
|
[1, 5]
|
|
416
|
-
]),
|
|
417
|
-
declared: [
|
|
814
|
+
]), H(Y({
|
|
815
|
+
declared: [d],
|
|
418
816
|
live: await o(),
|
|
419
|
-
dialect:
|
|
420
|
-
}).operations).toEqual([]), await
|
|
421
|
-
}),
|
|
422
|
-
let
|
|
423
|
-
await
|
|
424
|
-
let e = yield*
|
|
425
|
-
yield*
|
|
817
|
+
dialect: e.dialect.id
|
|
818
|
+
}).operations).toEqual([]), await i();
|
|
819
|
+
}), U("re-applying a create-table plan is a no-op, indexes included", async () => {
|
|
820
|
+
let n = "voltro_parity_reapply", r = a.run, i = async () => {
|
|
821
|
+
await r(D.gen(function* () {
|
|
822
|
+
let e = yield* G.SqlClient;
|
|
823
|
+
yield* D.orElseSucceed(e.unsafe(`DROP TABLE IF EXISTS ${n}`), () => []);
|
|
426
824
|
})).catch(() => {});
|
|
427
|
-
}, o = () =>
|
|
428
|
-
let e = yield*
|
|
825
|
+
}, o = () => r(D.gen(function* () {
|
|
826
|
+
let e = yield* G.SqlClient, t = yield* J(e);
|
|
429
827
|
return {
|
|
430
|
-
...
|
|
431
|
-
tables:
|
|
828
|
+
...t,
|
|
829
|
+
tables: t.tables.filter((e) => e.name === n)
|
|
432
830
|
};
|
|
433
|
-
})), s =
|
|
434
|
-
id:
|
|
435
|
-
name:
|
|
436
|
-
qty:
|
|
437
|
-
}).index(["qty"]),
|
|
831
|
+
})), s = C(n, {
|
|
832
|
+
id: h({ prefix: "ra" }),
|
|
833
|
+
name: w(),
|
|
834
|
+
qty: g().nullable()
|
|
835
|
+
}).index(["qty"]), c = async () => Y({
|
|
438
836
|
declared: [s],
|
|
439
837
|
live: await o(),
|
|
440
|
-
dialect:
|
|
838
|
+
dialect: e.dialect.id
|
|
441
839
|
});
|
|
442
|
-
await
|
|
443
|
-
let
|
|
444
|
-
|
|
445
|
-
for (let
|
|
446
|
-
let
|
|
447
|
-
return yield*
|
|
840
|
+
await i(), await a.migrate([t]);
|
|
841
|
+
let l = await c();
|
|
842
|
+
H(l.operations.some((e) => e.op.kind === "create-table"), `${e.dialect.id}: expected a create-table to re-apply`).toBe(!0);
|
|
843
|
+
for (let t = 0; t < 2; t++) await r(D.gen(function* () {
|
|
844
|
+
let t = yield* G.SqlClient;
|
|
845
|
+
return yield* K(t, l, {
|
|
448
846
|
declared: [s],
|
|
449
847
|
appliedBy: "dialect-parity",
|
|
450
848
|
environment: "dev",
|
|
451
849
|
source: "auto-diff",
|
|
452
|
-
replan: (
|
|
850
|
+
replan: (t) => D.map(J(t), (t) => Y({
|
|
453
851
|
declared: [s],
|
|
454
852
|
live: {
|
|
455
|
-
...
|
|
456
|
-
tables:
|
|
853
|
+
...t,
|
|
854
|
+
tables: t.tables.filter((e) => e.name === n)
|
|
457
855
|
},
|
|
458
|
-
dialect:
|
|
856
|
+
dialect: e.dialect.id
|
|
459
857
|
}))
|
|
460
858
|
});
|
|
461
859
|
}));
|
|
462
|
-
|
|
860
|
+
H((await c()).operations, `${e.dialect.id}: the re-applied plan must converge`).toEqual([]), await i();
|
|
463
861
|
});
|
|
464
|
-
let
|
|
465
|
-
let e = yield*
|
|
466
|
-
yield*
|
|
467
|
-
})),
|
|
862
|
+
let q = "voltro_parity_bulk", $ = () => a.run(D.gen(function* () {
|
|
863
|
+
let e = yield* G.SqlClient;
|
|
864
|
+
yield* D.orElseSucceed(e.unsafe(`DELETE FROM ${q}`), () => []);
|
|
865
|
+
})), Se = (e, t) => Array.from({ length: e }, (e, n) => ({
|
|
468
866
|
id: `${t}_${String(n).padStart(6, "0")}`,
|
|
469
867
|
title: `row ${n}`,
|
|
470
868
|
count: n,
|
|
471
869
|
flag: !1
|
|
472
870
|
}));
|
|
473
|
-
|
|
474
|
-
let
|
|
475
|
-
|
|
476
|
-
let
|
|
477
|
-
await
|
|
478
|
-
let
|
|
479
|
-
|
|
480
|
-
let
|
|
481
|
-
|
|
482
|
-
}, 12e4),
|
|
483
|
-
let
|
|
484
|
-
await
|
|
485
|
-
let
|
|
486
|
-
t
|
|
487
|
-
...t
|
|
488
|
-
id:
|
|
489
|
-
}, await
|
|
490
|
-
let
|
|
491
|
-
|
|
492
|
-
}, 12e4),
|
|
493
|
-
await
|
|
494
|
-
}),
|
|
495
|
-
let e = await
|
|
871
|
+
U("insertMany past this dialect's one-statement ceiling lands EVERY row", async () => {
|
|
872
|
+
let t = xe(e.dialect.id);
|
|
873
|
+
H(t, `no bulk-insert limits are declared for '${e.dialect.id}' — a dialect with no entry chunks at nothing, so this scenario would pass having tested nothing`).toBeGreaterThan(0);
|
|
874
|
+
let n = t + 5;
|
|
875
|
+
await $();
|
|
876
|
+
let r = await a.store.insertMany(q, Se(n, "bulk"));
|
|
877
|
+
H(r.length, `insertMany returned ${r.length} of ${n} post-images`).toBe(n), H(r[0]?.id).toBe("bulk_000000"), H(r[n - 1]?.id).toBe(`bulk_${String(n - 1).padStart(6, "0")}`);
|
|
878
|
+
let i = await a.store.query(X(q));
|
|
879
|
+
H(i.length, "the database holds fewer rows than insertMany reported").toBe(n), await $();
|
|
880
|
+
}, 12e4), U("a chunked insertMany is still ALL-OR-NOTHING", async () => {
|
|
881
|
+
let t = xe(e.dialect.id) + 5;
|
|
882
|
+
await $();
|
|
883
|
+
let n = Se(t, "atomic");
|
|
884
|
+
n[t - 1] = {
|
|
885
|
+
...n[t - 1],
|
|
886
|
+
id: n[0].id
|
|
887
|
+
}, await H(a.store.insertMany(q, n), "a duplicate primary key must still reject the whole call").rejects.toThrow();
|
|
888
|
+
let r = await a.store.query(X(q));
|
|
889
|
+
H(r.length, `${e.dialect.id}: ${r.length} rows survived a FAILED insertMany — chunking introduced partial success. The chunks must run inside one transaction when the caller holds none.`).toBe(0), await $();
|
|
890
|
+
}, 12e4), U("DDL is idempotent — re-applying the schema is a no-op", async () => {
|
|
891
|
+
await a.migrate([me, he]);
|
|
892
|
+
}), U("insert + query round-trips a row", async () => {
|
|
893
|
+
let e = await a.store.insert("voltro_parity_a", {
|
|
496
894
|
id: "a1",
|
|
497
895
|
title: "one",
|
|
498
896
|
count: 1
|
|
499
897
|
});
|
|
500
|
-
|
|
898
|
+
H(e).toMatchObject({
|
|
501
899
|
id: "a1",
|
|
502
900
|
title: "one",
|
|
503
901
|
count: 1
|
|
504
902
|
});
|
|
505
|
-
let t = await
|
|
506
|
-
|
|
507
|
-
}),
|
|
508
|
-
await
|
|
903
|
+
let t = await a.store.query(X("voltro_parity_a"));
|
|
904
|
+
H(t.length).toBe(1), H(t[0]?.id).toBe("a1");
|
|
905
|
+
}), U("update returns the post-image", async () => {
|
|
906
|
+
await a.store.insert("voltro_parity_a", {
|
|
509
907
|
id: "a2",
|
|
510
908
|
title: "pre",
|
|
511
909
|
count: 1
|
|
512
910
|
});
|
|
513
|
-
let e = await
|
|
514
|
-
|
|
911
|
+
let e = await a.store.update("voltro_parity_a", "a2", { title: "post" });
|
|
912
|
+
H(e).toMatchObject({
|
|
515
913
|
id: "a2",
|
|
516
914
|
title: "post"
|
|
517
915
|
});
|
|
518
|
-
}),
|
|
519
|
-
await
|
|
916
|
+
}), U("delete removes the row + reports true", async () => {
|
|
917
|
+
await a.store.insert("voltro_parity_a", {
|
|
520
918
|
id: "a3",
|
|
521
919
|
title: "x",
|
|
522
920
|
count: 1
|
|
523
|
-
}),
|
|
524
|
-
let e = await
|
|
525
|
-
|
|
526
|
-
}),
|
|
527
|
-
await
|
|
921
|
+
}), H(await a.store.delete("voltro_parity_a", "a3")).toBe(!0);
|
|
922
|
+
let e = await a.store.query(X("voltro_parity_a"));
|
|
923
|
+
H(e.length).toBe(0);
|
|
924
|
+
}), U("a composite key addresses the row, and only that row", async () => {
|
|
925
|
+
await a.store.insert("voltro_parity_composite", {
|
|
528
926
|
userId: "u1",
|
|
529
927
|
orgId: "o1",
|
|
530
928
|
role: "admin"
|
|
531
|
-
}), await
|
|
929
|
+
}), await a.store.insert("voltro_parity_composite", {
|
|
532
930
|
userId: "u1",
|
|
533
931
|
orgId: "o2",
|
|
534
932
|
role: "viewer"
|
|
535
933
|
});
|
|
536
|
-
let e = await
|
|
934
|
+
let e = await a.store.update("voltro_parity_composite", {
|
|
537
935
|
userId: "u1",
|
|
538
936
|
orgId: "o2"
|
|
539
937
|
}, { role: "member" });
|
|
540
|
-
|
|
938
|
+
H(e).toMatchObject({
|
|
541
939
|
userId: "u1",
|
|
542
940
|
orgId: "o2",
|
|
543
941
|
role: "member"
|
|
544
942
|
});
|
|
545
|
-
let t = await
|
|
546
|
-
|
|
547
|
-
}),
|
|
548
|
-
|
|
943
|
+
let t = await a.store.query(X("voltro_parity_composite")), n = new Map(t.map((e) => [e.orgId, e.role]));
|
|
944
|
+
H(n.get("o1")).toBe("admin"), H(n.get("o2")).toBe("member");
|
|
945
|
+
}), U("a composite key that matches nothing is `null` / `false`, not an error", async () => {
|
|
946
|
+
H(await a.store.update("voltro_parity_composite", {
|
|
549
947
|
userId: "nobody",
|
|
550
948
|
orgId: "nowhere"
|
|
551
|
-
}, { role: "x" })).toBeNull(),
|
|
949
|
+
}, { role: "x" })).toBeNull(), H(await a.store.delete("voltro_parity_composite", {
|
|
552
950
|
userId: "nobody",
|
|
553
951
|
orgId: "nowhere"
|
|
554
952
|
})).toBe(!1);
|
|
555
|
-
}),
|
|
556
|
-
await
|
|
953
|
+
}), U("a composite key deletes exactly the row it names", async () => {
|
|
954
|
+
await a.store.insert("voltro_parity_composite", {
|
|
557
955
|
userId: "u2",
|
|
558
956
|
orgId: "o1",
|
|
559
957
|
role: "admin"
|
|
560
|
-
}), await
|
|
958
|
+
}), await a.store.insert("voltro_parity_composite", {
|
|
561
959
|
userId: "u2",
|
|
562
960
|
orgId: "o2",
|
|
563
961
|
role: "viewer"
|
|
564
|
-
}),
|
|
962
|
+
}), H(await a.store.delete("voltro_parity_composite", {
|
|
565
963
|
userId: "u2",
|
|
566
964
|
orgId: "o1"
|
|
567
965
|
})).toBe(!0);
|
|
568
|
-
let e = (await
|
|
569
|
-
|
|
570
|
-
}),
|
|
966
|
+
let e = (await a.store.query(X("voltro_parity_composite"))).filter((e) => e.userId === "u2").map((e) => e.orgId);
|
|
967
|
+
H(e).toEqual(["o2"]);
|
|
968
|
+
}), U("undo: the _voltro_undo_log TEXT changes column round-trips a JSON ChangeSet", async () => {
|
|
571
969
|
let e = [{
|
|
572
970
|
table: "voltro_parity_c",
|
|
573
971
|
op: "update",
|
|
@@ -585,7 +983,7 @@ var K = h("voltro_parity_composite", {
|
|
|
585
983
|
flag: !1
|
|
586
984
|
}
|
|
587
985
|
}];
|
|
588
|
-
await
|
|
986
|
+
await a.store.insert("_voltro_undo_log", {
|
|
589
987
|
id: "undo_p1",
|
|
590
988
|
tag: "parityC.update",
|
|
591
989
|
label: null,
|
|
@@ -597,16 +995,16 @@ var K = h("voltro_parity_composite", {
|
|
|
597
995
|
undone: !1,
|
|
598
996
|
createdAt: /* @__PURE__ */ new Date()
|
|
599
997
|
});
|
|
600
|
-
let t = await
|
|
601
|
-
|
|
602
|
-
}),
|
|
998
|
+
let t = await a.store.query(X("_voltro_undo_log"));
|
|
999
|
+
H(t.length).toBe(1), H(JSON.parse(t[0].changes)).toEqual(e);
|
|
1000
|
+
}), U("undo: reverts an INSERT (inverse delete) on this dialect", async () => {
|
|
603
1001
|
let e = {
|
|
604
1002
|
invocationId: "inv1",
|
|
605
1003
|
subject: null,
|
|
606
1004
|
changes: [{
|
|
607
1005
|
table: "voltro_parity_c",
|
|
608
1006
|
op: "insert",
|
|
609
|
-
next: await
|
|
1007
|
+
next: await a.store.insert("voltro_parity_c", {
|
|
610
1008
|
id: "c2",
|
|
611
1009
|
title: "new",
|
|
612
1010
|
count: 1,
|
|
@@ -614,24 +1012,24 @@ var K = h("voltro_parity_composite", {
|
|
|
614
1012
|
})
|
|
615
1013
|
}]
|
|
616
1014
|
};
|
|
617
|
-
await
|
|
618
|
-
let t = await
|
|
619
|
-
|
|
620
|
-
}),
|
|
621
|
-
await
|
|
1015
|
+
await N(a.store, R(e));
|
|
1016
|
+
let t = await a.store.query(X("voltro_parity_c"));
|
|
1017
|
+
H(t.find((e) => e.id === "c2")).toBeUndefined();
|
|
1018
|
+
}), U("undo: reverts an UPDATE including the BOOLEAN, restoring the captured value FAITHFULLY", async () => {
|
|
1019
|
+
await a.store.insert("voltro_parity_c", {
|
|
622
1020
|
id: "c3",
|
|
623
1021
|
title: "t",
|
|
624
1022
|
count: 1,
|
|
625
1023
|
flag: !0
|
|
626
1024
|
});
|
|
627
|
-
let e = (await
|
|
628
|
-
...
|
|
629
|
-
predicate:
|
|
630
|
-
}))[0], t = await
|
|
1025
|
+
let e = (await a.store.query({
|
|
1026
|
+
...X("voltro_parity_c"),
|
|
1027
|
+
predicate: p("id", "c3")
|
|
1028
|
+
}))[0], t = await a.store.update("voltro_parity_c", "c3", {
|
|
631
1029
|
count: 2,
|
|
632
1030
|
flag: !1
|
|
633
1031
|
});
|
|
634
|
-
|
|
1032
|
+
H(t.flag).not.toEqual(e.flag);
|
|
635
1033
|
let n = {
|
|
636
1034
|
invocationId: "inv2",
|
|
637
1035
|
subject: null,
|
|
@@ -643,20 +1041,20 @@ var K = h("voltro_parity_composite", {
|
|
|
643
1041
|
next: t
|
|
644
1042
|
}]
|
|
645
1043
|
};
|
|
646
|
-
await
|
|
647
|
-
let
|
|
648
|
-
...
|
|
649
|
-
predicate:
|
|
1044
|
+
await N(a.store, R(n));
|
|
1045
|
+
let r = (await a.store.query({
|
|
1046
|
+
...X("voltro_parity_c"),
|
|
1047
|
+
predicate: p("id", "c3")
|
|
650
1048
|
}))[0];
|
|
651
|
-
|
|
652
|
-
}),
|
|
653
|
-
let e = await
|
|
1049
|
+
H(r.flag).toEqual(e.flag), H(r.count).toEqual(e.count);
|
|
1050
|
+
}), U("undo: reverts a DELETE (inverse re-insert) on this dialect", async () => {
|
|
1051
|
+
let e = await a.store.insert("voltro_parity_c", {
|
|
654
1052
|
id: "c4",
|
|
655
1053
|
title: "gone",
|
|
656
1054
|
count: 9,
|
|
657
1055
|
flag: !0
|
|
658
1056
|
});
|
|
659
|
-
await
|
|
1057
|
+
await a.store.delete("voltro_parity_c", "c4");
|
|
660
1058
|
let t = {
|
|
661
1059
|
invocationId: "inv3",
|
|
662
1060
|
subject: null,
|
|
@@ -667,46 +1065,46 @@ var K = h("voltro_parity_composite", {
|
|
|
667
1065
|
prev: e
|
|
668
1066
|
}]
|
|
669
1067
|
};
|
|
670
|
-
await
|
|
671
|
-
let n = (await
|
|
672
|
-
...
|
|
673
|
-
predicate:
|
|
1068
|
+
await N(a.store, R(t));
|
|
1069
|
+
let n = (await a.store.query({
|
|
1070
|
+
...X("voltro_parity_c"),
|
|
1071
|
+
predicate: p("id", "c4")
|
|
674
1072
|
}))[0];
|
|
675
|
-
|
|
676
|
-
}),
|
|
677
|
-
let e = [], t =
|
|
678
|
-
await
|
|
1073
|
+
H(n.id).toBe("c4"), H(n.title).toBe("gone"), H(n.flag).toEqual(e.flag);
|
|
1074
|
+
}), U("onChange fires insert + update + delete events", async () => {
|
|
1075
|
+
let e = [], t = a.store.onChange((t) => e.push(t.op));
|
|
1076
|
+
await a.store.insert("voltro_parity_a", {
|
|
679
1077
|
id: "a4",
|
|
680
1078
|
title: "a",
|
|
681
1079
|
count: 1
|
|
682
|
-
}), await
|
|
1080
|
+
}), await a.store.update("voltro_parity_a", "a4", { title: "b" }), await a.store.delete("voltro_parity_a", "a4"), t(), H(e).toEqual([
|
|
683
1081
|
"insert",
|
|
684
1082
|
"update",
|
|
685
1083
|
"delete"
|
|
686
1084
|
]);
|
|
687
|
-
}),
|
|
1085
|
+
}), U("binding more than 10 change listeners emits no MaxListeners warning", async () => {
|
|
688
1086
|
let e = [], t = (t) => {
|
|
689
1087
|
e.push(t.name);
|
|
690
1088
|
};
|
|
691
1089
|
process.on("warning", t);
|
|
692
|
-
let n = Array.from({ length: 32 }, () =>
|
|
1090
|
+
let n = Array.from({ length: 32 }, () => a.store.onChange(() => {}));
|
|
693
1091
|
await new Promise((e) => setImmediate(e));
|
|
694
1092
|
for (let e of n) e();
|
|
695
|
-
process.off("warning", t),
|
|
696
|
-
}),
|
|
697
|
-
let e = [], t =
|
|
698
|
-
await
|
|
1093
|
+
process.off("warning", t), H(e.filter((e) => e === "MaxListenersExceededWarning"), "the change bus still warns at 11 listeners — `raiseChangeListenerCeiling` is not wired into this dialect’s store").toEqual([]);
|
|
1094
|
+
}), U("transactional rolls back on throw + drops queued events", async () => {
|
|
1095
|
+
let e = [], t = a.store.onChange((t) => e.push(t.op));
|
|
1096
|
+
await H(a.store.transactional(async (e) => {
|
|
699
1097
|
throw await e.insert("voltro_parity_a", {
|
|
700
1098
|
id: "rollback",
|
|
701
1099
|
title: "r",
|
|
702
1100
|
count: 0
|
|
703
1101
|
}), Error("intentional rollback");
|
|
704
|
-
})).rejects.toThrow("intentional rollback"), t(),
|
|
705
|
-
let n = await
|
|
706
|
-
|
|
707
|
-
}),
|
|
708
|
-
let e = [], t =
|
|
709
|
-
await
|
|
1102
|
+
})).rejects.toThrow("intentional rollback"), t(), H(e).toEqual([]);
|
|
1103
|
+
let n = await a.store.query(X("voltro_parity_a"));
|
|
1104
|
+
H(n.length).toBe(0);
|
|
1105
|
+
}), U("transactional commit drains queued events in order", async () => {
|
|
1106
|
+
let e = [], t = a.store.onChange((t) => e.push(t.op));
|
|
1107
|
+
await a.store.transactional(async (e) => {
|
|
710
1108
|
await e.insert("voltro_parity_a", {
|
|
711
1109
|
id: "commit1",
|
|
712
1110
|
title: "a",
|
|
@@ -716,13 +1114,13 @@ var K = h("voltro_parity_composite", {
|
|
|
716
1114
|
title: "b",
|
|
717
1115
|
count: 2
|
|
718
1116
|
});
|
|
719
|
-
}), t(),
|
|
720
|
-
}),
|
|
721
|
-
await
|
|
1117
|
+
}), t(), H(e).toEqual(["insert", "insert"]);
|
|
1118
|
+
}), U("upsert inserts a new row, then updates on conflict — no duplicate", async () => {
|
|
1119
|
+
await a.store.upsert("voltro_parity_a", {
|
|
722
1120
|
id: "u1",
|
|
723
1121
|
title: "first",
|
|
724
1122
|
count: 1
|
|
725
|
-
}, { conflictColumns: ["id"] }), await
|
|
1123
|
+
}, { conflictColumns: ["id"] }), await a.store.upsert("voltro_parity_a", {
|
|
726
1124
|
id: "u1",
|
|
727
1125
|
title: "second",
|
|
728
1126
|
count: 2
|
|
@@ -730,13 +1128,13 @@ var K = h("voltro_parity_composite", {
|
|
|
730
1128
|
conflictColumns: ["id"],
|
|
731
1129
|
update: ["title", "count"]
|
|
732
1130
|
});
|
|
733
|
-
let e = await
|
|
734
|
-
|
|
1131
|
+
let e = await a.store.query(X("voltro_parity_a"));
|
|
1132
|
+
H(e.length).toBe(1), H(e[0]).toMatchObject({
|
|
735
1133
|
id: "u1",
|
|
736
1134
|
title: "second",
|
|
737
1135
|
count: 2
|
|
738
1136
|
});
|
|
739
|
-
}),
|
|
1137
|
+
}), U.each([
|
|
740
1138
|
"done",
|
|
741
1139
|
"",
|
|
742
1140
|
"null",
|
|
@@ -746,47 +1144,47 @@ var K = h("voltro_parity_composite", {
|
|
|
746
1144
|
"{\"a\":1}",
|
|
747
1145
|
"\"quoted\""
|
|
748
1146
|
])("JSON scalar string %j survives insert and update", async (e) => {
|
|
749
|
-
let t =
|
|
1147
|
+
let t = Z.tableName, n = await a.store.insert(t, {
|
|
750
1148
|
id: "json-string",
|
|
751
1149
|
title: "before",
|
|
752
1150
|
metadata: e,
|
|
753
1151
|
labels: []
|
|
754
1152
|
});
|
|
755
|
-
|
|
1153
|
+
H(n.metadata).toBe(e), H((await a.store.query(X(t)))[0]?.metadata).toBe(e), await a.store.transactional(async (n) => {
|
|
756
1154
|
let r = await n.update(t, "json-string", { metadata: `${e}\nupdated` });
|
|
757
|
-
|
|
758
|
-
}),
|
|
759
|
-
}),
|
|
760
|
-
let
|
|
761
|
-
let e = yield*
|
|
762
|
-
return yield*
|
|
763
|
-
}))).tables.find((e) => e.name ===
|
|
764
|
-
|
|
765
|
-
name:
|
|
1155
|
+
H(r?.metadata).toBe(`${e}\nupdated`);
|
|
1156
|
+
}), H((await a.store.query(X(t)))[0]?.metadata).toBe(`${e}\nupdated`);
|
|
1157
|
+
}), U("JSON validity checks are not introspected as enum membership", async () => {
|
|
1158
|
+
let t = (await a.run(D.gen(function* () {
|
|
1159
|
+
let e = yield* G.SqlClient;
|
|
1160
|
+
return yield* J(e);
|
|
1161
|
+
}))).tables.find((e) => e.name === Z.tableName)?.columns.find((e) => e.name === "metadata");
|
|
1162
|
+
H(t).toBeDefined(), H(t?.oneOf).toBeUndefined(), e.dialect.id === "mssql" && (H(t?.jsonValidation).toBe("value"), H(t?.jsonChecks).toEqual([{
|
|
1163
|
+
name: H.any(String),
|
|
766
1164
|
validation: "value",
|
|
767
1165
|
enabled: !0,
|
|
768
1166
|
trusted: !0
|
|
769
1167
|
}]));
|
|
770
|
-
}),
|
|
771
|
-
let e = (e) =>
|
|
772
|
-
yield* (yield*
|
|
773
|
-
})), t = () =>
|
|
774
|
-
let e = yield*
|
|
775
|
-
return (yield*
|
|
1168
|
+
}), e.dialect.id === "mssql" && U("JSON check catalog includes table-level names and enforcement state", async () => {
|
|
1169
|
+
let e = (e) => a.run(D.gen(function* () {
|
|
1170
|
+
yield* (yield* G.SqlClient).unsafe(e);
|
|
1171
|
+
})), t = () => a.run(D.gen(function* () {
|
|
1172
|
+
let e = yield* G.SqlClient;
|
|
1173
|
+
return (yield* J(e)).tables.find((e) => e.name === "voltro_parity_json_checks")?.columns.find((e) => e.name === "metadata")?.jsonChecks;
|
|
776
1174
|
}));
|
|
777
1175
|
await e("CREATE TABLE voltro_parity_json_checks (id INT PRIMARY KEY, metadata NVARCHAR(MAX), CONSTRAINT json_container_probe CHECK (metadata IS NULL OR ISJSON(metadata) = 1), CONSTRAINT custom_json_probe CHECK (metadata IS NULL OR LEN(metadata) > 2))");
|
|
778
1176
|
try {
|
|
779
|
-
|
|
1177
|
+
H(await t()).toEqual([{
|
|
780
1178
|
name: "json_container_probe",
|
|
781
1179
|
validation: "container",
|
|
782
1180
|
enabled: !0,
|
|
783
1181
|
trusted: !0
|
|
784
|
-
}]), await e("ALTER TABLE voltro_parity_json_checks NOCHECK CONSTRAINT json_container_probe"),
|
|
1182
|
+
}]), await e("ALTER TABLE voltro_parity_json_checks NOCHECK CONSTRAINT json_container_probe"), H(await t()).toEqual([{
|
|
785
1183
|
name: "json_container_probe",
|
|
786
1184
|
validation: "container",
|
|
787
1185
|
enabled: !1,
|
|
788
1186
|
trusted: !1
|
|
789
|
-
}]), await e("ALTER TABLE voltro_parity_json_checks CHECK CONSTRAINT json_container_probe"),
|
|
1187
|
+
}]), await e("ALTER TABLE voltro_parity_json_checks CHECK CONSTRAINT json_container_probe"), H(await t()).toEqual([{
|
|
790
1188
|
name: "json_container_probe",
|
|
791
1189
|
validation: "container",
|
|
792
1190
|
enabled: !0,
|
|
@@ -795,7 +1193,7 @@ var K = h("voltro_parity_composite", {
|
|
|
795
1193
|
} finally {
|
|
796
1194
|
await e("DROP TABLE voltro_parity_json_checks");
|
|
797
1195
|
}
|
|
798
|
-
}),
|
|
1196
|
+
}), e.dialect.id === "mssql" && U.each([
|
|
799
1197
|
{
|
|
800
1198
|
invalid: !1,
|
|
801
1199
|
rename: !1
|
|
@@ -812,121 +1210,121 @@ var K = h("voltro_parity_composite", {
|
|
|
812
1210
|
invalid: !0,
|
|
813
1211
|
rename: !0
|
|
814
1212
|
}
|
|
815
|
-
])("JSON check migration preserves data and is atomic (invalid=$invalid, rename=$rename)", async ({ invalid:
|
|
816
|
-
let
|
|
817
|
-
id:
|
|
818
|
-
[
|
|
819
|
-
})], s = (e) =>
|
|
820
|
-
return yield* (yield*
|
|
821
|
-
})),
|
|
1213
|
+
])("JSON check migration preserves data and is atomic (invalid=$invalid, rename=$rename)", async ({ invalid: e, rename: n }) => {
|
|
1214
|
+
let r = "voltro_parity_json_upgrade", i = n ? "renamed_metadata" : "metadata", o = [C(r, {
|
|
1215
|
+
id: h(),
|
|
1216
|
+
[i]: n ? _().nullable().renamedFrom("metadata") : _().nullable()
|
|
1217
|
+
})], s = (e) => a.run(D.gen(function* () {
|
|
1218
|
+
return yield* (yield* G.SqlClient).unsafe(e);
|
|
1219
|
+
})), c = (e) => J(e).pipe(D.map((e) => Y({
|
|
822
1220
|
declared: o,
|
|
823
1221
|
dialect: "mssql",
|
|
824
|
-
live: { tables: e.tables.filter((e) => e.name ===
|
|
825
|
-
}))),
|
|
826
|
-
await
|
|
1222
|
+
live: { tables: e.tables.filter((e) => e.name === r) }
|
|
1223
|
+
}))), l = () => a.run(G.SqlClient.pipe(D.flatMap(c)));
|
|
1224
|
+
await a.migrate([t]), await s(`CREATE TABLE ${r} (id VARCHAR(64) PRIMARY KEY, metadata NVARCHAR(MAX) NULL, CONSTRAINT upgrade_json_old CHECK (metadata IS NULL OR ISJSON(metadata)=1), CONSTRAINT upgrade_json_custom CHECK NOT FOR REPLICATION (metadata IS NULL OR (LEN(metadata)<100 AND metadata<>N'"[metadata]"')))`);
|
|
827
1225
|
try {
|
|
828
|
-
let
|
|
829
|
-
|
|
1226
|
+
let t = () => s(`SELECT is_disabled AS disabled, is_not_trusted AS untrusted, is_not_for_replication AS notForReplication FROM sys.check_constraints WHERE parent_object_id=OBJECT_ID('${r}') AND name='upgrade_json_custom'`), u = await t();
|
|
1227
|
+
H(u).toHaveLength(1), H(u[0]).toMatchObject({
|
|
830
1228
|
disabled: !1,
|
|
831
1229
|
notForReplication: !0
|
|
832
|
-
}),
|
|
833
|
-
let
|
|
834
|
-
|
|
1230
|
+
}), e && await s(`ALTER TABLE ${r} NOCHECK CONSTRAINT upgrade_json_old`), await s(`INSERT INTO ${r} (id,metadata) VALUES ('kept','${e ? "invalid" : "{\"kept\":true}"}')`);
|
|
1231
|
+
let d = await l();
|
|
1232
|
+
H(d.operations.map((e) => e.op)).toEqual([...n ? [{
|
|
835
1233
|
kind: "rename-column",
|
|
836
|
-
table:
|
|
1234
|
+
table: r,
|
|
837
1235
|
from: "metadata",
|
|
838
|
-
to:
|
|
1236
|
+
to: i
|
|
839
1237
|
}] : [], {
|
|
840
1238
|
kind: "reconcile-json-check",
|
|
841
|
-
table:
|
|
842
|
-
column:
|
|
1239
|
+
table: r,
|
|
1240
|
+
column: i,
|
|
843
1241
|
validation: "value",
|
|
844
|
-
previousValidation:
|
|
1242
|
+
previousValidation: e ? "unverified" : "container",
|
|
845
1243
|
dropConstraints: ["upgrade_json_old"]
|
|
846
1244
|
}]);
|
|
847
|
-
let f =
|
|
848
|
-
let e = yield*
|
|
849
|
-
return yield*
|
|
1245
|
+
let f = a.run(D.gen(function* () {
|
|
1246
|
+
let e = yield* G.SqlClient;
|
|
1247
|
+
return yield* K(e, d, {
|
|
850
1248
|
declared: o,
|
|
851
1249
|
appliedBy: "json-parity",
|
|
852
1250
|
environment: "dev",
|
|
853
1251
|
source: "auto-diff",
|
|
854
|
-
replan:
|
|
1252
|
+
replan: c
|
|
855
1253
|
});
|
|
856
1254
|
}));
|
|
857
|
-
|
|
1255
|
+
e ? (await H(f).rejects.toThrow("op=reconcile-json-check"), H((await l()).operations.map((e) => e.op)).toEqual(d.operations.map((e) => e.op))) : (await f, H((await l()).operations).toEqual([]), await s(`INSERT INTO ${r} (id,${i}) VALUES ('scalar','"done"')`), await H(s(`INSERT INTO ${r} (id,${i}) VALUES ('too-long','"${"x".repeat(110)}"')`)).rejects.toThrow(), await H(s(`INSERT INTO ${r} (id,${i}) VALUES ('literal','"[metadata]"')`)).rejects.toThrow()), H(await s(`SELECT ${e ? "metadata" : i} AS metadata FROM ${r} WHERE id='kept'`)).toEqual([{ metadata: e ? "invalid" : "{\"kept\":true}" }]), H(await s(`SELECT name FROM sys.check_constraints WHERE parent_object_id=OBJECT_ID('${r}') AND name='upgrade_json_custom'`)).toEqual([{ name: "upgrade_json_custom" }]), H(await t()).toEqual(u);
|
|
858
1256
|
} finally {
|
|
859
|
-
await s(`DROP TABLE ${
|
|
1257
|
+
await s(`DROP TABLE ${r}`);
|
|
860
1258
|
}
|
|
861
|
-
}),
|
|
1259
|
+
}), e.dialect.id === "mssql" && U.each([
|
|
862
1260
|
"trusted",
|
|
863
1261
|
"untrusted",
|
|
864
1262
|
"disabled"
|
|
865
|
-
])("column rename preserves %s application CHECK state", async (
|
|
866
|
-
let n = "voltro_parity_rename_checks",
|
|
867
|
-
id:
|
|
868
|
-
renamed:
|
|
869
|
-
})],
|
|
870
|
-
return yield* (yield*
|
|
871
|
-
})), o = (e) =>
|
|
872
|
-
declared:
|
|
1263
|
+
])("column rename preserves %s application CHECK state", async (e) => {
|
|
1264
|
+
let n = "voltro_parity_rename_checks", r = [C(n, {
|
|
1265
|
+
id: h(),
|
|
1266
|
+
renamed: w().nullable().renamedFrom("original")
|
|
1267
|
+
})], i = (e) => a.run(D.gen(function* () {
|
|
1268
|
+
return yield* (yield* G.SqlClient).unsafe(e);
|
|
1269
|
+
})), o = (e) => J(e).pipe(D.map((e) => Y({
|
|
1270
|
+
declared: r,
|
|
873
1271
|
dialect: "mssql",
|
|
874
1272
|
live: { tables: e.tables.filter((e) => e.name === n) }
|
|
875
1273
|
})));
|
|
876
|
-
await
|
|
1274
|
+
await a.migrate([t]), await i(`CREATE TABLE ${n} (id VARCHAR(64) PRIMARY KEY, original NVARCHAR(MAX) NULL, CONSTRAINT rename_state_check CHECK (LEN(original)<10))`);
|
|
877
1275
|
try {
|
|
878
|
-
|
|
879
|
-
let
|
|
880
|
-
|
|
1276
|
+
e !== "trusted" && await i(`ALTER TABLE ${n} NOCHECK CONSTRAINT rename_state_check`), e === "untrusted" && await i(`ALTER TABLE ${n} CHECK CONSTRAINT rename_state_check`);
|
|
1277
|
+
let t = () => i(`SELECT name,is_disabled AS disabled,is_not_trusted AS untrusted FROM sys.check_constraints WHERE parent_object_id=OBJECT_ID('${n}')`), s = await t();
|
|
1278
|
+
H(s).toEqual([{
|
|
881
1279
|
name: "rename_state_check",
|
|
882
|
-
disabled:
|
|
883
|
-
untrusted:
|
|
884
|
-
}]), await
|
|
885
|
-
let e = yield*
|
|
886
|
-
|
|
887
|
-
declared:
|
|
1280
|
+
disabled: e === "disabled",
|
|
1281
|
+
untrusted: e !== "trusted"
|
|
1282
|
+
}]), await i(`INSERT INTO ${n} (id,original) VALUES ('kept','${e === "disabled" ? "long invalid existing value" : "valid"}')`), await a.run(D.gen(function* () {
|
|
1283
|
+
let e = yield* G.SqlClient, t = yield* o(e);
|
|
1284
|
+
H(t.operations.map((e) => e.op.kind)).toEqual(["rename-column"]), yield* K(e, t, {
|
|
1285
|
+
declared: r,
|
|
888
1286
|
appliedBy: "rename-parity",
|
|
889
1287
|
environment: "dev",
|
|
890
1288
|
source: "auto-diff",
|
|
891
1289
|
replan: o
|
|
892
|
-
}),
|
|
893
|
-
})),
|
|
894
|
-
let c =
|
|
895
|
-
|
|
1290
|
+
}), H((yield* o(e)).operations).toEqual([]);
|
|
1291
|
+
})), H(await t()).toEqual(s), H(await i(`SELECT renamed FROM ${n} WHERE id='kept'`)).toEqual([{ renamed: e === "disabled" ? "long invalid existing value" : "valid" }]);
|
|
1292
|
+
let c = i(`INSERT INTO ${n} (id,renamed) VALUES ('probe','another invalid value')`);
|
|
1293
|
+
e === "disabled" ? await c : await H(c).rejects.toThrow();
|
|
896
1294
|
} finally {
|
|
897
|
-
await
|
|
1295
|
+
await i(`DROP TABLE ${n}`);
|
|
898
1296
|
}
|
|
899
|
-
}),
|
|
900
|
-
let
|
|
901
|
-
id:
|
|
902
|
-
metadata:
|
|
903
|
-
labels:
|
|
904
|
-
})], o = (e) =>
|
|
905
|
-
return yield* (yield*
|
|
906
|
-
})), s = (e) =>
|
|
907
|
-
declared:
|
|
1297
|
+
}), e.dialect.id === "mssql" && U.each(["create", "add"])("JSON checks are complete after %s-column DDL", async (e) => {
|
|
1298
|
+
let r = "voltro_parity_json_ddl", i = [C(r, {
|
|
1299
|
+
id: h(),
|
|
1300
|
+
metadata: _().nullable(),
|
|
1301
|
+
labels: n(w()).nullable()
|
|
1302
|
+
})], o = (e) => a.run(D.gen(function* () {
|
|
1303
|
+
return yield* (yield* G.SqlClient).unsafe(e);
|
|
1304
|
+
})), s = (e) => J(e).pipe(D.map((e) => Y({
|
|
1305
|
+
declared: i,
|
|
908
1306
|
dialect: "mssql",
|
|
909
|
-
live: { tables: e.tables.filter((e) => e.name ===
|
|
1307
|
+
live: { tables: e.tables.filter((e) => e.name === r) }
|
|
910
1308
|
})));
|
|
911
|
-
await
|
|
1309
|
+
await a.migrate([t]), e === "add" && (await o(`CREATE TABLE ${r} (id VARCHAR(64) PRIMARY KEY)`), await o(`INSERT INTO ${r} (id) VALUES ('existing')`));
|
|
912
1310
|
try {
|
|
913
|
-
await
|
|
914
|
-
let
|
|
915
|
-
|
|
916
|
-
declared:
|
|
1311
|
+
await a.run(D.gen(function* () {
|
|
1312
|
+
let t = yield* G.SqlClient, n = yield* s(t);
|
|
1313
|
+
H(n.operations.map((e) => e.op.kind)).toEqual(e === "create" ? ["create-table"] : ["add-column", "add-column"]), yield* K(t, n, {
|
|
1314
|
+
declared: i,
|
|
917
1315
|
appliedBy: "json-ddl",
|
|
918
1316
|
environment: "dev",
|
|
919
1317
|
source: "auto-diff",
|
|
920
1318
|
replan: s
|
|
921
|
-
}),
|
|
922
|
-
})), await o(`INSERT INTO ${
|
|
1319
|
+
}), H((yield* s(t)).operations).toEqual([]);
|
|
1320
|
+
})), await o(`INSERT INTO ${r} (id,metadata,labels) VALUES ('valid','"done"','["x"]')`), await H(o(`INSERT INTO ${r} (id,metadata) VALUES ('invalid','1,2')`)).rejects.toThrow(), await H(o(`INSERT INTO ${r} (id,labels) VALUES ('invalid-array','"x"')`)).rejects.toThrow(), e === "add" && H(await o(`SELECT metadata,labels FROM ${r} WHERE id='existing'`)).toEqual([{
|
|
923
1321
|
metadata: null,
|
|
924
1322
|
labels: null
|
|
925
1323
|
}]);
|
|
926
1324
|
} finally {
|
|
927
|
-
await o(`DROP TABLE IF EXISTS ${
|
|
1325
|
+
await o(`DROP TABLE IF EXISTS ${r}`);
|
|
928
1326
|
}
|
|
929
|
-
}),
|
|
1327
|
+
}), U.each([
|
|
930
1328
|
"",
|
|
931
1329
|
"1,2",
|
|
932
1330
|
"1,\"other\":2",
|
|
@@ -934,12 +1332,12 @@ var K = h("voltro_parity_composite", {
|
|
|
934
1332
|
"{bad}",
|
|
935
1333
|
"undefined"
|
|
936
1334
|
])("JSON check rejects raw invalid value %j", async (e) => {
|
|
937
|
-
await
|
|
938
|
-
let t = yield*
|
|
939
|
-
yield* t`INSERT INTO ${t(
|
|
1335
|
+
await H(a.run(D.gen(function* () {
|
|
1336
|
+
let t = yield* G.SqlClient;
|
|
1337
|
+
yield* t`INSERT INTO ${t(Z.tableName)} (${t("id")}, ${t("title")}, ${t("metadata")}) VALUES (${"invalid-json"}, ${"probe"}, ${e})`;
|
|
940
1338
|
}))).rejects.toThrow();
|
|
941
|
-
}),
|
|
942
|
-
|
|
1339
|
+
}), V.each(["direct", "transactional"])("upsert recorder atomicity (%s)", (t) => {
|
|
1340
|
+
U.each([
|
|
943
1341
|
{
|
|
944
1342
|
kind: "native",
|
|
945
1343
|
existing: !1
|
|
@@ -956,21 +1354,21 @@ var K = h("voltro_parity_composite", {
|
|
|
956
1354
|
kind: "functional",
|
|
957
1355
|
existing: !0
|
|
958
1356
|
}
|
|
959
|
-
])("$kind upsert (existing=$existing) rolls back its write and recorder append", async ({ kind:
|
|
960
|
-
let
|
|
1357
|
+
])("$kind upsert (existing=$existing) rolls back its write and recorder append", async ({ kind: n, existing: r }) => {
|
|
1358
|
+
let i = a.store, o = "voltro_parity_a", s = {
|
|
961
1359
|
id: "recorded",
|
|
962
1360
|
title: "before",
|
|
963
1361
|
count: 0
|
|
964
1362
|
};
|
|
965
|
-
|
|
966
|
-
let c = `parity-upsert-${
|
|
1363
|
+
r && await i.insert(o, s);
|
|
1364
|
+
let c = `parity-upsert-${e.dialect.id}`, l = [], u = i.onChange((e) => {
|
|
967
1365
|
e.table === o && l.push(e.op);
|
|
968
1366
|
});
|
|
969
|
-
|
|
1367
|
+
x(c, {
|
|
970
1368
|
tables: [o],
|
|
971
1369
|
ownTables: ["voltro_parity_b"],
|
|
972
1370
|
recorder: async (e, t) => {
|
|
973
|
-
throw
|
|
1371
|
+
throw H(t.traceId).toBe("recorder-trace"), H(t.subjectId).toBe("recorder-subject"), await e.append("voltro_parity_b", {
|
|
974
1372
|
id: "recorder-append",
|
|
975
1373
|
title: "trail"
|
|
976
1374
|
}), Error("recorder refused");
|
|
@@ -982,39 +1380,39 @@ var K = h("voltro_parity_composite", {
|
|
|
982
1380
|
count: 1
|
|
983
1381
|
}, {
|
|
984
1382
|
conflictColumns: ["id"],
|
|
985
|
-
update:
|
|
1383
|
+
update: n === "native" ? ["title"] : () => ({ title: "computed" })
|
|
986
1384
|
});
|
|
987
1385
|
try {
|
|
988
|
-
await
|
|
1386
|
+
await H(ne({
|
|
989
1387
|
traceId: "recorder-trace",
|
|
990
1388
|
subjectId: "recorder-subject"
|
|
991
|
-
}, () =>
|
|
1389
|
+
}, () => t === "direct" ? d(i) : i.transactional(d))).rejects.toThrow("recorder refused"), H(await i.query(X(o))).toEqual(r ? [s] : []), H(await i.query(X("voltro_parity_b"))).toEqual([]), H(l).toEqual([]);
|
|
992
1390
|
} finally {
|
|
993
|
-
|
|
1391
|
+
re(c), u();
|
|
994
1392
|
}
|
|
995
1393
|
});
|
|
996
|
-
}),
|
|
997
|
-
let e =
|
|
1394
|
+
}), U("structured write returns and change images decode exactly once", async () => {
|
|
1395
|
+
let e = a.store, t = Z.tableName, n = [], r = e.onChange((e) => {
|
|
998
1396
|
e.table === t && n.push((e.new ?? e.old)?.metadata);
|
|
999
|
-
}),
|
|
1397
|
+
}), i = (e) => ({
|
|
1000
1398
|
id: e,
|
|
1001
1399
|
title: "before",
|
|
1002
1400
|
metadata: "null",
|
|
1003
1401
|
labels: ["x"]
|
|
1004
1402
|
});
|
|
1005
1403
|
try {
|
|
1006
|
-
|
|
1404
|
+
H((await e.insertMany(t, [i("one"), i("two")])).map((e) => e.metadata)).toEqual(["null", "null"]), H((await e.upsert(t, i("one"), {
|
|
1007
1405
|
conflictColumns: ["id"],
|
|
1008
1406
|
update: ["metadata"]
|
|
1009
|
-
})).metadata).toBe("null"),
|
|
1407
|
+
})).metadata).toBe("null"), H((await e.insertIgnore(t, i("three"), { conflictColumns: ["id"] })).metadata).toBe("null"), H((await e.insertIgnore(t, i("three"), { conflictColumns: ["id"] })).metadata).toBe("null"), await e.upsert(t, i("one"), {
|
|
1010
1408
|
conflictColumns: ["id"],
|
|
1011
|
-
update: (e) => (
|
|
1012
|
-
}), await e.updateMany(t, { metadata: "null" }, { where:
|
|
1409
|
+
update: (e) => (H(e.metadata).toBe("null"), { metadata: "null" })
|
|
1410
|
+
}), await e.updateMany(t, { metadata: "null" }, { where: p("id", "one") }), await e.delete(t, "two"), await e.deleteMany(t, { where: p("id", "three") }), H(n.length).toBeGreaterThanOrEqual(8), H(n.every((e) => e === "null")).toBe(!0);
|
|
1013
1411
|
} finally {
|
|
1014
|
-
|
|
1412
|
+
r();
|
|
1015
1413
|
}
|
|
1016
|
-
}),
|
|
1017
|
-
let t = (t) => e === "transactional" ?
|
|
1414
|
+
}), V.each(["direct", "transactional"])("JSON patch and column onUpdate (%s)", (e) => {
|
|
1415
|
+
let t = (t) => e === "transactional" ? a.store.transactional(t) : t(a.store), n = [
|
|
1018
1416
|
{
|
|
1019
1417
|
name: "shallow object merge retains unrelated keys and nulls",
|
|
1020
1418
|
before: {
|
|
@@ -1232,7 +1630,7 @@ var K = h("voltro_parity_composite", {
|
|
|
1232
1630
|
}
|
|
1233
1631
|
}))
|
|
1234
1632
|
];
|
|
1235
|
-
|
|
1633
|
+
U.each(n)("$name with companion fields in the same write", async ({ before: e, path: n, value: r, after: i }) => {
|
|
1236
1634
|
await t(async (t) => {
|
|
1237
1635
|
await t.insert("voltro_parity_values", {
|
|
1238
1636
|
id: "patch",
|
|
@@ -1244,19 +1642,19 @@ var K = h("voltro_parity_composite", {
|
|
|
1244
1642
|
title: "after",
|
|
1245
1643
|
labels: []
|
|
1246
1644
|
});
|
|
1247
|
-
|
|
1645
|
+
H(a).toMatchObject({
|
|
1248
1646
|
id: "patch",
|
|
1249
1647
|
title: "after",
|
|
1250
1648
|
metadata: i,
|
|
1251
1649
|
labels: []
|
|
1252
|
-
}),
|
|
1650
|
+
}), H(await t.query(X("voltro_parity_values"))).toEqual([{
|
|
1253
1651
|
id: "patch",
|
|
1254
1652
|
title: "after",
|
|
1255
1653
|
metadata: i,
|
|
1256
1654
|
labels: []
|
|
1257
1655
|
}]);
|
|
1258
1656
|
});
|
|
1259
|
-
}),
|
|
1657
|
+
}), U("refuses companion primary-key or JSON-column replacement before writing", async () => {
|
|
1260
1658
|
await t(async (e) => {
|
|
1261
1659
|
let t = {
|
|
1262
1660
|
id: "refuse",
|
|
@@ -1265,16 +1663,16 @@ var K = h("voltro_parity_composite", {
|
|
|
1265
1663
|
labels: []
|
|
1266
1664
|
};
|
|
1267
1665
|
await e.insert("voltro_parity_values", t);
|
|
1268
|
-
for (let t of [{ id: "renamed" }, { metadata: {} }]) await
|
|
1269
|
-
|
|
1666
|
+
for (let t of [{ id: "renamed" }, { metadata: {} }]) await H(e.patchJson("voltro_parity_values", "refuse", "metadata.value", 1, t)).rejects.toThrow("cannot rewrite");
|
|
1667
|
+
H(await e.query(X("voltro_parity_values"))).toEqual([t]), H(await e.patchJson("voltro_parity_values", "missing", "metadata.value", 1, { title: "absent" })).toBeNull();
|
|
1270
1668
|
});
|
|
1271
|
-
}),
|
|
1669
|
+
}), U("does not confuse JSON columns named key/value with iterator columns", async () => {
|
|
1272
1670
|
await t(async (e) => {
|
|
1273
|
-
await e.insert(
|
|
1671
|
+
await e.insert(Q.tableName, {
|
|
1274
1672
|
id: "names",
|
|
1275
1673
|
key: { "a\"b": { keep: !0 } },
|
|
1276
1674
|
value: { "c\\d": { keep: !1 } }
|
|
1277
|
-
}), await e.patchJson(
|
|
1675
|
+
}), await e.patchJson(Q.tableName, "names", "key.a\"b.changed", !1), await e.patchJson(Q.tableName, "names", "value.c\\d.changed", !0), H(await e.query(X(Q.tableName))).toEqual([{
|
|
1278
1676
|
id: "names",
|
|
1279
1677
|
key: { "a\"b": {
|
|
1280
1678
|
keep: !0,
|
|
@@ -1286,21 +1684,21 @@ var K = h("voltro_parity_composite", {
|
|
|
1286
1684
|
} }
|
|
1287
1685
|
}]);
|
|
1288
1686
|
});
|
|
1289
|
-
}),
|
|
1290
|
-
let t =
|
|
1291
|
-
tableName:
|
|
1292
|
-
fields:
|
|
1687
|
+
}), U("executes application onUpdate across keyed, bulk, upsert and JSON writes", async () => {
|
|
1688
|
+
let t = F([{
|
|
1689
|
+
tableName: _e.tableName,
|
|
1690
|
+
fields: _e.fields
|
|
1293
1691
|
}, {
|
|
1294
|
-
tableName:
|
|
1295
|
-
fields:
|
|
1296
|
-
}]), n =
|
|
1692
|
+
tableName: Z.tableName,
|
|
1693
|
+
fields: Z.fields
|
|
1694
|
+
}]), n = B(a.store, {
|
|
1297
1695
|
subject: {
|
|
1298
1696
|
type: "anonymous",
|
|
1299
1697
|
id: null,
|
|
1300
1698
|
tenantId: null
|
|
1301
1699
|
},
|
|
1302
1700
|
schemaRegistry: t
|
|
1303
|
-
}),
|
|
1701
|
+
}), r = async (e) => {
|
|
1304
1702
|
let t = /* @__PURE__ */ new Date("2026-01-01T00:00:00.000Z"), n = Date.now();
|
|
1305
1703
|
for (let n of [
|
|
1306
1704
|
"keyed",
|
|
@@ -1312,29 +1710,29 @@ var K = h("voltro_parity_composite", {
|
|
|
1312
1710
|
title: "before",
|
|
1313
1711
|
at: t,
|
|
1314
1712
|
n: 9
|
|
1315
|
-
}), n === "keyed" ? await e.update("voltro_parity_d", n, { title: "edited" }) : n === "bulk" ? await e.updateMany("voltro_parity_d", { title: "edited" }, { where:
|
|
1713
|
+
}), n === "keyed" ? await e.update("voltro_parity_d", n, { title: "edited" }) : n === "bulk" ? await e.updateMany("voltro_parity_d", { title: "edited" }, { where: p("id", n) }) : await e.upsert("voltro_parity_d", {
|
|
1316
1714
|
id: n,
|
|
1317
1715
|
title: "edited"
|
|
1318
1716
|
}, {
|
|
1319
1717
|
conflictColumns: ["id"],
|
|
1320
1718
|
update: n === "callback" ? () => ({ title: "edited" }) : ["title"]
|
|
1321
1719
|
});
|
|
1322
|
-
let r = await e.query(
|
|
1323
|
-
|
|
1720
|
+
let r = await e.query(X("voltro_parity_d"));
|
|
1721
|
+
H(r).toHaveLength(4);
|
|
1324
1722
|
for (let e of r) {
|
|
1325
|
-
|
|
1723
|
+
H(e).toMatchObject({
|
|
1326
1724
|
title: "edited",
|
|
1327
1725
|
n: 9
|
|
1328
1726
|
});
|
|
1329
1727
|
let t = e.at instanceof Date ? e.at.getTime() : new Date(String(e.at)).getTime();
|
|
1330
|
-
|
|
1728
|
+
H(t).toBeGreaterThanOrEqual(n), H(t).toBeLessThanOrEqual(Date.now());
|
|
1331
1729
|
}
|
|
1332
1730
|
await e.insert("voltro_parity_values", {
|
|
1333
1731
|
id: "automatic",
|
|
1334
1732
|
title: "original",
|
|
1335
1733
|
metadata: { keep: !0 },
|
|
1336
1734
|
labels: []
|
|
1337
|
-
}), await e.patchJson("voltro_parity_values", "automatic", "metadata.child.value", !1),
|
|
1735
|
+
}), await e.patchJson("voltro_parity_values", "automatic", "metadata.child.value", !1), H(await e.query(X("voltro_parity_values"))).toEqual([{
|
|
1338
1736
|
id: "automatic",
|
|
1339
1737
|
title: "automatic",
|
|
1340
1738
|
metadata: {
|
|
@@ -1342,7 +1740,7 @@ var K = h("voltro_parity_composite", {
|
|
|
1342
1740
|
child: { value: !1 }
|
|
1343
1741
|
},
|
|
1344
1742
|
labels: []
|
|
1345
|
-
}]),
|
|
1743
|
+
}]), H(await e.upsert("voltro_parity_d", {
|
|
1346
1744
|
id: "new",
|
|
1347
1745
|
title: "insert"
|
|
1348
1746
|
}, {
|
|
@@ -1350,35 +1748,35 @@ var K = h("voltro_parity_composite", {
|
|
|
1350
1748
|
update: []
|
|
1351
1749
|
})).toMatchObject({ at: null });
|
|
1352
1750
|
};
|
|
1353
|
-
e === "transactional" ? await n.transactional(
|
|
1751
|
+
e === "transactional" ? await n.transactional(r) : await r(n);
|
|
1354
1752
|
});
|
|
1355
|
-
}),
|
|
1753
|
+
}), U("JSON patch companion failure rolls back the JSON write too", async () => {
|
|
1356
1754
|
let e = {
|
|
1357
1755
|
id: "atomic-json",
|
|
1358
1756
|
title: "keep",
|
|
1359
1757
|
metadata: { keep: !0 },
|
|
1360
1758
|
labels: []
|
|
1361
1759
|
};
|
|
1362
|
-
await
|
|
1363
|
-
}),
|
|
1364
|
-
await
|
|
1760
|
+
await a.store.insert("voltro_parity_values", e), await H(a.store.patchJson("voltro_parity_values", e.id, "metadata.changed", !0, { title: null })).rejects.toThrow(), H(await a.store.query(X("voltro_parity_values"))).toEqual([e]);
|
|
1761
|
+
}), U("concurrent JSON patches preserve every sibling and the companion write", async () => {
|
|
1762
|
+
await a.store.insert("voltro_parity_values", {
|
|
1365
1763
|
id: "concurrent-json",
|
|
1366
1764
|
title: "before",
|
|
1367
1765
|
metadata: {},
|
|
1368
1766
|
labels: []
|
|
1369
|
-
}), await Promise.all(Array.from({ length: 8 }, (e, t) =>
|
|
1767
|
+
}), await Promise.all(Array.from({ length: 8 }, (e, t) => a.store.transactional((e) => e.patchJson("voltro_parity_values", "concurrent-json", `metadata.key${t}`, t, { title: "after" })))), H(await a.store.query(X("voltro_parity_values"))).toEqual([{
|
|
1370
1768
|
id: "concurrent-json",
|
|
1371
1769
|
title: "after",
|
|
1372
1770
|
metadata: Object.fromEntries(Array.from({ length: 8 }, (e, t) => [`key${t}`, t])),
|
|
1373
1771
|
labels: []
|
|
1374
1772
|
}]);
|
|
1375
|
-
}),
|
|
1376
|
-
await
|
|
1773
|
+
}), U("upsert on a NON-primary-key unique constraint updates that row", async () => {
|
|
1774
|
+
await a.store.upsert("voltro_parity_upsert", {
|
|
1377
1775
|
id: "up_first",
|
|
1378
1776
|
teamId: "t-1",
|
|
1379
1777
|
year: 2026,
|
|
1380
1778
|
hours: 10
|
|
1381
|
-
}, { conflictColumns: ["teamId", "year"] }), await
|
|
1779
|
+
}, { conflictColumns: ["teamId", "year"] }), await a.store.upsert("voltro_parity_upsert", {
|
|
1382
1780
|
id: "up_second",
|
|
1383
1781
|
teamId: "t-1",
|
|
1384
1782
|
year: 2026,
|
|
@@ -1387,16 +1785,16 @@ var K = h("voltro_parity_composite", {
|
|
|
1387
1785
|
conflictColumns: ["teamId", "year"],
|
|
1388
1786
|
update: ["hours"]
|
|
1389
1787
|
});
|
|
1390
|
-
let e = await
|
|
1391
|
-
|
|
1788
|
+
let e = await a.store.query(X("voltro_parity_upsert"));
|
|
1789
|
+
H(e.length, "the second upsert inserted a duplicate instead of updating").toBe(1), H(e[0]).toMatchObject({
|
|
1392
1790
|
id: "up_first",
|
|
1393
1791
|
teamId: "t-1",
|
|
1394
1792
|
year: 2026,
|
|
1395
1793
|
hours: 111
|
|
1396
1794
|
});
|
|
1397
|
-
}),
|
|
1398
|
-
let t = (t) => e === "transactional" ?
|
|
1399
|
-
|
|
1795
|
+
}), V.each(["direct", "transactional"])("upsert updateValues (%s)", (e) => {
|
|
1796
|
+
let t = (t) => e === "transactional" ? a.store.transactional(t) : t(a.store);
|
|
1797
|
+
U("keeps INSERT defaults and writes omitted columns only on conflict", async () => {
|
|
1400
1798
|
let e = /* @__PURE__ */ new Date("2026-09-05T10:20:30.000Z"), n = {
|
|
1401
1799
|
conflictColumns: ["id"],
|
|
1402
1800
|
update: [],
|
|
@@ -1410,7 +1808,7 @@ var K = h("voltro_parity_composite", {
|
|
|
1410
1808
|
id: "constant",
|
|
1411
1809
|
title: "Lobby"
|
|
1412
1810
|
}, n);
|
|
1413
|
-
|
|
1811
|
+
H(r).toMatchObject({
|
|
1414
1812
|
id: "constant",
|
|
1415
1813
|
title: "Lobby",
|
|
1416
1814
|
at: null,
|
|
@@ -1420,19 +1818,19 @@ var K = h("voltro_parity_composite", {
|
|
|
1420
1818
|
id: "constant",
|
|
1421
1819
|
title: "Do not overwrite"
|
|
1422
1820
|
}, n);
|
|
1423
|
-
|
|
1821
|
+
H(i).toMatchObject({
|
|
1424
1822
|
id: "constant",
|
|
1425
1823
|
title: "Lobby",
|
|
1426
1824
|
n: 42
|
|
1427
|
-
}),
|
|
1825
|
+
}), H(new Date(String(i.at)).toISOString()).toBe(e.toISOString());
|
|
1428
1826
|
});
|
|
1429
|
-
let [
|
|
1430
|
-
|
|
1827
|
+
let [r] = await a.store.query(X("voltro_parity_d"));
|
|
1828
|
+
H(r).toMatchObject({
|
|
1431
1829
|
id: "constant",
|
|
1432
1830
|
title: "Lobby",
|
|
1433
1831
|
n: 42
|
|
1434
|
-
}),
|
|
1435
|
-
}),
|
|
1832
|
+
}), H(new Date(String(r.at)).toISOString()).toBe(e.toISOString());
|
|
1833
|
+
}), U.each([
|
|
1436
1834
|
"omitted",
|
|
1437
1835
|
"empty",
|
|
1438
1836
|
"columns",
|
|
@@ -1455,19 +1853,19 @@ var K = h("voltro_parity_composite", {
|
|
|
1455
1853
|
...n === void 0 ? {} : { update: n },
|
|
1456
1854
|
updateValues: { hours: 42 }
|
|
1457
1855
|
});
|
|
1458
|
-
|
|
1856
|
+
H(r).toMatchObject({
|
|
1459
1857
|
id: "original",
|
|
1460
1858
|
teamId: "t1",
|
|
1461
1859
|
year: 2026,
|
|
1462
1860
|
hours: 42
|
|
1463
1861
|
});
|
|
1464
|
-
}),
|
|
1862
|
+
}), H(await a.store.query(X("voltro_parity_upsert"))).toEqual([H.objectContaining({
|
|
1465
1863
|
id: "original",
|
|
1466
1864
|
teamId: "t1",
|
|
1467
1865
|
year: 2026,
|
|
1468
1866
|
hours: 42
|
|
1469
1867
|
})]);
|
|
1470
|
-
}),
|
|
1868
|
+
}), U("binds explicit NULL to timestamp/int columns and omits undefined assignments", async () => {
|
|
1471
1869
|
await t(async (e) => {
|
|
1472
1870
|
await e.insert("voltro_parity_d", {
|
|
1473
1871
|
id: "nulls",
|
|
@@ -1489,12 +1887,12 @@ var K = h("voltro_parity_composite", {
|
|
|
1489
1887
|
n: void 0
|
|
1490
1888
|
}
|
|
1491
1889
|
});
|
|
1492
|
-
|
|
1890
|
+
H(n).toMatchObject({
|
|
1493
1891
|
id: "nulls",
|
|
1494
1892
|
title: "",
|
|
1495
1893
|
at: null,
|
|
1496
1894
|
n: 9
|
|
1497
|
-
}),
|
|
1895
|
+
}), H(await e.upsert("voltro_parity_d", t, {
|
|
1498
1896
|
conflictColumns: ["id"],
|
|
1499
1897
|
update: [],
|
|
1500
1898
|
updateValues: { n: null }
|
|
@@ -1505,7 +1903,7 @@ var K = h("voltro_parity_composite", {
|
|
|
1505
1903
|
n: null
|
|
1506
1904
|
});
|
|
1507
1905
|
});
|
|
1508
|
-
}),
|
|
1906
|
+
}), U("binds false and zero without treating them as omitted", async () => {
|
|
1509
1907
|
await t(async (e) => {
|
|
1510
1908
|
let t = {
|
|
1511
1909
|
id: "falsy",
|
|
@@ -1521,14 +1919,14 @@ var K = h("voltro_parity_composite", {
|
|
|
1521
1919
|
count: 0
|
|
1522
1920
|
}
|
|
1523
1921
|
});
|
|
1524
|
-
let n = await e.query(
|
|
1525
|
-
|
|
1922
|
+
let n = await e.query(X("voltro_parity_c"));
|
|
1923
|
+
H(n).toEqual([H.objectContaining({
|
|
1526
1924
|
id: "falsy",
|
|
1527
1925
|
title: "keep",
|
|
1528
1926
|
count: 0
|
|
1529
|
-
})]),
|
|
1927
|
+
})]), H([!1, 0]).toContain(n[0].flag);
|
|
1530
1928
|
});
|
|
1531
|
-
}),
|
|
1929
|
+
}), U("encodes JSON/array assignments and binds SQL-looking strings as data", async () => {
|
|
1532
1930
|
await t(async (e) => {
|
|
1533
1931
|
let t = {
|
|
1534
1932
|
id: "structured",
|
|
@@ -1553,7 +1951,7 @@ var K = h("voltro_parity_composite", {
|
|
|
1553
1951
|
update: [],
|
|
1554
1952
|
updateValues: n
|
|
1555
1953
|
};
|
|
1556
|
-
await e.upsert("voltro_parity_values", t, r),
|
|
1954
|
+
await e.upsert("voltro_parity_values", t, r), H(await e.query(X("voltro_parity_values"))).toEqual([t]), await e.upsert("voltro_parity_values", t, r), H(await e.query(X("voltro_parity_values"))).toEqual([{
|
|
1557
1955
|
id: "structured",
|
|
1558
1956
|
...n
|
|
1559
1957
|
}]), await e.upsert("voltro_parity_values", t, {
|
|
@@ -1562,14 +1960,14 @@ var K = h("voltro_parity_composite", {
|
|
|
1562
1960
|
metadata: null,
|
|
1563
1961
|
labels: []
|
|
1564
1962
|
}
|
|
1565
|
-
}),
|
|
1963
|
+
}), H(await e.query(X("voltro_parity_values"))).toEqual([{
|
|
1566
1964
|
id: "structured",
|
|
1567
1965
|
title: n.title,
|
|
1568
1966
|
metadata: null,
|
|
1569
1967
|
labels: []
|
|
1570
1968
|
}]);
|
|
1571
1969
|
});
|
|
1572
|
-
}),
|
|
1970
|
+
}), U("keeps structured raw-driver values working through all write verbs", async () => {
|
|
1573
1971
|
await t(async (e) => {
|
|
1574
1972
|
let t = {
|
|
1575
1973
|
title: "before",
|
|
@@ -1589,7 +1987,7 @@ var K = h("voltro_parity_composite", {
|
|
|
1589
1987
|
}, {
|
|
1590
1988
|
id: "callback",
|
|
1591
1989
|
...t
|
|
1592
|
-
}]), await e.update("voltro_parity_values", "single", n), await e.updateMany("voltro_parity_values", n, { where:
|
|
1990
|
+
}]), await e.update("voltro_parity_values", "single", n), await e.updateMany("voltro_parity_values", n, { where: p("id", "bulk") }), await e.upsert("voltro_parity_values", {
|
|
1593
1991
|
id: "callback",
|
|
1594
1992
|
...t
|
|
1595
1993
|
}, {
|
|
@@ -1602,8 +2000,8 @@ var K = h("voltro_parity_composite", {
|
|
|
1602
2000
|
id: "ignore-new",
|
|
1603
2001
|
...n
|
|
1604
2002
|
}, { conflictColumns: ["id"] });
|
|
1605
|
-
let r = await e.query(
|
|
1606
|
-
|
|
2003
|
+
let r = await e.query(X("voltro_parity_values"));
|
|
2004
|
+
H([...r].sort((e, t) => String(e.id).localeCompare(String(t.id)))).toEqual([
|
|
1607
2005
|
{
|
|
1608
2006
|
id: "bulk",
|
|
1609
2007
|
...n
|
|
@@ -1622,7 +2020,7 @@ var K = h("voltro_parity_composite", {
|
|
|
1622
2020
|
}
|
|
1623
2021
|
]);
|
|
1624
2022
|
});
|
|
1625
|
-
}),
|
|
2023
|
+
}), U.each([
|
|
1626
2024
|
"id",
|
|
1627
2025
|
"teamId",
|
|
1628
2026
|
"year"
|
|
@@ -1638,20 +2036,20 @@ var K = h("voltro_parity_composite", {
|
|
|
1638
2036
|
update: [],
|
|
1639
2037
|
updateValues: { [e]: "different" }
|
|
1640
2038
|
};
|
|
1641
|
-
await
|
|
2039
|
+
await H(t.upsert("voltro_parity_upsert", n, r)).rejects.toThrow("identity"), H(await t.query(X("voltro_parity_upsert"))).toEqual([]), await t.insert("voltro_parity_upsert", n), await H(t.upsert("voltro_parity_upsert", n, r)).rejects.toThrow("identity"), H(await t.query(X("voltro_parity_upsert"))).toEqual([H.objectContaining(n)]);
|
|
1642
2040
|
});
|
|
1643
2041
|
});
|
|
1644
|
-
}),
|
|
1645
|
-
await
|
|
2042
|
+
}), U("upsert updateValues rolls back row changes and discards transaction events", async () => {
|
|
2043
|
+
await a.store.insert("voltro_parity_a", {
|
|
1646
2044
|
id: "rollback-values",
|
|
1647
2045
|
title: "keep",
|
|
1648
2046
|
count: 1
|
|
1649
2047
|
});
|
|
1650
|
-
let e = [], t =
|
|
2048
|
+
let e = [], t = a.store.onChange((t) => {
|
|
1651
2049
|
e.push(t);
|
|
1652
2050
|
});
|
|
1653
2051
|
try {
|
|
1654
|
-
await
|
|
2052
|
+
await H(a.store.transactional(async (e) => {
|
|
1655
2053
|
let t = await e.upsert("voltro_parity_a", {
|
|
1656
2054
|
id: "rollback-values",
|
|
1657
2055
|
title: "incoming",
|
|
@@ -1661,25 +2059,25 @@ var K = h("voltro_parity_composite", {
|
|
|
1661
2059
|
update: [],
|
|
1662
2060
|
updateValues: { count: 42 }
|
|
1663
2061
|
});
|
|
1664
|
-
throw
|
|
2062
|
+
throw H(t).toMatchObject({
|
|
1665
2063
|
title: "keep",
|
|
1666
2064
|
count: 42
|
|
1667
2065
|
}), Error("rollback values");
|
|
1668
|
-
})).rejects.toThrow("rollback values"),
|
|
2066
|
+
})).rejects.toThrow("rollback values"), H(await a.store.query(X("voltro_parity_a"))).toEqual([H.objectContaining({
|
|
1669
2067
|
id: "rollback-values",
|
|
1670
2068
|
title: "keep",
|
|
1671
2069
|
count: 1
|
|
1672
|
-
})]),
|
|
2070
|
+
})]), H(e).toEqual([]);
|
|
1673
2071
|
} finally {
|
|
1674
2072
|
t();
|
|
1675
2073
|
}
|
|
1676
|
-
}),
|
|
1677
|
-
await
|
|
2074
|
+
}), U("and a DIFFERENT row is still not silently overwritten", async () => {
|
|
2075
|
+
await a.store.upsert("voltro_parity_upsert", {
|
|
1678
2076
|
id: "up_a",
|
|
1679
2077
|
teamId: "t-1",
|
|
1680
2078
|
year: 2026,
|
|
1681
2079
|
hours: 1
|
|
1682
|
-
}, { conflictColumns: ["teamId", "year"] }), await
|
|
2080
|
+
}, { conflictColumns: ["teamId", "year"] }), await a.store.upsert("voltro_parity_upsert", {
|
|
1683
2081
|
id: "up_b",
|
|
1684
2082
|
teamId: "t-2",
|
|
1685
2083
|
year: 2026,
|
|
@@ -1688,66 +2086,66 @@ var K = h("voltro_parity_composite", {
|
|
|
1688
2086
|
conflictColumns: ["teamId", "year"],
|
|
1689
2087
|
update: ["hours"]
|
|
1690
2088
|
});
|
|
1691
|
-
let e = await
|
|
1692
|
-
|
|
1693
|
-
}),
|
|
1694
|
-
let e = await
|
|
2089
|
+
let e = await a.store.query(X("voltro_parity_upsert"));
|
|
2090
|
+
H(e.length).toBe(2), H([...e].map((e) => e.hours).sort()).toEqual([1, 2]);
|
|
2091
|
+
}), U("insert + update bind an explicit NULL into a datetime2/int column", async () => {
|
|
2092
|
+
let e = await a.store.insert("voltro_parity_d", {
|
|
1695
2093
|
id: "nd1",
|
|
1696
2094
|
title: "nulls",
|
|
1697
2095
|
at: null,
|
|
1698
2096
|
n: null
|
|
1699
2097
|
});
|
|
1700
|
-
|
|
2098
|
+
H(e).toMatchObject({
|
|
1701
2099
|
id: "nd1",
|
|
1702
2100
|
title: "nulls",
|
|
1703
2101
|
at: null,
|
|
1704
2102
|
n: null
|
|
1705
|
-
}), await
|
|
2103
|
+
}), await a.store.insert("voltro_parity_d", {
|
|
1706
2104
|
id: "nd2",
|
|
1707
2105
|
title: "set",
|
|
1708
2106
|
at: /* @__PURE__ */ new Date(),
|
|
1709
2107
|
n: 7
|
|
1710
2108
|
});
|
|
1711
|
-
let t = await
|
|
2109
|
+
let t = await a.store.update("voltro_parity_d", "nd2", {
|
|
1712
2110
|
at: null,
|
|
1713
2111
|
n: null
|
|
1714
2112
|
});
|
|
1715
|
-
|
|
2113
|
+
H(t).toMatchObject({
|
|
1716
2114
|
id: "nd2",
|
|
1717
2115
|
at: null,
|
|
1718
2116
|
n: null
|
|
1719
2117
|
});
|
|
1720
|
-
}),
|
|
1721
|
-
await
|
|
2118
|
+
}), U("updateMany updates only the rows matching the predicate + returns the count", async () => {
|
|
2119
|
+
await a.store.insert("voltro_parity_a", {
|
|
1722
2120
|
id: "m1",
|
|
1723
2121
|
title: "x",
|
|
1724
2122
|
count: 5
|
|
1725
|
-
}), await
|
|
2123
|
+
}), await a.store.insert("voltro_parity_a", {
|
|
1726
2124
|
id: "m2",
|
|
1727
2125
|
title: "y",
|
|
1728
2126
|
count: 5
|
|
1729
|
-
}), await
|
|
2127
|
+
}), await a.store.insert("voltro_parity_a", {
|
|
1730
2128
|
id: "m3",
|
|
1731
2129
|
title: "z",
|
|
1732
2130
|
count: 9
|
|
1733
2131
|
});
|
|
1734
|
-
let e = await
|
|
2132
|
+
let e = await a.store.updateMany("voltro_parity_a", { title: "hit" }, { where: {
|
|
1735
2133
|
column: "count",
|
|
1736
2134
|
op: "eq",
|
|
1737
2135
|
value: 5
|
|
1738
2136
|
} });
|
|
1739
|
-
|
|
1740
|
-
let t = await
|
|
1741
|
-
...
|
|
2137
|
+
H(e).toBe(2);
|
|
2138
|
+
let t = await a.store.query({
|
|
2139
|
+
...X("voltro_parity_a"),
|
|
1742
2140
|
predicate: {
|
|
1743
2141
|
column: "title",
|
|
1744
2142
|
op: "eq",
|
|
1745
2143
|
value: "hit"
|
|
1746
2144
|
}
|
|
1747
2145
|
});
|
|
1748
|
-
|
|
1749
|
-
}),
|
|
1750
|
-
await
|
|
2146
|
+
H(t.length).toBe(2);
|
|
2147
|
+
}), U("updateMany with an AND predicate is an atomic compare-and-set (the wakeup-claim pattern)", async () => {
|
|
2148
|
+
await a.store.insert("voltro_parity_a", {
|
|
1751
2149
|
id: "cas",
|
|
1752
2150
|
title: "pending",
|
|
1753
2151
|
count: 1
|
|
@@ -1760,76 +2158,76 @@ var K = h("voltro_parity_composite", {
|
|
|
1760
2158
|
column: "title",
|
|
1761
2159
|
op: "eq",
|
|
1762
2160
|
value: e
|
|
1763
|
-
}] }), t = await
|
|
1764
|
-
|
|
1765
|
-
let n = await
|
|
1766
|
-
|
|
1767
|
-
}),
|
|
1768
|
-
await
|
|
2161
|
+
}] }), t = await a.store.updateMany("voltro_parity_a", { title: "claimed" }, { where: e("pending") });
|
|
2162
|
+
H(t).toBe(1);
|
|
2163
|
+
let n = await a.store.updateMany("voltro_parity_a", { title: "claimed-again" }, { where: e("pending") });
|
|
2164
|
+
H(n).toBe(0);
|
|
2165
|
+
}), U("deleteMany removes only the rows matching the predicate + returns the count", async () => {
|
|
2166
|
+
await a.store.insert("voltro_parity_a", {
|
|
1769
2167
|
id: "d1",
|
|
1770
2168
|
title: "x",
|
|
1771
2169
|
count: 5
|
|
1772
|
-
}), await
|
|
2170
|
+
}), await a.store.insert("voltro_parity_a", {
|
|
1773
2171
|
id: "d2",
|
|
1774
2172
|
title: "y",
|
|
1775
2173
|
count: 5
|
|
1776
|
-
}), await
|
|
2174
|
+
}), await a.store.insert("voltro_parity_a", {
|
|
1777
2175
|
id: "d3",
|
|
1778
2176
|
title: "z",
|
|
1779
2177
|
count: 9
|
|
1780
2178
|
});
|
|
1781
|
-
let e = await
|
|
2179
|
+
let e = await a.store.deleteMany("voltro_parity_a", { where: {
|
|
1782
2180
|
column: "count",
|
|
1783
2181
|
op: "eq",
|
|
1784
2182
|
value: 5
|
|
1785
2183
|
} });
|
|
1786
|
-
|
|
1787
|
-
let t = await
|
|
1788
|
-
|
|
1789
|
-
}),
|
|
1790
|
-
await
|
|
2184
|
+
H(e).toBe(2);
|
|
2185
|
+
let t = await a.store.query(X("voltro_parity_a"));
|
|
2186
|
+
H(t.map((e) => e.id)).toEqual(["d3"]);
|
|
2187
|
+
}), U("deleteMany emits one delete ChangeEvent per removed row (old-image)", async () => {
|
|
2188
|
+
await a.store.insert("voltro_parity_a", {
|
|
1791
2189
|
id: "e1",
|
|
1792
2190
|
title: "gone",
|
|
1793
2191
|
count: 1
|
|
1794
|
-
}), await
|
|
2192
|
+
}), await a.store.insert("voltro_parity_a", {
|
|
1795
2193
|
id: "e2",
|
|
1796
2194
|
title: "gone",
|
|
1797
2195
|
count: 1
|
|
1798
|
-
}), await
|
|
2196
|
+
}), await a.store.insert("voltro_parity_a", {
|
|
1799
2197
|
id: "e3",
|
|
1800
2198
|
title: "stay",
|
|
1801
2199
|
count: 2
|
|
1802
2200
|
});
|
|
1803
|
-
let e = [], t =
|
|
2201
|
+
let e = [], t = a.store.onChange((t) => e.push({
|
|
1804
2202
|
op: t.op,
|
|
1805
2203
|
id: t.old?.id
|
|
1806
|
-
})), n = await
|
|
2204
|
+
})), n = await a.store.deleteMany("voltro_parity_a", { where: {
|
|
1807
2205
|
column: "title",
|
|
1808
2206
|
op: "eq",
|
|
1809
2207
|
value: "gone"
|
|
1810
2208
|
} });
|
|
1811
|
-
t(),
|
|
1812
|
-
let
|
|
1813
|
-
|
|
1814
|
-
}),
|
|
1815
|
-
await
|
|
2209
|
+
t(), H(n).toBe(2);
|
|
2210
|
+
let r = e.filter((e) => e.op === "delete");
|
|
2211
|
+
H(r).toHaveLength(2), H(r.map((e) => e.id).sort()).toEqual(["e1", "e2"]);
|
|
2212
|
+
}), U("deleteMany with no match returns 0 and removes nothing", async () => {
|
|
2213
|
+
await a.store.insert("voltro_parity_a", {
|
|
1816
2214
|
id: "k1",
|
|
1817
2215
|
title: "keep",
|
|
1818
2216
|
count: 1
|
|
1819
2217
|
});
|
|
1820
|
-
let e = await
|
|
2218
|
+
let e = await a.store.deleteMany("voltro_parity_a", { where: {
|
|
1821
2219
|
column: "title",
|
|
1822
2220
|
op: "eq",
|
|
1823
2221
|
value: "absent"
|
|
1824
2222
|
} });
|
|
1825
|
-
|
|
1826
|
-
let t = await
|
|
1827
|
-
|
|
1828
|
-
}),
|
|
1829
|
-
let
|
|
1830
|
-
|
|
2223
|
+
H(e).toBe(0);
|
|
2224
|
+
let t = await a.store.query(X("voltro_parity_a"));
|
|
2225
|
+
H(t.length).toBe(1);
|
|
2226
|
+
}), U("retryFilter recognises the dialect's own transient codes", () => {
|
|
2227
|
+
let t = e.dialect.retryFilter(/* @__PURE__ */ Error("garbage"));
|
|
2228
|
+
H(["retry", "noRetry"]).toContain(t);
|
|
1831
2229
|
});
|
|
1832
2230
|
});
|
|
1833
2231
|
};
|
|
1834
2232
|
//#endregion
|
|
1835
|
-
export {
|
|
2233
|
+
export { q as applySchema, $ as runDialectParity, e as runHotStoreSqlParity };
|