@voltro/testing 0.86.0 → 0.88.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 +5484 -0
- package/README.md +11 -0
- package/THIRD-PARTY-NOTICES.md +1511 -276
- package/dist/client.d.ts +5 -0
- package/dist/client.js +70 -58
- package/dist/crdt.js +1 -2
- package/dist/dialect.d.ts +20 -0
- package/dist/dialect.js +992 -694
- package/dist/factory.js +1 -2
- package/dist/fieldKeys.js +72 -0
- package/dist/fieldKeysSql.js +13 -0
- package/dist/fixtureRow.js +1 -2
- package/dist/formBindingHarness.js +1 -2
- package/dist/hotStoreSqlParity.js +135 -0
- package/dist/hydrateFixtureRows.js +1 -2
- package/dist/index.d.ts +119 -1
- package/dist/index.js +18 -16
- package/dist/invoke.js +77 -40
- package/dist/mockClock.js +1 -2
- package/dist/mockLLM.js +1 -2
- package/dist/mockWebhooks.js +1 -2
- package/dist/platform.js +1 -2
- package/dist/reachable.js +1 -2
- package/dist/serviceApiMock.js +1 -2
- package/dist/subject.js +1 -2
- package/dist/subscribeContext.js +1 -2
- package/dist/system.d.ts +99 -0
- package/dist/system.js +93 -0
- package/dist/testApps.js +1 -2
- package/dist/testContext.js +133 -113
- package/dist/testEventBus.js +1 -2
- package/dist/testRequest.js +56 -55
- package/dist/withConnections.js +25 -0
- package/dist/workflowRunner.js +1 -2
- package/package.json +17 -10
package/dist/dialect.js
CHANGED
|
@@ -1,82 +1,83 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
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";
|
|
9
|
+
// src/dialectParity.ts
|
|
10
|
+
var X = (e) => ({
|
|
10
11
|
table: e,
|
|
11
12
|
predicate: void 0,
|
|
12
13
|
order: [],
|
|
13
14
|
take: void 0,
|
|
14
15
|
skip: void 0,
|
|
15
16
|
projection: void 0
|
|
16
|
-
}),
|
|
17
|
-
id:
|
|
18
|
-
title:
|
|
19
|
-
count:
|
|
20
|
-
}),
|
|
21
|
-
id:
|
|
22
|
-
title:
|
|
23
|
-
}),
|
|
24
|
-
id:
|
|
25
|
-
title:
|
|
26
|
-
count:
|
|
27
|
-
flag:
|
|
28
|
-
}),
|
|
29
|
-
id:
|
|
30
|
-
title:
|
|
31
|
-
at:
|
|
32
|
-
n:
|
|
33
|
-
}),
|
|
34
|
-
id:
|
|
35
|
-
teamId:
|
|
36
|
-
year:
|
|
37
|
-
hours:
|
|
38
|
-
}).unique("byTeamYear", ["teamId", "year"]),
|
|
39
|
-
id:
|
|
40
|
-
title:
|
|
41
|
-
metadata:
|
|
42
|
-
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()
|
|
43
44
|
});
|
|
44
|
-
|
|
45
|
-
var
|
|
46
|
-
id:
|
|
47
|
-
key:
|
|
48
|
-
value:
|
|
45
|
+
b(Z);
|
|
46
|
+
var Q = C("voltro_parity_json_names", {
|
|
47
|
+
id: h(),
|
|
48
|
+
key: _(),
|
|
49
|
+
value: _()
|
|
49
50
|
});
|
|
50
|
-
|
|
51
|
-
var
|
|
52
|
-
userId:
|
|
53
|
-
orgId:
|
|
54
|
-
role:
|
|
55
|
-
}).primaryKey(["userId", "orgId"]),
|
|
56
|
-
id:
|
|
57
|
-
title:
|
|
58
|
-
count:
|
|
59
|
-
flag:
|
|
60
|
-
}),
|
|
61
|
-
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);
|
|
62
63
|
if (t === void 0) return 0;
|
|
63
|
-
let n = Object.keys(
|
|
64
|
+
let n = Object.keys(be.fields).length;
|
|
64
65
|
return Math.max(1, Math.min(Math.floor(t.maxBindParameters / n), t.maxRowsPerStatement ?? Infinity));
|
|
65
|
-
},
|
|
66
|
-
|
|
67
|
-
let
|
|
68
|
-
|
|
69
|
-
await
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
80
81
|
]);
|
|
81
82
|
for (let e of [
|
|
82
83
|
"voltro_parity_a",
|
|
@@ -88,42 +89,340 @@ var q = h("voltro_parity_composite", {
|
|
|
88
89
|
"voltro_parity_json_names",
|
|
89
90
|
"_voltro_undo_log"
|
|
90
91
|
]) {
|
|
91
|
-
let t = await
|
|
92
|
-
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);
|
|
93
94
|
}
|
|
94
|
-
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", {
|
|
95
96
|
userId: e.userId,
|
|
96
97
|
orgId: e.orgId
|
|
97
98
|
});
|
|
98
|
-
}),
|
|
99
|
-
|
|
100
|
-
}),
|
|
101
|
-
let
|
|
102
|
-
yield* (yield*
|
|
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}`);
|
|
103
104
|
}));
|
|
104
|
-
|
|
105
|
-
let e = `
|
|
106
|
-
id:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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)
|
|
110
112
|
}).nonReactive();
|
|
111
|
-
|
|
113
|
+
b(t);
|
|
112
114
|
try {
|
|
113
|
-
await
|
|
114
|
-
let
|
|
115
|
-
|
|
116
|
-
|
|
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,
|
|
117
416
|
appliedMixins: [{ id: "voltro/tenant" }]
|
|
118
|
-
}]),
|
|
417
|
+
}]), i = (e) => B(a.store, {
|
|
119
418
|
subject: {
|
|
120
419
|
type: "user",
|
|
121
420
|
id: "numeric_writer",
|
|
122
421
|
tenantId: e
|
|
123
422
|
},
|
|
124
|
-
schemaRegistry:
|
|
423
|
+
schemaRegistry: r
|
|
125
424
|
});
|
|
126
|
-
await
|
|
425
|
+
await n(i("owner"), async (t) => {
|
|
127
426
|
for (let n of [
|
|
128
427
|
"update",
|
|
129
428
|
"patchJson",
|
|
@@ -133,48 +432,48 @@ var q = h("voltro_parity_composite", {
|
|
|
133
432
|
title: n,
|
|
134
433
|
metadata: {}
|
|
135
434
|
});
|
|
136
|
-
|
|
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({
|
|
137
436
|
id: r.id,
|
|
138
437
|
title: "edited"
|
|
139
|
-
}) : n === "patchJson" ?
|
|
438
|
+
}) : n === "patchJson" ? H(await t.patchJson(e, r.id, "metadata.seen", !0)).toMatchObject({
|
|
140
439
|
id: r.id,
|
|
141
440
|
metadata: { seen: !0 }
|
|
142
|
-
}) :
|
|
441
|
+
}) : H(await t.delete(e, r.id)).toBe(!0);
|
|
143
442
|
}
|
|
144
|
-
let n = await t.query(
|
|
145
|
-
|
|
443
|
+
let n = await t.query(X(e));
|
|
444
|
+
H(n).toHaveLength(2), H(n.every((e) => typeof e.id == "number")).toBe(!0);
|
|
146
445
|
});
|
|
147
|
-
let [o] = await
|
|
148
|
-
await
|
|
446
|
+
let [o] = await a.store.query(X(e));
|
|
447
|
+
await H(i("other").update(e, o.id, { title: "stolen" })).rejects.toMatchObject({ _tag: "TenantRowNotFound" });
|
|
149
448
|
} finally {
|
|
150
|
-
|
|
449
|
+
E(e), await u(e);
|
|
151
450
|
}
|
|
152
|
-
}),
|
|
153
|
-
let e = `voltro_audit_${
|
|
154
|
-
id:
|
|
155
|
-
externalId:
|
|
156
|
-
value:
|
|
157
|
-
createdAt:
|
|
158
|
-
createdBy:
|
|
159
|
-
updatedAt:
|
|
160
|
-
updatedBy:
|
|
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()
|
|
161
460
|
}).nonReactive();
|
|
162
|
-
|
|
461
|
+
b(t);
|
|
163
462
|
try {
|
|
164
|
-
await
|
|
165
|
-
let
|
|
166
|
-
tableName:
|
|
167
|
-
fields:
|
|
463
|
+
await a.migrate([t]);
|
|
464
|
+
let r = F([{
|
|
465
|
+
tableName: t.tableName,
|
|
466
|
+
fields: t.fields,
|
|
168
467
|
appliedMixins: [{ id: "voltro/audit" }]
|
|
169
|
-
}]),
|
|
468
|
+
}]), i = B(a.store, {
|
|
170
469
|
subject: {
|
|
171
470
|
type: "user",
|
|
172
471
|
id: "editor",
|
|
173
472
|
tenantId: "audit_scope"
|
|
174
473
|
},
|
|
175
|
-
schemaRegistry:
|
|
474
|
+
schemaRegistry: r
|
|
176
475
|
}), o = /* @__PURE__ */ new Date("2026-01-01T00:00:00Z");
|
|
177
|
-
await
|
|
476
|
+
await n(i, async (t) => {
|
|
178
477
|
for (let n of ["upsert", "upsertWithOutcome"]) {
|
|
179
478
|
await t.insert(e, {
|
|
180
479
|
externalId: n,
|
|
@@ -190,483 +489,483 @@ var q = h("voltro_parity_composite", {
|
|
|
190
489
|
externalId: n,
|
|
191
490
|
value: "after"
|
|
192
491
|
}, { conflictColumns: ["externalId"] })).row;
|
|
193
|
-
|
|
492
|
+
H(r).toMatchObject({
|
|
194
493
|
value: "after",
|
|
195
494
|
createdBy: "creator",
|
|
196
495
|
updatedBy: "editor"
|
|
197
|
-
}),
|
|
496
|
+
}), H(new Date(String(r.createdAt)).toISOString()).toBe(o.toISOString()), H(new Date(String(r.updatedAt)).getTime()).toBeGreaterThan(o.getTime());
|
|
198
497
|
}
|
|
199
498
|
});
|
|
200
499
|
} finally {
|
|
201
|
-
|
|
500
|
+
E(e), await u(e);
|
|
202
501
|
}
|
|
203
502
|
});
|
|
204
|
-
}),
|
|
205
|
-
let
|
|
206
|
-
await
|
|
207
|
-
let e = yield*
|
|
208
|
-
for (let
|
|
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}`), () => []);
|
|
209
508
|
})).catch(() => {});
|
|
210
|
-
}, s = () =>
|
|
211
|
-
let e = yield*
|
|
509
|
+
}, s = () => i(D.gen(function* () {
|
|
510
|
+
let e = yield* G.SqlClient, t = yield* J(e);
|
|
212
511
|
return {
|
|
213
|
-
...
|
|
214
|
-
tables:
|
|
512
|
+
...t,
|
|
513
|
+
tables: t.tables.filter((e) => e.name === n || e.name === r)
|
|
215
514
|
};
|
|
216
|
-
})),
|
|
217
|
-
declared: [
|
|
515
|
+
})), c = async () => Y({
|
|
516
|
+
declared: [u],
|
|
218
517
|
live: await s(),
|
|
219
|
-
dialect:
|
|
518
|
+
dialect: e.dialect.id
|
|
220
519
|
});
|
|
221
520
|
await o();
|
|
222
|
-
let
|
|
223
|
-
id:
|
|
224
|
-
title:
|
|
225
|
-
}),
|
|
226
|
-
id:
|
|
227
|
-
title:
|
|
228
|
-
}).renamedFrom(
|
|
229
|
-
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, {
|
|
230
529
|
id: "rn_1",
|
|
231
530
|
title: "one"
|
|
232
|
-
}), await
|
|
531
|
+
}), await a.store.insert(n, {
|
|
233
532
|
id: "rn_2",
|
|
234
533
|
title: "two"
|
|
235
534
|
});
|
|
236
|
-
let
|
|
237
|
-
|
|
238
|
-
let e = yield*
|
|
239
|
-
return yield*
|
|
240
|
-
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],
|
|
241
540
|
appliedBy: "dialect-parity",
|
|
242
541
|
environment: "dev",
|
|
243
542
|
source: "auto-diff",
|
|
244
|
-
replan: () =>
|
|
543
|
+
replan: () => D.promise(c)
|
|
245
544
|
});
|
|
246
545
|
}));
|
|
247
|
-
let
|
|
248
|
-
|
|
249
|
-
}),
|
|
250
|
-
let
|
|
251
|
-
await
|
|
252
|
-
let e = yield*
|
|
253
|
-
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"), () => []);
|
|
254
553
|
})).catch(() => {});
|
|
255
|
-
},
|
|
256
|
-
let e = yield*
|
|
554
|
+
}, o = () => r(D.gen(function* () {
|
|
555
|
+
let e = yield* G.SqlClient, t = yield* J(e);
|
|
257
556
|
return {
|
|
258
|
-
...
|
|
259
|
-
tables:
|
|
557
|
+
...t,
|
|
558
|
+
tables: t.tables.filter((e) => e.name === n)
|
|
260
559
|
};
|
|
261
|
-
})),
|
|
262
|
-
declared:
|
|
263
|
-
live: await
|
|
264
|
-
dialect:
|
|
265
|
-
}),
|
|
266
|
-
let
|
|
267
|
-
|
|
268
|
-
let r = yield*
|
|
269
|
-
return yield*
|
|
270
|
-
declared:
|
|
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,
|
|
271
570
|
appliedBy: "dialect-parity",
|
|
272
571
|
environment: "dev",
|
|
273
572
|
source: "auto-diff",
|
|
274
|
-
replan: (r) =>
|
|
275
|
-
declared:
|
|
573
|
+
replan: (r) => D.map(J(r), (r) => Y({
|
|
574
|
+
declared: t,
|
|
276
575
|
live: {
|
|
277
576
|
...r,
|
|
278
|
-
tables: r.tables.filter((e) => e.name ===
|
|
577
|
+
tables: r.tables.filter((e) => e.name === n)
|
|
279
578
|
},
|
|
280
|
-
dialect:
|
|
579
|
+
dialect: e.dialect.id
|
|
281
580
|
}))
|
|
282
581
|
});
|
|
283
|
-
})),
|
|
284
|
-
for (let
|
|
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);
|
|
285
584
|
})();
|
|
286
585
|
};
|
|
287
|
-
await
|
|
288
|
-
id:
|
|
289
|
-
name:
|
|
290
|
-
})], !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, {
|
|
291
590
|
id: "ev_1",
|
|
292
591
|
name: "one"
|
|
293
|
-
}), await
|
|
592
|
+
}), await a.store.insert(n, {
|
|
294
593
|
id: "ev_2",
|
|
295
594
|
name: "two"
|
|
296
|
-
}), await
|
|
297
|
-
id:
|
|
298
|
-
name:
|
|
299
|
-
qty:
|
|
300
|
-
})], !0),
|
|
301
|
-
id:
|
|
302
|
-
name:
|
|
303
|
-
qty:
|
|
304
|
-
}).index(["qty"])], !0), await
|
|
305
|
-
id:
|
|
306
|
-
name:
|
|
307
|
-
qty:
|
|
308
|
-
})], !0),
|
|
309
|
-
let
|
|
310
|
-
id:
|
|
311
|
-
name:
|
|
312
|
-
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()
|
|
313
612
|
});
|
|
314
|
-
await
|
|
613
|
+
await l([m], !0), await a.store.insert(n, {
|
|
315
614
|
id: "ev_3",
|
|
316
615
|
name: "three",
|
|
317
616
|
amount: "12.34"
|
|
318
617
|
});
|
|
319
|
-
let
|
|
320
|
-
id:
|
|
321
|
-
name:
|
|
322
|
-
amount:
|
|
618
|
+
let _ = C(n, {
|
|
619
|
+
id: h({ prefix: "ev" }),
|
|
620
|
+
name: w(),
|
|
621
|
+
amount: d(12, 4).nullable()
|
|
323
622
|
});
|
|
324
|
-
if (
|
|
325
|
-
let e = await
|
|
326
|
-
|
|
623
|
+
if (p) {
|
|
624
|
+
let e = await o(), t = await s([_]);
|
|
625
|
+
H(t.operations).toHaveLength(1), H(t.operations[0]).toMatchObject({
|
|
327
626
|
op: {
|
|
328
627
|
kind: "alter-column-type",
|
|
329
|
-
table:
|
|
628
|
+
table: n,
|
|
330
629
|
column: "amount",
|
|
331
630
|
from: "decimal",
|
|
332
631
|
to: "decimal"
|
|
333
632
|
},
|
|
334
633
|
classification: "multi-step",
|
|
335
|
-
blocked: { fix:
|
|
336
|
-
}),
|
|
337
|
-
let
|
|
338
|
-
|
|
339
|
-
} else await
|
|
340
|
-
let
|
|
341
|
-
|
|
342
|
-
p:
|
|
343
|
-
s:
|
|
344
|
-
}, `${
|
|
345
|
-
p:
|
|
346
|
-
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
|
|
347
646
|
});
|
|
348
|
-
let
|
|
349
|
-
id:
|
|
350
|
-
name:
|
|
351
|
-
amount:
|
|
352
|
-
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()
|
|
353
652
|
});
|
|
354
|
-
await
|
|
653
|
+
await l([S], !0), H(await c(), `${e.dialect.id}: the enum column is live`).toContain("status"), await a.store.insert(n, {
|
|
355
654
|
id: "ev_4",
|
|
356
655
|
name: "four",
|
|
357
656
|
status: "closed"
|
|
358
657
|
});
|
|
359
|
-
let
|
|
360
|
-
name:
|
|
658
|
+
let re = (e) => pe({ tables: [{
|
|
659
|
+
name: n,
|
|
361
660
|
columns: e.filter((e) => e.name === "status"),
|
|
362
661
|
indexes: []
|
|
363
|
-
}] }),
|
|
364
|
-
|
|
365
|
-
let
|
|
366
|
-
|
|
367
|
-
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", [
|
|
368
667
|
"open",
|
|
369
668
|
"closed",
|
|
370
669
|
"held"
|
|
371
|
-
]),
|
|
372
|
-
id:
|
|
373
|
-
name:
|
|
374
|
-
amount:
|
|
375
|
-
status:
|
|
670
|
+
]), M = C(n, {
|
|
671
|
+
id: h({ prefix: "ev" }),
|
|
672
|
+
name: w(),
|
|
673
|
+
amount: y(),
|
|
674
|
+
status: j.column().nullable()
|
|
376
675
|
});
|
|
377
|
-
await
|
|
676
|
+
await l([M], !0), await a.store.insert(n, {
|
|
378
677
|
id: "ev_5",
|
|
379
678
|
name: "five",
|
|
380
679
|
status: "held"
|
|
381
|
-
}), await
|
|
382
|
-
id:
|
|
383
|
-
name:
|
|
384
|
-
amount:
|
|
385
|
-
status:
|
|
386
|
-
parentId:
|
|
387
|
-
})], !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, {
|
|
388
687
|
id: "ev_6",
|
|
389
688
|
name: "six",
|
|
390
689
|
parentId: "ev_1"
|
|
391
|
-
}), await
|
|
392
|
-
id:
|
|
393
|
-
name:
|
|
394
|
-
amount:
|
|
395
|
-
status:
|
|
396
|
-
parentId:
|
|
397
|
-
})], !0),
|
|
398
|
-
id:
|
|
399
|
-
name:
|
|
400
|
-
amount:
|
|
401
|
-
status:
|
|
402
|
-
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()
|
|
403
702
|
})], !0);
|
|
404
|
-
for (let [e,
|
|
703
|
+
for (let [e, t] of Object.entries({
|
|
405
704
|
ev_1: "2024-03-04T05:06:07Z",
|
|
406
705
|
ev_2: "2025-01-01T00:00:00Z",
|
|
407
706
|
ev_3: "2026-09-15T05:48:00Z"
|
|
408
|
-
})) await
|
|
409
|
-
await
|
|
410
|
-
id:
|
|
411
|
-
name:
|
|
412
|
-
amount:
|
|
413
|
-
status:
|
|
414
|
-
createdAt:
|
|
415
|
-
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)
|
|
416
715
|
})], !0);
|
|
417
|
-
let
|
|
418
|
-
|
|
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({
|
|
419
718
|
ev_1: 2024,
|
|
420
719
|
ev_2: 2025,
|
|
421
720
|
ev_3: 2026,
|
|
422
721
|
ev_4: -1,
|
|
423
722
|
ev_5: -1,
|
|
424
723
|
ev_6: -1
|
|
425
|
-
}), await
|
|
426
|
-
id:
|
|
427
|
-
name:
|
|
428
|
-
amount:
|
|
429
|
-
status:
|
|
430
|
-
createdAt:
|
|
431
|
-
createdYear:
|
|
432
|
-
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)}`)
|
|
433
732
|
})], !0);
|
|
434
|
-
let
|
|
435
|
-
|
|
436
|
-
let
|
|
437
|
-
|
|
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([
|
|
438
737
|
"ev_1",
|
|
439
738
|
"ev_2",
|
|
440
739
|
"ev_3",
|
|
441
740
|
"ev_4",
|
|
442
741
|
"ev_5",
|
|
443
742
|
"ev_6"
|
|
444
|
-
]),
|
|
445
|
-
}),
|
|
446
|
-
let
|
|
447
|
-
let e = yield*
|
|
448
|
-
yield*
|
|
449
|
-
})).catch(() => {}), o = () =>
|
|
450
|
-
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);
|
|
451
750
|
return {
|
|
452
|
-
...
|
|
453
|
-
tables:
|
|
751
|
+
...t,
|
|
752
|
+
tables: t.tables.filter((e) => e.name === n)
|
|
454
753
|
};
|
|
455
|
-
})), s = (
|
|
456
|
-
let r = yield*
|
|
457
|
-
declared:
|
|
458
|
-
live: yield*
|
|
459
|
-
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
|
|
460
759
|
});
|
|
461
|
-
return yield*
|
|
462
|
-
declared:
|
|
760
|
+
return yield* K(r, i, {
|
|
761
|
+
declared: t,
|
|
463
762
|
appliedBy: "dialect-parity",
|
|
464
763
|
environment: "dev",
|
|
465
764
|
source: "auto-diff",
|
|
466
|
-
replan: (r) =>
|
|
467
|
-
declared:
|
|
765
|
+
replan: (r) => D.map(J(r), (r) => Y({
|
|
766
|
+
declared: t,
|
|
468
767
|
live: {
|
|
469
768
|
...r,
|
|
470
|
-
tables: r.tables.filter((e) => e.name ===
|
|
769
|
+
tables: r.tables.filter((e) => e.name === n)
|
|
471
770
|
},
|
|
472
|
-
dialect:
|
|
771
|
+
dialect: e.dialect.id
|
|
473
772
|
}))
|
|
474
773
|
});
|
|
475
774
|
}));
|
|
476
|
-
await
|
|
477
|
-
id:
|
|
478
|
-
name:
|
|
775
|
+
await i(), await a.migrate([t]), await s([C(n, {
|
|
776
|
+
id: h({ prefix: "at" }),
|
|
777
|
+
name: w()
|
|
479
778
|
})]);
|
|
480
779
|
for (let e of [
|
|
481
780
|
"one",
|
|
482
781
|
"two",
|
|
483
782
|
"three"
|
|
484
|
-
]) await
|
|
783
|
+
]) await a.store.insert(n, {
|
|
485
784
|
id: `at_${e}`,
|
|
486
785
|
name: e
|
|
487
786
|
});
|
|
488
|
-
let
|
|
489
|
-
id:
|
|
490
|
-
name:
|
|
491
|
-
first:
|
|
492
|
-
second:
|
|
787
|
+
let c = C(n, {
|
|
788
|
+
id: h({ prefix: "at" }),
|
|
789
|
+
name: w(),
|
|
790
|
+
first: g().backfill(() => 1),
|
|
791
|
+
second: g().backfill((e) => {
|
|
493
792
|
throw Error(`no value for ${String(e.name)}`);
|
|
494
793
|
})
|
|
495
794
|
});
|
|
496
|
-
await
|
|
497
|
-
let
|
|
498
|
-
|
|
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([
|
|
499
798
|
null,
|
|
500
799
|
null,
|
|
501
800
|
null
|
|
502
|
-
]) :
|
|
503
|
-
let
|
|
504
|
-
id:
|
|
505
|
-
name:
|
|
506
|
-
first:
|
|
507
|
-
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)
|
|
508
807
|
});
|
|
509
|
-
await s([
|
|
510
|
-
let
|
|
511
|
-
|
|
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([
|
|
512
811
|
[1, 3],
|
|
513
812
|
[1, 3],
|
|
514
813
|
[1, 5]
|
|
515
|
-
]),
|
|
516
|
-
declared: [
|
|
814
|
+
]), H(Y({
|
|
815
|
+
declared: [d],
|
|
517
816
|
live: await o(),
|
|
518
|
-
dialect:
|
|
519
|
-
}).operations).toEqual([]), await
|
|
520
|
-
}),
|
|
521
|
-
let
|
|
522
|
-
await
|
|
523
|
-
let e = yield*
|
|
524
|
-
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}`), () => []);
|
|
525
824
|
})).catch(() => {});
|
|
526
|
-
}, o = () =>
|
|
527
|
-
let e = yield*
|
|
825
|
+
}, o = () => r(D.gen(function* () {
|
|
826
|
+
let e = yield* G.SqlClient, t = yield* J(e);
|
|
528
827
|
return {
|
|
529
|
-
...
|
|
530
|
-
tables:
|
|
828
|
+
...t,
|
|
829
|
+
tables: t.tables.filter((e) => e.name === n)
|
|
531
830
|
};
|
|
532
|
-
})), s =
|
|
533
|
-
id:
|
|
534
|
-
name:
|
|
535
|
-
qty:
|
|
536
|
-
}).index(["qty"]),
|
|
831
|
+
})), s = C(n, {
|
|
832
|
+
id: h({ prefix: "ra" }),
|
|
833
|
+
name: w(),
|
|
834
|
+
qty: g().nullable()
|
|
835
|
+
}).index(["qty"]), c = async () => Y({
|
|
537
836
|
declared: [s],
|
|
538
837
|
live: await o(),
|
|
539
|
-
dialect:
|
|
838
|
+
dialect: e.dialect.id
|
|
540
839
|
});
|
|
541
|
-
await
|
|
542
|
-
let
|
|
543
|
-
|
|
544
|
-
for (let
|
|
545
|
-
let
|
|
546
|
-
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, {
|
|
547
846
|
declared: [s],
|
|
548
847
|
appliedBy: "dialect-parity",
|
|
549
848
|
environment: "dev",
|
|
550
849
|
source: "auto-diff",
|
|
551
|
-
replan: (
|
|
850
|
+
replan: (t) => D.map(J(t), (t) => Y({
|
|
552
851
|
declared: [s],
|
|
553
852
|
live: {
|
|
554
|
-
...
|
|
555
|
-
tables:
|
|
853
|
+
...t,
|
|
854
|
+
tables: t.tables.filter((e) => e.name === n)
|
|
556
855
|
},
|
|
557
|
-
dialect:
|
|
856
|
+
dialect: e.dialect.id
|
|
558
857
|
}))
|
|
559
858
|
});
|
|
560
859
|
}));
|
|
561
|
-
|
|
860
|
+
H((await c()).operations, `${e.dialect.id}: the re-applied plan must converge`).toEqual([]), await i();
|
|
562
861
|
});
|
|
563
|
-
let
|
|
564
|
-
let e = yield*
|
|
565
|
-
yield*
|
|
566
|
-
})),
|
|
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) => ({
|
|
567
866
|
id: `${t}_${String(n).padStart(6, "0")}`,
|
|
568
867
|
title: `row ${n}`,
|
|
569
868
|
count: n,
|
|
570
869
|
flag: !1
|
|
571
870
|
}));
|
|
572
|
-
|
|
573
|
-
let
|
|
574
|
-
|
|
575
|
-
let
|
|
576
|
-
await
|
|
577
|
-
let
|
|
578
|
-
|
|
579
|
-
let
|
|
580
|
-
|
|
581
|
-
}, 12e4),
|
|
582
|
-
let
|
|
583
|
-
await
|
|
584
|
-
let
|
|
585
|
-
t
|
|
586
|
-
...t
|
|
587
|
-
id:
|
|
588
|
-
}, await
|
|
589
|
-
let
|
|
590
|
-
|
|
591
|
-
}, 12e4),
|
|
592
|
-
await
|
|
593
|
-
}),
|
|
594
|
-
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", {
|
|
595
894
|
id: "a1",
|
|
596
895
|
title: "one",
|
|
597
896
|
count: 1
|
|
598
897
|
});
|
|
599
|
-
|
|
898
|
+
H(e).toMatchObject({
|
|
600
899
|
id: "a1",
|
|
601
900
|
title: "one",
|
|
602
901
|
count: 1
|
|
603
902
|
});
|
|
604
|
-
let t = await
|
|
605
|
-
|
|
606
|
-
}),
|
|
607
|
-
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", {
|
|
608
907
|
id: "a2",
|
|
609
908
|
title: "pre",
|
|
610
909
|
count: 1
|
|
611
910
|
});
|
|
612
|
-
let e = await
|
|
613
|
-
|
|
911
|
+
let e = await a.store.update("voltro_parity_a", "a2", { title: "post" });
|
|
912
|
+
H(e).toMatchObject({
|
|
614
913
|
id: "a2",
|
|
615
914
|
title: "post"
|
|
616
915
|
});
|
|
617
|
-
}),
|
|
618
|
-
await
|
|
916
|
+
}), U("delete removes the row + reports true", async () => {
|
|
917
|
+
await a.store.insert("voltro_parity_a", {
|
|
619
918
|
id: "a3",
|
|
620
919
|
title: "x",
|
|
621
920
|
count: 1
|
|
622
|
-
}),
|
|
623
|
-
let e = await
|
|
624
|
-
|
|
625
|
-
}),
|
|
626
|
-
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", {
|
|
627
926
|
userId: "u1",
|
|
628
927
|
orgId: "o1",
|
|
629
928
|
role: "admin"
|
|
630
|
-
}), await
|
|
929
|
+
}), await a.store.insert("voltro_parity_composite", {
|
|
631
930
|
userId: "u1",
|
|
632
931
|
orgId: "o2",
|
|
633
932
|
role: "viewer"
|
|
634
933
|
});
|
|
635
|
-
let e = await
|
|
934
|
+
let e = await a.store.update("voltro_parity_composite", {
|
|
636
935
|
userId: "u1",
|
|
637
936
|
orgId: "o2"
|
|
638
937
|
}, { role: "member" });
|
|
639
|
-
|
|
938
|
+
H(e).toMatchObject({
|
|
640
939
|
userId: "u1",
|
|
641
940
|
orgId: "o2",
|
|
642
941
|
role: "member"
|
|
643
942
|
});
|
|
644
|
-
let t = await
|
|
645
|
-
|
|
646
|
-
}),
|
|
647
|
-
|
|
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", {
|
|
648
947
|
userId: "nobody",
|
|
649
948
|
orgId: "nowhere"
|
|
650
|
-
}, { role: "x" })).toBeNull(),
|
|
949
|
+
}, { role: "x" })).toBeNull(), H(await a.store.delete("voltro_parity_composite", {
|
|
651
950
|
userId: "nobody",
|
|
652
951
|
orgId: "nowhere"
|
|
653
952
|
})).toBe(!1);
|
|
654
|
-
}),
|
|
655
|
-
await
|
|
953
|
+
}), U("a composite key deletes exactly the row it names", async () => {
|
|
954
|
+
await a.store.insert("voltro_parity_composite", {
|
|
656
955
|
userId: "u2",
|
|
657
956
|
orgId: "o1",
|
|
658
957
|
role: "admin"
|
|
659
|
-
}), await
|
|
958
|
+
}), await a.store.insert("voltro_parity_composite", {
|
|
660
959
|
userId: "u2",
|
|
661
960
|
orgId: "o2",
|
|
662
961
|
role: "viewer"
|
|
663
|
-
}),
|
|
962
|
+
}), H(await a.store.delete("voltro_parity_composite", {
|
|
664
963
|
userId: "u2",
|
|
665
964
|
orgId: "o1"
|
|
666
965
|
})).toBe(!0);
|
|
667
|
-
let e = (await
|
|
668
|
-
|
|
669
|
-
}),
|
|
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 () => {
|
|
670
969
|
let e = [{
|
|
671
970
|
table: "voltro_parity_c",
|
|
672
971
|
op: "update",
|
|
@@ -684,7 +983,7 @@ var q = h("voltro_parity_composite", {
|
|
|
684
983
|
flag: !1
|
|
685
984
|
}
|
|
686
985
|
}];
|
|
687
|
-
await
|
|
986
|
+
await a.store.insert("_voltro_undo_log", {
|
|
688
987
|
id: "undo_p1",
|
|
689
988
|
tag: "parityC.update",
|
|
690
989
|
label: null,
|
|
@@ -696,16 +995,16 @@ var q = h("voltro_parity_composite", {
|
|
|
696
995
|
undone: !1,
|
|
697
996
|
createdAt: /* @__PURE__ */ new Date()
|
|
698
997
|
});
|
|
699
|
-
let t = await
|
|
700
|
-
|
|
701
|
-
}),
|
|
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 () => {
|
|
702
1001
|
let e = {
|
|
703
1002
|
invocationId: "inv1",
|
|
704
1003
|
subject: null,
|
|
705
1004
|
changes: [{
|
|
706
1005
|
table: "voltro_parity_c",
|
|
707
1006
|
op: "insert",
|
|
708
|
-
next: await
|
|
1007
|
+
next: await a.store.insert("voltro_parity_c", {
|
|
709
1008
|
id: "c2",
|
|
710
1009
|
title: "new",
|
|
711
1010
|
count: 1,
|
|
@@ -713,24 +1012,24 @@ var q = h("voltro_parity_composite", {
|
|
|
713
1012
|
})
|
|
714
1013
|
}]
|
|
715
1014
|
};
|
|
716
|
-
await
|
|
717
|
-
let t = await
|
|
718
|
-
|
|
719
|
-
}),
|
|
720
|
-
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", {
|
|
721
1020
|
id: "c3",
|
|
722
1021
|
title: "t",
|
|
723
1022
|
count: 1,
|
|
724
1023
|
flag: !0
|
|
725
1024
|
});
|
|
726
|
-
let e = (await
|
|
727
|
-
...
|
|
728
|
-
predicate:
|
|
729
|
-
}))[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", {
|
|
730
1029
|
count: 2,
|
|
731
1030
|
flag: !1
|
|
732
1031
|
});
|
|
733
|
-
|
|
1032
|
+
H(t.flag).not.toEqual(e.flag);
|
|
734
1033
|
let n = {
|
|
735
1034
|
invocationId: "inv2",
|
|
736
1035
|
subject: null,
|
|
@@ -742,20 +1041,20 @@ var q = h("voltro_parity_composite", {
|
|
|
742
1041
|
next: t
|
|
743
1042
|
}]
|
|
744
1043
|
};
|
|
745
|
-
await
|
|
746
|
-
let
|
|
747
|
-
...
|
|
748
|
-
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")
|
|
749
1048
|
}))[0];
|
|
750
|
-
|
|
751
|
-
}),
|
|
752
|
-
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", {
|
|
753
1052
|
id: "c4",
|
|
754
1053
|
title: "gone",
|
|
755
1054
|
count: 9,
|
|
756
1055
|
flag: !0
|
|
757
1056
|
});
|
|
758
|
-
await
|
|
1057
|
+
await a.store.delete("voltro_parity_c", "c4");
|
|
759
1058
|
let t = {
|
|
760
1059
|
invocationId: "inv3",
|
|
761
1060
|
subject: null,
|
|
@@ -766,46 +1065,46 @@ var q = h("voltro_parity_composite", {
|
|
|
766
1065
|
prev: e
|
|
767
1066
|
}]
|
|
768
1067
|
};
|
|
769
|
-
await
|
|
770
|
-
let n = (await
|
|
771
|
-
...
|
|
772
|
-
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")
|
|
773
1072
|
}))[0];
|
|
774
|
-
|
|
775
|
-
}),
|
|
776
|
-
let e = [], t =
|
|
777
|
-
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", {
|
|
778
1077
|
id: "a4",
|
|
779
1078
|
title: "a",
|
|
780
1079
|
count: 1
|
|
781
|
-
}), await
|
|
1080
|
+
}), await a.store.update("voltro_parity_a", "a4", { title: "b" }), await a.store.delete("voltro_parity_a", "a4"), t(), H(e).toEqual([
|
|
782
1081
|
"insert",
|
|
783
1082
|
"update",
|
|
784
1083
|
"delete"
|
|
785
1084
|
]);
|
|
786
|
-
}),
|
|
1085
|
+
}), U("binding more than 10 change listeners emits no MaxListeners warning", async () => {
|
|
787
1086
|
let e = [], t = (t) => {
|
|
788
1087
|
e.push(t.name);
|
|
789
1088
|
};
|
|
790
1089
|
process.on("warning", t);
|
|
791
|
-
let n = Array.from({ length: 32 }, () =>
|
|
1090
|
+
let n = Array.from({ length: 32 }, () => a.store.onChange(() => {}));
|
|
792
1091
|
await new Promise((e) => setImmediate(e));
|
|
793
1092
|
for (let e of n) e();
|
|
794
|
-
process.off("warning", t),
|
|
795
|
-
}),
|
|
796
|
-
let e = [], t =
|
|
797
|
-
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) => {
|
|
798
1097
|
throw await e.insert("voltro_parity_a", {
|
|
799
1098
|
id: "rollback",
|
|
800
1099
|
title: "r",
|
|
801
1100
|
count: 0
|
|
802
1101
|
}), Error("intentional rollback");
|
|
803
|
-
})).rejects.toThrow("intentional rollback"), t(),
|
|
804
|
-
let n = await
|
|
805
|
-
|
|
806
|
-
}),
|
|
807
|
-
let e = [], t =
|
|
808
|
-
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) => {
|
|
809
1108
|
await e.insert("voltro_parity_a", {
|
|
810
1109
|
id: "commit1",
|
|
811
1110
|
title: "a",
|
|
@@ -815,13 +1114,13 @@ var q = h("voltro_parity_composite", {
|
|
|
815
1114
|
title: "b",
|
|
816
1115
|
count: 2
|
|
817
1116
|
});
|
|
818
|
-
}), t(),
|
|
819
|
-
}),
|
|
820
|
-
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", {
|
|
821
1120
|
id: "u1",
|
|
822
1121
|
title: "first",
|
|
823
1122
|
count: 1
|
|
824
|
-
}, { conflictColumns: ["id"] }), await
|
|
1123
|
+
}, { conflictColumns: ["id"] }), await a.store.upsert("voltro_parity_a", {
|
|
825
1124
|
id: "u1",
|
|
826
1125
|
title: "second",
|
|
827
1126
|
count: 2
|
|
@@ -829,13 +1128,13 @@ var q = h("voltro_parity_composite", {
|
|
|
829
1128
|
conflictColumns: ["id"],
|
|
830
1129
|
update: ["title", "count"]
|
|
831
1130
|
});
|
|
832
|
-
let e = await
|
|
833
|
-
|
|
1131
|
+
let e = await a.store.query(X("voltro_parity_a"));
|
|
1132
|
+
H(e.length).toBe(1), H(e[0]).toMatchObject({
|
|
834
1133
|
id: "u1",
|
|
835
1134
|
title: "second",
|
|
836
1135
|
count: 2
|
|
837
1136
|
});
|
|
838
|
-
}),
|
|
1137
|
+
}), U.each([
|
|
839
1138
|
"done",
|
|
840
1139
|
"",
|
|
841
1140
|
"null",
|
|
@@ -845,47 +1144,47 @@ var q = h("voltro_parity_composite", {
|
|
|
845
1144
|
"{\"a\":1}",
|
|
846
1145
|
"\"quoted\""
|
|
847
1146
|
])("JSON scalar string %j survives insert and update", async (e) => {
|
|
848
|
-
let t =
|
|
1147
|
+
let t = Z.tableName, n = await a.store.insert(t, {
|
|
849
1148
|
id: "json-string",
|
|
850
1149
|
title: "before",
|
|
851
1150
|
metadata: e,
|
|
852
1151
|
labels: []
|
|
853
1152
|
});
|
|
854
|
-
|
|
1153
|
+
H(n.metadata).toBe(e), H((await a.store.query(X(t)))[0]?.metadata).toBe(e), await a.store.transactional(async (n) => {
|
|
855
1154
|
let r = await n.update(t, "json-string", { metadata: `${e}\nupdated` });
|
|
856
|
-
|
|
857
|
-
}),
|
|
858
|
-
}),
|
|
859
|
-
let
|
|
860
|
-
let e = yield*
|
|
861
|
-
return yield*
|
|
862
|
-
}))).tables.find((e) => e.name ===
|
|
863
|
-
|
|
864
|
-
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),
|
|
865
1164
|
validation: "value",
|
|
866
1165
|
enabled: !0,
|
|
867
1166
|
trusted: !0
|
|
868
1167
|
}]));
|
|
869
|
-
}),
|
|
870
|
-
let e = (e) =>
|
|
871
|
-
yield* (yield*
|
|
872
|
-
})), t = () =>
|
|
873
|
-
let e = yield*
|
|
874
|
-
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;
|
|
875
1174
|
}));
|
|
876
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))");
|
|
877
1176
|
try {
|
|
878
|
-
|
|
1177
|
+
H(await t()).toEqual([{
|
|
879
1178
|
name: "json_container_probe",
|
|
880
1179
|
validation: "container",
|
|
881
1180
|
enabled: !0,
|
|
882
1181
|
trusted: !0
|
|
883
|
-
}]), 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([{
|
|
884
1183
|
name: "json_container_probe",
|
|
885
1184
|
validation: "container",
|
|
886
1185
|
enabled: !1,
|
|
887
1186
|
trusted: !1
|
|
888
|
-
}]), 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([{
|
|
889
1188
|
name: "json_container_probe",
|
|
890
1189
|
validation: "container",
|
|
891
1190
|
enabled: !0,
|
|
@@ -894,7 +1193,7 @@ var q = h("voltro_parity_composite", {
|
|
|
894
1193
|
} finally {
|
|
895
1194
|
await e("DROP TABLE voltro_parity_json_checks");
|
|
896
1195
|
}
|
|
897
|
-
}),
|
|
1196
|
+
}), e.dialect.id === "mssql" && U.each([
|
|
898
1197
|
{
|
|
899
1198
|
invalid: !1,
|
|
900
1199
|
rename: !1
|
|
@@ -911,121 +1210,121 @@ var q = h("voltro_parity_composite", {
|
|
|
911
1210
|
invalid: !0,
|
|
912
1211
|
rename: !0
|
|
913
1212
|
}
|
|
914
|
-
])("JSON check migration preserves data and is atomic (invalid=$invalid, rename=$rename)", async ({ invalid:
|
|
915
|
-
let
|
|
916
|
-
id:
|
|
917
|
-
[
|
|
918
|
-
})], s = (e) =>
|
|
919
|
-
return yield* (yield*
|
|
920
|
-
})),
|
|
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({
|
|
921
1220
|
declared: o,
|
|
922
1221
|
dialect: "mssql",
|
|
923
|
-
live: { tables: e.tables.filter((e) => e.name ===
|
|
924
|
-
}))),
|
|
925
|
-
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]"')))`);
|
|
926
1225
|
try {
|
|
927
|
-
let
|
|
928
|
-
|
|
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({
|
|
929
1228
|
disabled: !1,
|
|
930
1229
|
notForReplication: !0
|
|
931
|
-
}),
|
|
932
|
-
let
|
|
933
|
-
|
|
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 ? [{
|
|
934
1233
|
kind: "rename-column",
|
|
935
|
-
table:
|
|
1234
|
+
table: r,
|
|
936
1235
|
from: "metadata",
|
|
937
|
-
to:
|
|
1236
|
+
to: i
|
|
938
1237
|
}] : [], {
|
|
939
1238
|
kind: "reconcile-json-check",
|
|
940
|
-
table:
|
|
941
|
-
column:
|
|
1239
|
+
table: r,
|
|
1240
|
+
column: i,
|
|
942
1241
|
validation: "value",
|
|
943
|
-
previousValidation:
|
|
1242
|
+
previousValidation: e ? "unverified" : "container",
|
|
944
1243
|
dropConstraints: ["upgrade_json_old"]
|
|
945
1244
|
}]);
|
|
946
|
-
let f =
|
|
947
|
-
let e = yield*
|
|
948
|
-
return yield*
|
|
1245
|
+
let f = a.run(D.gen(function* () {
|
|
1246
|
+
let e = yield* G.SqlClient;
|
|
1247
|
+
return yield* K(e, d, {
|
|
949
1248
|
declared: o,
|
|
950
1249
|
appliedBy: "json-parity",
|
|
951
1250
|
environment: "dev",
|
|
952
1251
|
source: "auto-diff",
|
|
953
|
-
replan:
|
|
1252
|
+
replan: c
|
|
954
1253
|
});
|
|
955
1254
|
}));
|
|
956
|
-
|
|
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);
|
|
957
1256
|
} finally {
|
|
958
|
-
await s(`DROP TABLE ${
|
|
1257
|
+
await s(`DROP TABLE ${r}`);
|
|
959
1258
|
}
|
|
960
|
-
}),
|
|
1259
|
+
}), e.dialect.id === "mssql" && U.each([
|
|
961
1260
|
"trusted",
|
|
962
1261
|
"untrusted",
|
|
963
1262
|
"disabled"
|
|
964
|
-
])("column rename preserves %s application CHECK state", async (
|
|
965
|
-
let n = "voltro_parity_rename_checks",
|
|
966
|
-
id:
|
|
967
|
-
renamed:
|
|
968
|
-
})],
|
|
969
|
-
return yield* (yield*
|
|
970
|
-
})), o = (e) =>
|
|
971
|
-
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,
|
|
972
1271
|
dialect: "mssql",
|
|
973
1272
|
live: { tables: e.tables.filter((e) => e.name === n) }
|
|
974
1273
|
})));
|
|
975
|
-
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))`);
|
|
976
1275
|
try {
|
|
977
|
-
|
|
978
|
-
let
|
|
979
|
-
|
|
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([{
|
|
980
1279
|
name: "rename_state_check",
|
|
981
|
-
disabled:
|
|
982
|
-
untrusted:
|
|
983
|
-
}]), await
|
|
984
|
-
let e = yield*
|
|
985
|
-
|
|
986
|
-
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,
|
|
987
1286
|
appliedBy: "rename-parity",
|
|
988
1287
|
environment: "dev",
|
|
989
1288
|
source: "auto-diff",
|
|
990
1289
|
replan: o
|
|
991
|
-
}),
|
|
992
|
-
})),
|
|
993
|
-
let c =
|
|
994
|
-
|
|
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();
|
|
995
1294
|
} finally {
|
|
996
|
-
await
|
|
1295
|
+
await i(`DROP TABLE ${n}`);
|
|
997
1296
|
}
|
|
998
|
-
}),
|
|
999
|
-
let
|
|
1000
|
-
id:
|
|
1001
|
-
metadata:
|
|
1002
|
-
labels:
|
|
1003
|
-
})], o = (e) =>
|
|
1004
|
-
return yield* (yield*
|
|
1005
|
-
})), s = (e) =>
|
|
1006
|
-
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,
|
|
1007
1306
|
dialect: "mssql",
|
|
1008
|
-
live: { tables: e.tables.filter((e) => e.name ===
|
|
1307
|
+
live: { tables: e.tables.filter((e) => e.name === r) }
|
|
1009
1308
|
})));
|
|
1010
|
-
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')`));
|
|
1011
1310
|
try {
|
|
1012
|
-
await
|
|
1013
|
-
let
|
|
1014
|
-
|
|
1015
|
-
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,
|
|
1016
1315
|
appliedBy: "json-ddl",
|
|
1017
1316
|
environment: "dev",
|
|
1018
1317
|
source: "auto-diff",
|
|
1019
1318
|
replan: s
|
|
1020
|
-
}),
|
|
1021
|
-
})), 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([{
|
|
1022
1321
|
metadata: null,
|
|
1023
1322
|
labels: null
|
|
1024
1323
|
}]);
|
|
1025
1324
|
} finally {
|
|
1026
|
-
await o(`DROP TABLE IF EXISTS ${
|
|
1325
|
+
await o(`DROP TABLE IF EXISTS ${r}`);
|
|
1027
1326
|
}
|
|
1028
|
-
}),
|
|
1327
|
+
}), U.each([
|
|
1029
1328
|
"",
|
|
1030
1329
|
"1,2",
|
|
1031
1330
|
"1,\"other\":2",
|
|
@@ -1033,12 +1332,12 @@ var q = h("voltro_parity_composite", {
|
|
|
1033
1332
|
"{bad}",
|
|
1034
1333
|
"undefined"
|
|
1035
1334
|
])("JSON check rejects raw invalid value %j", async (e) => {
|
|
1036
|
-
await
|
|
1037
|
-
let t = yield*
|
|
1038
|
-
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})`;
|
|
1039
1338
|
}))).rejects.toThrow();
|
|
1040
|
-
}),
|
|
1041
|
-
|
|
1339
|
+
}), V.each(["direct", "transactional"])("upsert recorder atomicity (%s)", (t) => {
|
|
1340
|
+
U.each([
|
|
1042
1341
|
{
|
|
1043
1342
|
kind: "native",
|
|
1044
1343
|
existing: !1
|
|
@@ -1055,21 +1354,21 @@ var q = h("voltro_parity_composite", {
|
|
|
1055
1354
|
kind: "functional",
|
|
1056
1355
|
existing: !0
|
|
1057
1356
|
}
|
|
1058
|
-
])("$kind upsert (existing=$existing) rolls back its write and recorder append", async ({ kind:
|
|
1059
|
-
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 = {
|
|
1060
1359
|
id: "recorded",
|
|
1061
1360
|
title: "before",
|
|
1062
1361
|
count: 0
|
|
1063
1362
|
};
|
|
1064
|
-
|
|
1065
|
-
let c = `parity-upsert-${
|
|
1363
|
+
r && await i.insert(o, s);
|
|
1364
|
+
let c = `parity-upsert-${e.dialect.id}`, l = [], u = i.onChange((e) => {
|
|
1066
1365
|
e.table === o && l.push(e.op);
|
|
1067
1366
|
});
|
|
1068
|
-
|
|
1367
|
+
x(c, {
|
|
1069
1368
|
tables: [o],
|
|
1070
1369
|
ownTables: ["voltro_parity_b"],
|
|
1071
1370
|
recorder: async (e, t) => {
|
|
1072
|
-
throw
|
|
1371
|
+
throw H(t.traceId).toBe("recorder-trace"), H(t.subjectId).toBe("recorder-subject"), await e.append("voltro_parity_b", {
|
|
1073
1372
|
id: "recorder-append",
|
|
1074
1373
|
title: "trail"
|
|
1075
1374
|
}), Error("recorder refused");
|
|
@@ -1081,39 +1380,39 @@ var q = h("voltro_parity_composite", {
|
|
|
1081
1380
|
count: 1
|
|
1082
1381
|
}, {
|
|
1083
1382
|
conflictColumns: ["id"],
|
|
1084
|
-
update:
|
|
1383
|
+
update: n === "native" ? ["title"] : () => ({ title: "computed" })
|
|
1085
1384
|
});
|
|
1086
1385
|
try {
|
|
1087
|
-
await
|
|
1386
|
+
await H(ne({
|
|
1088
1387
|
traceId: "recorder-trace",
|
|
1089
1388
|
subjectId: "recorder-subject"
|
|
1090
|
-
}, () =>
|
|
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([]);
|
|
1091
1390
|
} finally {
|
|
1092
|
-
|
|
1391
|
+
re(c), u();
|
|
1093
1392
|
}
|
|
1094
1393
|
});
|
|
1095
|
-
}),
|
|
1096
|
-
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) => {
|
|
1097
1396
|
e.table === t && n.push((e.new ?? e.old)?.metadata);
|
|
1098
|
-
}),
|
|
1397
|
+
}), i = (e) => ({
|
|
1099
1398
|
id: e,
|
|
1100
1399
|
title: "before",
|
|
1101
1400
|
metadata: "null",
|
|
1102
1401
|
labels: ["x"]
|
|
1103
1402
|
});
|
|
1104
1403
|
try {
|
|
1105
|
-
|
|
1404
|
+
H((await e.insertMany(t, [i("one"), i("two")])).map((e) => e.metadata)).toEqual(["null", "null"]), H((await e.upsert(t, i("one"), {
|
|
1106
1405
|
conflictColumns: ["id"],
|
|
1107
1406
|
update: ["metadata"]
|
|
1108
|
-
})).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"), {
|
|
1109
1408
|
conflictColumns: ["id"],
|
|
1110
|
-
update: (e) => (
|
|
1111
|
-
}), 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);
|
|
1112
1411
|
} finally {
|
|
1113
|
-
|
|
1412
|
+
r();
|
|
1114
1413
|
}
|
|
1115
|
-
}),
|
|
1116
|
-
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 = [
|
|
1117
1416
|
{
|
|
1118
1417
|
name: "shallow object merge retains unrelated keys and nulls",
|
|
1119
1418
|
before: {
|
|
@@ -1331,7 +1630,7 @@ var q = h("voltro_parity_composite", {
|
|
|
1331
1630
|
}
|
|
1332
1631
|
}))
|
|
1333
1632
|
];
|
|
1334
|
-
|
|
1633
|
+
U.each(n)("$name with companion fields in the same write", async ({ before: e, path: n, value: r, after: i }) => {
|
|
1335
1634
|
await t(async (t) => {
|
|
1336
1635
|
await t.insert("voltro_parity_values", {
|
|
1337
1636
|
id: "patch",
|
|
@@ -1343,19 +1642,19 @@ var q = h("voltro_parity_composite", {
|
|
|
1343
1642
|
title: "after",
|
|
1344
1643
|
labels: []
|
|
1345
1644
|
});
|
|
1346
|
-
|
|
1645
|
+
H(a).toMatchObject({
|
|
1347
1646
|
id: "patch",
|
|
1348
1647
|
title: "after",
|
|
1349
1648
|
metadata: i,
|
|
1350
1649
|
labels: []
|
|
1351
|
-
}),
|
|
1650
|
+
}), H(await t.query(X("voltro_parity_values"))).toEqual([{
|
|
1352
1651
|
id: "patch",
|
|
1353
1652
|
title: "after",
|
|
1354
1653
|
metadata: i,
|
|
1355
1654
|
labels: []
|
|
1356
1655
|
}]);
|
|
1357
1656
|
});
|
|
1358
|
-
}),
|
|
1657
|
+
}), U("refuses companion primary-key or JSON-column replacement before writing", async () => {
|
|
1359
1658
|
await t(async (e) => {
|
|
1360
1659
|
let t = {
|
|
1361
1660
|
id: "refuse",
|
|
@@ -1364,16 +1663,16 @@ var q = h("voltro_parity_composite", {
|
|
|
1364
1663
|
labels: []
|
|
1365
1664
|
};
|
|
1366
1665
|
await e.insert("voltro_parity_values", t);
|
|
1367
|
-
for (let t of [{ id: "renamed" }, { metadata: {} }]) await
|
|
1368
|
-
|
|
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();
|
|
1369
1668
|
});
|
|
1370
|
-
}),
|
|
1669
|
+
}), U("does not confuse JSON columns named key/value with iterator columns", async () => {
|
|
1371
1670
|
await t(async (e) => {
|
|
1372
|
-
await e.insert(
|
|
1671
|
+
await e.insert(Q.tableName, {
|
|
1373
1672
|
id: "names",
|
|
1374
1673
|
key: { "a\"b": { keep: !0 } },
|
|
1375
1674
|
value: { "c\\d": { keep: !1 } }
|
|
1376
|
-
}), 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([{
|
|
1377
1676
|
id: "names",
|
|
1378
1677
|
key: { "a\"b": {
|
|
1379
1678
|
keep: !0,
|
|
@@ -1385,21 +1684,21 @@ var q = h("voltro_parity_composite", {
|
|
|
1385
1684
|
} }
|
|
1386
1685
|
}]);
|
|
1387
1686
|
});
|
|
1388
|
-
}),
|
|
1389
|
-
let t =
|
|
1390
|
-
tableName:
|
|
1391
|
-
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
|
|
1392
1691
|
}, {
|
|
1393
|
-
tableName:
|
|
1394
|
-
fields:
|
|
1395
|
-
}]), n =
|
|
1692
|
+
tableName: Z.tableName,
|
|
1693
|
+
fields: Z.fields
|
|
1694
|
+
}]), n = B(a.store, {
|
|
1396
1695
|
subject: {
|
|
1397
1696
|
type: "anonymous",
|
|
1398
1697
|
id: null,
|
|
1399
1698
|
tenantId: null
|
|
1400
1699
|
},
|
|
1401
1700
|
schemaRegistry: t
|
|
1402
|
-
}),
|
|
1701
|
+
}), r = async (e) => {
|
|
1403
1702
|
let t = /* @__PURE__ */ new Date("2026-01-01T00:00:00.000Z"), n = Date.now();
|
|
1404
1703
|
for (let n of [
|
|
1405
1704
|
"keyed",
|
|
@@ -1411,29 +1710,29 @@ var q = h("voltro_parity_composite", {
|
|
|
1411
1710
|
title: "before",
|
|
1412
1711
|
at: t,
|
|
1413
1712
|
n: 9
|
|
1414
|
-
}), 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", {
|
|
1415
1714
|
id: n,
|
|
1416
1715
|
title: "edited"
|
|
1417
1716
|
}, {
|
|
1418
1717
|
conflictColumns: ["id"],
|
|
1419
1718
|
update: n === "callback" ? () => ({ title: "edited" }) : ["title"]
|
|
1420
1719
|
});
|
|
1421
|
-
let r = await e.query(
|
|
1422
|
-
|
|
1720
|
+
let r = await e.query(X("voltro_parity_d"));
|
|
1721
|
+
H(r).toHaveLength(4);
|
|
1423
1722
|
for (let e of r) {
|
|
1424
|
-
|
|
1723
|
+
H(e).toMatchObject({
|
|
1425
1724
|
title: "edited",
|
|
1426
1725
|
n: 9
|
|
1427
1726
|
});
|
|
1428
1727
|
let t = e.at instanceof Date ? e.at.getTime() : new Date(String(e.at)).getTime();
|
|
1429
|
-
|
|
1728
|
+
H(t).toBeGreaterThanOrEqual(n), H(t).toBeLessThanOrEqual(Date.now());
|
|
1430
1729
|
}
|
|
1431
1730
|
await e.insert("voltro_parity_values", {
|
|
1432
1731
|
id: "automatic",
|
|
1433
1732
|
title: "original",
|
|
1434
1733
|
metadata: { keep: !0 },
|
|
1435
1734
|
labels: []
|
|
1436
|
-
}), 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([{
|
|
1437
1736
|
id: "automatic",
|
|
1438
1737
|
title: "automatic",
|
|
1439
1738
|
metadata: {
|
|
@@ -1441,7 +1740,7 @@ var q = h("voltro_parity_composite", {
|
|
|
1441
1740
|
child: { value: !1 }
|
|
1442
1741
|
},
|
|
1443
1742
|
labels: []
|
|
1444
|
-
}]),
|
|
1743
|
+
}]), H(await e.upsert("voltro_parity_d", {
|
|
1445
1744
|
id: "new",
|
|
1446
1745
|
title: "insert"
|
|
1447
1746
|
}, {
|
|
@@ -1449,35 +1748,35 @@ var q = h("voltro_parity_composite", {
|
|
|
1449
1748
|
update: []
|
|
1450
1749
|
})).toMatchObject({ at: null });
|
|
1451
1750
|
};
|
|
1452
|
-
e === "transactional" ? await n.transactional(
|
|
1751
|
+
e === "transactional" ? await n.transactional(r) : await r(n);
|
|
1453
1752
|
});
|
|
1454
|
-
}),
|
|
1753
|
+
}), U("JSON patch companion failure rolls back the JSON write too", async () => {
|
|
1455
1754
|
let e = {
|
|
1456
1755
|
id: "atomic-json",
|
|
1457
1756
|
title: "keep",
|
|
1458
1757
|
metadata: { keep: !0 },
|
|
1459
1758
|
labels: []
|
|
1460
1759
|
};
|
|
1461
|
-
await
|
|
1462
|
-
}),
|
|
1463
|
-
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", {
|
|
1464
1763
|
id: "concurrent-json",
|
|
1465
1764
|
title: "before",
|
|
1466
1765
|
metadata: {},
|
|
1467
1766
|
labels: []
|
|
1468
|
-
}), 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([{
|
|
1469
1768
|
id: "concurrent-json",
|
|
1470
1769
|
title: "after",
|
|
1471
1770
|
metadata: Object.fromEntries(Array.from({ length: 8 }, (e, t) => [`key${t}`, t])),
|
|
1472
1771
|
labels: []
|
|
1473
1772
|
}]);
|
|
1474
|
-
}),
|
|
1475
|
-
await
|
|
1773
|
+
}), U("upsert on a NON-primary-key unique constraint updates that row", async () => {
|
|
1774
|
+
await a.store.upsert("voltro_parity_upsert", {
|
|
1476
1775
|
id: "up_first",
|
|
1477
1776
|
teamId: "t-1",
|
|
1478
1777
|
year: 2026,
|
|
1479
1778
|
hours: 10
|
|
1480
|
-
}, { conflictColumns: ["teamId", "year"] }), await
|
|
1779
|
+
}, { conflictColumns: ["teamId", "year"] }), await a.store.upsert("voltro_parity_upsert", {
|
|
1481
1780
|
id: "up_second",
|
|
1482
1781
|
teamId: "t-1",
|
|
1483
1782
|
year: 2026,
|
|
@@ -1486,16 +1785,16 @@ var q = h("voltro_parity_composite", {
|
|
|
1486
1785
|
conflictColumns: ["teamId", "year"],
|
|
1487
1786
|
update: ["hours"]
|
|
1488
1787
|
});
|
|
1489
|
-
let e = await
|
|
1490
|
-
|
|
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({
|
|
1491
1790
|
id: "up_first",
|
|
1492
1791
|
teamId: "t-1",
|
|
1493
1792
|
year: 2026,
|
|
1494
1793
|
hours: 111
|
|
1495
1794
|
});
|
|
1496
|
-
}),
|
|
1497
|
-
let t = (t) => e === "transactional" ?
|
|
1498
|
-
|
|
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 () => {
|
|
1499
1798
|
let e = /* @__PURE__ */ new Date("2026-09-05T10:20:30.000Z"), n = {
|
|
1500
1799
|
conflictColumns: ["id"],
|
|
1501
1800
|
update: [],
|
|
@@ -1509,7 +1808,7 @@ var q = h("voltro_parity_composite", {
|
|
|
1509
1808
|
id: "constant",
|
|
1510
1809
|
title: "Lobby"
|
|
1511
1810
|
}, n);
|
|
1512
|
-
|
|
1811
|
+
H(r).toMatchObject({
|
|
1513
1812
|
id: "constant",
|
|
1514
1813
|
title: "Lobby",
|
|
1515
1814
|
at: null,
|
|
@@ -1519,19 +1818,19 @@ var q = h("voltro_parity_composite", {
|
|
|
1519
1818
|
id: "constant",
|
|
1520
1819
|
title: "Do not overwrite"
|
|
1521
1820
|
}, n);
|
|
1522
|
-
|
|
1821
|
+
H(i).toMatchObject({
|
|
1523
1822
|
id: "constant",
|
|
1524
1823
|
title: "Lobby",
|
|
1525
1824
|
n: 42
|
|
1526
|
-
}),
|
|
1825
|
+
}), H(new Date(String(i.at)).toISOString()).toBe(e.toISOString());
|
|
1527
1826
|
});
|
|
1528
|
-
let [
|
|
1529
|
-
|
|
1827
|
+
let [r] = await a.store.query(X("voltro_parity_d"));
|
|
1828
|
+
H(r).toMatchObject({
|
|
1530
1829
|
id: "constant",
|
|
1531
1830
|
title: "Lobby",
|
|
1532
1831
|
n: 42
|
|
1533
|
-
}),
|
|
1534
|
-
}),
|
|
1832
|
+
}), H(new Date(String(r.at)).toISOString()).toBe(e.toISOString());
|
|
1833
|
+
}), U.each([
|
|
1535
1834
|
"omitted",
|
|
1536
1835
|
"empty",
|
|
1537
1836
|
"columns",
|
|
@@ -1554,19 +1853,19 @@ var q = h("voltro_parity_composite", {
|
|
|
1554
1853
|
...n === void 0 ? {} : { update: n },
|
|
1555
1854
|
updateValues: { hours: 42 }
|
|
1556
1855
|
});
|
|
1557
|
-
|
|
1856
|
+
H(r).toMatchObject({
|
|
1558
1857
|
id: "original",
|
|
1559
1858
|
teamId: "t1",
|
|
1560
1859
|
year: 2026,
|
|
1561
1860
|
hours: 42
|
|
1562
1861
|
});
|
|
1563
|
-
}),
|
|
1862
|
+
}), H(await a.store.query(X("voltro_parity_upsert"))).toEqual([H.objectContaining({
|
|
1564
1863
|
id: "original",
|
|
1565
1864
|
teamId: "t1",
|
|
1566
1865
|
year: 2026,
|
|
1567
1866
|
hours: 42
|
|
1568
1867
|
})]);
|
|
1569
|
-
}),
|
|
1868
|
+
}), U("binds explicit NULL to timestamp/int columns and omits undefined assignments", async () => {
|
|
1570
1869
|
await t(async (e) => {
|
|
1571
1870
|
await e.insert("voltro_parity_d", {
|
|
1572
1871
|
id: "nulls",
|
|
@@ -1588,12 +1887,12 @@ var q = h("voltro_parity_composite", {
|
|
|
1588
1887
|
n: void 0
|
|
1589
1888
|
}
|
|
1590
1889
|
});
|
|
1591
|
-
|
|
1890
|
+
H(n).toMatchObject({
|
|
1592
1891
|
id: "nulls",
|
|
1593
1892
|
title: "",
|
|
1594
1893
|
at: null,
|
|
1595
1894
|
n: 9
|
|
1596
|
-
}),
|
|
1895
|
+
}), H(await e.upsert("voltro_parity_d", t, {
|
|
1597
1896
|
conflictColumns: ["id"],
|
|
1598
1897
|
update: [],
|
|
1599
1898
|
updateValues: { n: null }
|
|
@@ -1604,7 +1903,7 @@ var q = h("voltro_parity_composite", {
|
|
|
1604
1903
|
n: null
|
|
1605
1904
|
});
|
|
1606
1905
|
});
|
|
1607
|
-
}),
|
|
1906
|
+
}), U("binds false and zero without treating them as omitted", async () => {
|
|
1608
1907
|
await t(async (e) => {
|
|
1609
1908
|
let t = {
|
|
1610
1909
|
id: "falsy",
|
|
@@ -1620,14 +1919,14 @@ var q = h("voltro_parity_composite", {
|
|
|
1620
1919
|
count: 0
|
|
1621
1920
|
}
|
|
1622
1921
|
});
|
|
1623
|
-
let n = await e.query(
|
|
1624
|
-
|
|
1922
|
+
let n = await e.query(X("voltro_parity_c"));
|
|
1923
|
+
H(n).toEqual([H.objectContaining({
|
|
1625
1924
|
id: "falsy",
|
|
1626
1925
|
title: "keep",
|
|
1627
1926
|
count: 0
|
|
1628
|
-
})]),
|
|
1927
|
+
})]), H([!1, 0]).toContain(n[0].flag);
|
|
1629
1928
|
});
|
|
1630
|
-
}),
|
|
1929
|
+
}), U("encodes JSON/array assignments and binds SQL-looking strings as data", async () => {
|
|
1631
1930
|
await t(async (e) => {
|
|
1632
1931
|
let t = {
|
|
1633
1932
|
id: "structured",
|
|
@@ -1652,7 +1951,7 @@ var q = h("voltro_parity_composite", {
|
|
|
1652
1951
|
update: [],
|
|
1653
1952
|
updateValues: n
|
|
1654
1953
|
};
|
|
1655
|
-
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([{
|
|
1656
1955
|
id: "structured",
|
|
1657
1956
|
...n
|
|
1658
1957
|
}]), await e.upsert("voltro_parity_values", t, {
|
|
@@ -1661,14 +1960,14 @@ var q = h("voltro_parity_composite", {
|
|
|
1661
1960
|
metadata: null,
|
|
1662
1961
|
labels: []
|
|
1663
1962
|
}
|
|
1664
|
-
}),
|
|
1963
|
+
}), H(await e.query(X("voltro_parity_values"))).toEqual([{
|
|
1665
1964
|
id: "structured",
|
|
1666
1965
|
title: n.title,
|
|
1667
1966
|
metadata: null,
|
|
1668
1967
|
labels: []
|
|
1669
1968
|
}]);
|
|
1670
1969
|
});
|
|
1671
|
-
}),
|
|
1970
|
+
}), U("keeps structured raw-driver values working through all write verbs", async () => {
|
|
1672
1971
|
await t(async (e) => {
|
|
1673
1972
|
let t = {
|
|
1674
1973
|
title: "before",
|
|
@@ -1688,7 +1987,7 @@ var q = h("voltro_parity_composite", {
|
|
|
1688
1987
|
}, {
|
|
1689
1988
|
id: "callback",
|
|
1690
1989
|
...t
|
|
1691
|
-
}]), 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", {
|
|
1692
1991
|
id: "callback",
|
|
1693
1992
|
...t
|
|
1694
1993
|
}, {
|
|
@@ -1701,8 +2000,8 @@ var q = h("voltro_parity_composite", {
|
|
|
1701
2000
|
id: "ignore-new",
|
|
1702
2001
|
...n
|
|
1703
2002
|
}, { conflictColumns: ["id"] });
|
|
1704
|
-
let r = await e.query(
|
|
1705
|
-
|
|
2003
|
+
let r = await e.query(X("voltro_parity_values"));
|
|
2004
|
+
H([...r].sort((e, t) => String(e.id).localeCompare(String(t.id)))).toEqual([
|
|
1706
2005
|
{
|
|
1707
2006
|
id: "bulk",
|
|
1708
2007
|
...n
|
|
@@ -1721,7 +2020,7 @@ var q = h("voltro_parity_composite", {
|
|
|
1721
2020
|
}
|
|
1722
2021
|
]);
|
|
1723
2022
|
});
|
|
1724
|
-
}),
|
|
2023
|
+
}), U.each([
|
|
1725
2024
|
"id",
|
|
1726
2025
|
"teamId",
|
|
1727
2026
|
"year"
|
|
@@ -1737,20 +2036,20 @@ var q = h("voltro_parity_composite", {
|
|
|
1737
2036
|
update: [],
|
|
1738
2037
|
updateValues: { [e]: "different" }
|
|
1739
2038
|
};
|
|
1740
|
-
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)]);
|
|
1741
2040
|
});
|
|
1742
2041
|
});
|
|
1743
|
-
}),
|
|
1744
|
-
await
|
|
2042
|
+
}), U("upsert updateValues rolls back row changes and discards transaction events", async () => {
|
|
2043
|
+
await a.store.insert("voltro_parity_a", {
|
|
1745
2044
|
id: "rollback-values",
|
|
1746
2045
|
title: "keep",
|
|
1747
2046
|
count: 1
|
|
1748
2047
|
});
|
|
1749
|
-
let e = [], t =
|
|
2048
|
+
let e = [], t = a.store.onChange((t) => {
|
|
1750
2049
|
e.push(t);
|
|
1751
2050
|
});
|
|
1752
2051
|
try {
|
|
1753
|
-
await
|
|
2052
|
+
await H(a.store.transactional(async (e) => {
|
|
1754
2053
|
let t = await e.upsert("voltro_parity_a", {
|
|
1755
2054
|
id: "rollback-values",
|
|
1756
2055
|
title: "incoming",
|
|
@@ -1760,25 +2059,25 @@ var q = h("voltro_parity_composite", {
|
|
|
1760
2059
|
update: [],
|
|
1761
2060
|
updateValues: { count: 42 }
|
|
1762
2061
|
});
|
|
1763
|
-
throw
|
|
2062
|
+
throw H(t).toMatchObject({
|
|
1764
2063
|
title: "keep",
|
|
1765
2064
|
count: 42
|
|
1766
2065
|
}), Error("rollback values");
|
|
1767
|
-
})).rejects.toThrow("rollback values"),
|
|
2066
|
+
})).rejects.toThrow("rollback values"), H(await a.store.query(X("voltro_parity_a"))).toEqual([H.objectContaining({
|
|
1768
2067
|
id: "rollback-values",
|
|
1769
2068
|
title: "keep",
|
|
1770
2069
|
count: 1
|
|
1771
|
-
})]),
|
|
2070
|
+
})]), H(e).toEqual([]);
|
|
1772
2071
|
} finally {
|
|
1773
2072
|
t();
|
|
1774
2073
|
}
|
|
1775
|
-
}),
|
|
1776
|
-
await
|
|
2074
|
+
}), U("and a DIFFERENT row is still not silently overwritten", async () => {
|
|
2075
|
+
await a.store.upsert("voltro_parity_upsert", {
|
|
1777
2076
|
id: "up_a",
|
|
1778
2077
|
teamId: "t-1",
|
|
1779
2078
|
year: 2026,
|
|
1780
2079
|
hours: 1
|
|
1781
|
-
}, { conflictColumns: ["teamId", "year"] }), await
|
|
2080
|
+
}, { conflictColumns: ["teamId", "year"] }), await a.store.upsert("voltro_parity_upsert", {
|
|
1782
2081
|
id: "up_b",
|
|
1783
2082
|
teamId: "t-2",
|
|
1784
2083
|
year: 2026,
|
|
@@ -1787,66 +2086,66 @@ var q = h("voltro_parity_composite", {
|
|
|
1787
2086
|
conflictColumns: ["teamId", "year"],
|
|
1788
2087
|
update: ["hours"]
|
|
1789
2088
|
});
|
|
1790
|
-
let e = await
|
|
1791
|
-
|
|
1792
|
-
}),
|
|
1793
|
-
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", {
|
|
1794
2093
|
id: "nd1",
|
|
1795
2094
|
title: "nulls",
|
|
1796
2095
|
at: null,
|
|
1797
2096
|
n: null
|
|
1798
2097
|
});
|
|
1799
|
-
|
|
2098
|
+
H(e).toMatchObject({
|
|
1800
2099
|
id: "nd1",
|
|
1801
2100
|
title: "nulls",
|
|
1802
2101
|
at: null,
|
|
1803
2102
|
n: null
|
|
1804
|
-
}), await
|
|
2103
|
+
}), await a.store.insert("voltro_parity_d", {
|
|
1805
2104
|
id: "nd2",
|
|
1806
2105
|
title: "set",
|
|
1807
2106
|
at: /* @__PURE__ */ new Date(),
|
|
1808
2107
|
n: 7
|
|
1809
2108
|
});
|
|
1810
|
-
let t = await
|
|
2109
|
+
let t = await a.store.update("voltro_parity_d", "nd2", {
|
|
1811
2110
|
at: null,
|
|
1812
2111
|
n: null
|
|
1813
2112
|
});
|
|
1814
|
-
|
|
2113
|
+
H(t).toMatchObject({
|
|
1815
2114
|
id: "nd2",
|
|
1816
2115
|
at: null,
|
|
1817
2116
|
n: null
|
|
1818
2117
|
});
|
|
1819
|
-
}),
|
|
1820
|
-
await
|
|
2118
|
+
}), U("updateMany updates only the rows matching the predicate + returns the count", async () => {
|
|
2119
|
+
await a.store.insert("voltro_parity_a", {
|
|
1821
2120
|
id: "m1",
|
|
1822
2121
|
title: "x",
|
|
1823
2122
|
count: 5
|
|
1824
|
-
}), await
|
|
2123
|
+
}), await a.store.insert("voltro_parity_a", {
|
|
1825
2124
|
id: "m2",
|
|
1826
2125
|
title: "y",
|
|
1827
2126
|
count: 5
|
|
1828
|
-
}), await
|
|
2127
|
+
}), await a.store.insert("voltro_parity_a", {
|
|
1829
2128
|
id: "m3",
|
|
1830
2129
|
title: "z",
|
|
1831
2130
|
count: 9
|
|
1832
2131
|
});
|
|
1833
|
-
let e = await
|
|
2132
|
+
let e = await a.store.updateMany("voltro_parity_a", { title: "hit" }, { where: {
|
|
1834
2133
|
column: "count",
|
|
1835
2134
|
op: "eq",
|
|
1836
2135
|
value: 5
|
|
1837
2136
|
} });
|
|
1838
|
-
|
|
1839
|
-
let t = await
|
|
1840
|
-
...
|
|
2137
|
+
H(e).toBe(2);
|
|
2138
|
+
let t = await a.store.query({
|
|
2139
|
+
...X("voltro_parity_a"),
|
|
1841
2140
|
predicate: {
|
|
1842
2141
|
column: "title",
|
|
1843
2142
|
op: "eq",
|
|
1844
2143
|
value: "hit"
|
|
1845
2144
|
}
|
|
1846
2145
|
});
|
|
1847
|
-
|
|
1848
|
-
}),
|
|
1849
|
-
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", {
|
|
1850
2149
|
id: "cas",
|
|
1851
2150
|
title: "pending",
|
|
1852
2151
|
count: 1
|
|
@@ -1859,76 +2158,75 @@ var q = h("voltro_parity_composite", {
|
|
|
1859
2158
|
column: "title",
|
|
1860
2159
|
op: "eq",
|
|
1861
2160
|
value: e
|
|
1862
|
-
}] }), t = await
|
|
1863
|
-
|
|
1864
|
-
let n = await
|
|
1865
|
-
|
|
1866
|
-
}),
|
|
1867
|
-
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", {
|
|
1868
2167
|
id: "d1",
|
|
1869
2168
|
title: "x",
|
|
1870
2169
|
count: 5
|
|
1871
|
-
}), await
|
|
2170
|
+
}), await a.store.insert("voltro_parity_a", {
|
|
1872
2171
|
id: "d2",
|
|
1873
2172
|
title: "y",
|
|
1874
2173
|
count: 5
|
|
1875
|
-
}), await
|
|
2174
|
+
}), await a.store.insert("voltro_parity_a", {
|
|
1876
2175
|
id: "d3",
|
|
1877
2176
|
title: "z",
|
|
1878
2177
|
count: 9
|
|
1879
2178
|
});
|
|
1880
|
-
let e = await
|
|
2179
|
+
let e = await a.store.deleteMany("voltro_parity_a", { where: {
|
|
1881
2180
|
column: "count",
|
|
1882
2181
|
op: "eq",
|
|
1883
2182
|
value: 5
|
|
1884
2183
|
} });
|
|
1885
|
-
|
|
1886
|
-
let t = await
|
|
1887
|
-
|
|
1888
|
-
}),
|
|
1889
|
-
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", {
|
|
1890
2189
|
id: "e1",
|
|
1891
2190
|
title: "gone",
|
|
1892
2191
|
count: 1
|
|
1893
|
-
}), await
|
|
2192
|
+
}), await a.store.insert("voltro_parity_a", {
|
|
1894
2193
|
id: "e2",
|
|
1895
2194
|
title: "gone",
|
|
1896
2195
|
count: 1
|
|
1897
|
-
}), await
|
|
2196
|
+
}), await a.store.insert("voltro_parity_a", {
|
|
1898
2197
|
id: "e3",
|
|
1899
2198
|
title: "stay",
|
|
1900
2199
|
count: 2
|
|
1901
2200
|
});
|
|
1902
|
-
let e = [], t =
|
|
2201
|
+
let e = [], t = a.store.onChange((t) => e.push({
|
|
1903
2202
|
op: t.op,
|
|
1904
2203
|
id: t.old?.id
|
|
1905
|
-
})), n = await
|
|
2204
|
+
})), n = await a.store.deleteMany("voltro_parity_a", { where: {
|
|
1906
2205
|
column: "title",
|
|
1907
2206
|
op: "eq",
|
|
1908
2207
|
value: "gone"
|
|
1909
2208
|
} });
|
|
1910
|
-
t(),
|
|
1911
|
-
let
|
|
1912
|
-
|
|
1913
|
-
}),
|
|
1914
|
-
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", {
|
|
1915
2214
|
id: "k1",
|
|
1916
2215
|
title: "keep",
|
|
1917
2216
|
count: 1
|
|
1918
2217
|
});
|
|
1919
|
-
let e = await
|
|
2218
|
+
let e = await a.store.deleteMany("voltro_parity_a", { where: {
|
|
1920
2219
|
column: "title",
|
|
1921
2220
|
op: "eq",
|
|
1922
2221
|
value: "absent"
|
|
1923
2222
|
} });
|
|
1924
|
-
|
|
1925
|
-
let t = await
|
|
1926
|
-
|
|
1927
|
-
}),
|
|
1928
|
-
let
|
|
1929
|
-
|
|
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);
|
|
1930
2229
|
});
|
|
1931
2230
|
});
|
|
1932
2231
|
};
|
|
1933
|
-
|
|
1934
|
-
export { P as applySchema, X as runDialectParity };
|
|
2232
|
+
export { q as applySchema, $ as runDialectParity, e as runHotStoreSqlParity };
|