toiljs 0.0.60 → 0.0.61
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/.github/workflows/ci.yml +31 -0
- package/CHANGELOG.md +5 -0
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +2 -2
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/index.d.ts +1 -1
- package/build/client/index.js +1 -1
- package/build/client/routing/mount.js +12 -1
- package/build/client/ssr/markers.d.ts +1 -0
- package/build/client/ssr/markers.js +3 -0
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/config.d.ts +21 -0
- package/build/compiler/config.js +35 -0
- package/build/compiler/docs.d.ts +2 -1
- package/build/compiler/docs.js +33 -304
- package/build/compiler/index.d.ts +13 -0
- package/build/compiler/index.js +113 -21
- package/build/compiler/template-build.d.ts +21 -1
- package/build/compiler/template-build.js +110 -26
- package/build/compiler/toil-docs.generated.d.ts +1 -0
- package/build/compiler/toil-docs.generated.js +20 -0
- package/build/devserver/.tsbuildinfo +1 -1
- package/build/devserver/daemon/catalog.d.ts +26 -0
- package/build/devserver/daemon/catalog.js +48 -0
- package/build/devserver/daemon/cron.d.ts +4 -0
- package/build/devserver/daemon/cron.js +50 -0
- package/build/devserver/daemon/host.d.ts +37 -0
- package/build/devserver/daemon/host.js +94 -0
- package/build/devserver/daemon/index.d.ts +34 -0
- package/build/devserver/daemon/index.js +241 -0
- package/build/devserver/db/catalog.d.ts +2 -1
- package/build/devserver/db/catalog.js +44 -44
- package/build/devserver/db/database.d.ts +27 -11
- package/build/devserver/db/database.js +539 -169
- package/build/devserver/db/index.d.ts +1 -1
- package/build/devserver/db/index.js +1 -1
- package/build/devserver/db/routeKinds.d.ts +8 -0
- package/build/devserver/db/routeKinds.js +139 -0
- package/build/devserver/db/types.d.ts +64 -1
- package/build/devserver/db/types.js +33 -1
- package/build/devserver/index.d.ts +10 -0
- package/build/devserver/index.js +7 -0
- package/build/devserver/mstore/store.d.ts +18 -0
- package/build/devserver/mstore/store.js +82 -0
- package/build/devserver/runtime/host.d.ts +6 -0
- package/build/devserver/runtime/host.js +45 -1
- package/build/devserver/runtime/module.d.ts +1 -0
- package/build/devserver/runtime/module.js +27 -1
- package/build/devserver/server.d.ts +6 -0
- package/build/devserver/server.js +59 -0
- package/build/devserver/ssr.d.ts +25 -0
- package/build/devserver/ssr.js +114 -0
- package/build/devserver/wasm/sections.d.ts +2 -0
- package/build/devserver/wasm/sections.js +42 -0
- package/build/devserver/wasm/surface.d.ts +18 -0
- package/build/devserver/wasm/surface.js +41 -0
- package/docs/README.md +4 -4
- package/docs/auth-todo.md +6 -6
- package/docs/caching.md +5 -5
- package/docs/cli.md +15 -0
- package/docs/client.md +40 -0
- package/docs/crypto.md +4 -4
- package/docs/data.md +6 -6
- package/docs/email.md +28 -28
- package/docs/environment.md +10 -10
- package/docs/index.md +26 -0
- package/docs/ratelimit.md +10 -10
- package/docs/routing.md +2 -2
- package/docs/server.md +61 -0
- package/docs/ssr.md +561 -113
- package/docs/styling.md +22 -0
- package/docs/time.md +1 -1
- package/eslint.config.js +10 -1
- package/examples/basic/client/components/Header.tsx +3 -0
- package/examples/basic/client/routes/features/actions.tsx +0 -2
- package/examples/basic/client/routes/hello.tsx +89 -19
- package/examples/basic/client/styles/main.css +48 -0
- package/examples/basic/server/SsrHelloRender.ts +97 -0
- package/examples/basic/server/main.ts +5 -0
- package/examples/basic/server/streams/Echo.ts +49 -0
- package/package.json +12 -10
- package/scripts/gen-toil-docs.mjs +96 -0
- package/src/cli/create.ts +2 -2
- package/src/client/index.ts +1 -1
- package/src/client/routing/mount.tsx +18 -2
- package/src/client/ssr/markers.tsx +22 -0
- package/src/compiler/config.ts +88 -2
- package/src/compiler/docs.ts +47 -308
- package/src/compiler/index.ts +236 -32
- package/src/compiler/ssr-codegen.ts +1 -1
- package/src/compiler/template-build.ts +247 -46
- package/src/compiler/toil-docs.generated.ts +26 -0
- package/src/devserver/daemon/catalog.ts +120 -0
- package/src/devserver/daemon/cron.ts +87 -0
- package/src/devserver/daemon/host.ts +224 -0
- package/src/devserver/daemon/index.ts +349 -0
- package/src/devserver/db/catalog.ts +61 -53
- package/src/devserver/db/database.ts +613 -149
- package/src/devserver/db/index.ts +1 -1
- package/src/devserver/db/routeKinds.ts +147 -0
- package/src/devserver/db/types.ts +65 -2
- package/src/devserver/index.ts +12 -0
- package/src/devserver/mstore/store.ts +121 -0
- package/src/devserver/runtime/host.ts +92 -1
- package/src/devserver/runtime/module.ts +35 -1
- package/src/devserver/server.ts +101 -0
- package/src/devserver/ssr.ts +166 -0
- package/src/devserver/wasm/sections.ts +59 -0
- package/src/devserver/wasm/surface.ts +88 -0
- package/test/daemon-build.test.ts +198 -0
- package/test/daemon-catalog.test.ts +265 -0
- package/test/daemon-emulation.test.ts +216 -0
- package/test/devserver-database.test.ts +396 -5
- package/test/email-preview.test.ts +6 -1
- package/test/fixtures/daemon-app.ts +56 -0
- package/test/global-setup.ts +17 -0
- package/test/ssr-render.test.ts +94 -27
- package/test/ssr-template.test.tsx +44 -1
- package/vitest.config.ts +3 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { DataReader, DataWriter } from 'toiljs/io';
|
|
4
5
|
import { parseCatalog } from './catalog.js';
|
|
5
|
-
import { ABSENT, ALREADY_EXISTS, CODEC_ERR, CONFLICT, INVALID_HANDLE, MAX_KEY, MAX_NAME, MAX_RESERVATION_TTL_MS, MAX_RESERVATIONS, MAX_VALUE, satI64, TOO_MANY_KEYS, TOO_SMALL, } from './types.js';
|
|
6
|
+
import { ABSENT, ALREADY_EXISTS, CODEC_ERR, CollectionFamily, CONFLICT, DbFunctionKind, DEFAULT_FILL_WAIT_MS, INVALID_HANDLE, MAX_KEY, MAX_FILL_WAIT_MS, MAX_NAME, MAX_RESERVATION_TTL_MS, MAX_RESERVATIONS, MAX_VALUE, OP_NOT_ALLOWED_FOR_FAMILY, OP_NOT_ALLOWED_IN_KIND, satI64, SCHEMA_UNAVAILABLE, TOO_MANY_KEYS, TOO_SMALL, UNAVAILABLE, isCollectionFamily, } from './types.js';
|
|
6
7
|
function mem(ref) {
|
|
7
8
|
if (!ref.memory)
|
|
8
9
|
throw new Error('data host import called before memory was bound');
|
|
@@ -22,27 +23,221 @@ function readKey(ref, ptr, len) {
|
|
|
22
23
|
function storeKey(collection, key) {
|
|
23
24
|
return collection + '\0' + key.toString('latin1');
|
|
24
25
|
}
|
|
26
|
+
function readIdem(ref, ptr) {
|
|
27
|
+
if (ptr === 0)
|
|
28
|
+
return null;
|
|
29
|
+
return readCopy(ref, ptr, 16);
|
|
30
|
+
}
|
|
31
|
+
function u64le(n) {
|
|
32
|
+
const b = Buffer.allocUnsafe(8);
|
|
33
|
+
b.writeBigUInt64LE(BigInt(n));
|
|
34
|
+
return b;
|
|
35
|
+
}
|
|
36
|
+
const RECORD_OP_CREATE = 4;
|
|
37
|
+
const RECORD_OP_PATCH = 5;
|
|
38
|
+
const RECORD_OP_DELETE = 6;
|
|
39
|
+
const RECORD_OP_GET_DELETE = 7;
|
|
40
|
+
const RECORD_OP_ENQUEUE = 8;
|
|
41
|
+
function recordRequestHash(op, key, value) {
|
|
42
|
+
return createHash('sha256')
|
|
43
|
+
.update('toildb/record-idempotency/request/v1')
|
|
44
|
+
.update(Buffer.from([op]))
|
|
45
|
+
.update(u64le(key.length))
|
|
46
|
+
.update(key)
|
|
47
|
+
.update(u64le(value.length))
|
|
48
|
+
.update(value)
|
|
49
|
+
.digest('hex');
|
|
50
|
+
}
|
|
51
|
+
function idemKey(coll, key, op, idem) {
|
|
52
|
+
return `${storeKey(coll.name, key)}\0${op}\0${idem.toString('hex')}`;
|
|
53
|
+
}
|
|
54
|
+
function reservationIdFromIdem(coll, key, idem) {
|
|
55
|
+
const digest = createHash('sha256')
|
|
56
|
+
.update('toildb/capacity-reservation-id/v1')
|
|
57
|
+
.update(coll.name)
|
|
58
|
+
.update('\0')
|
|
59
|
+
.update(key)
|
|
60
|
+
.update(idem)
|
|
61
|
+
.digest();
|
|
62
|
+
return digest.readBigUInt64LE(0) | (1n << 63n);
|
|
63
|
+
}
|
|
64
|
+
function snapshotOutcome(outcome) {
|
|
65
|
+
switch (outcome.kind) {
|
|
66
|
+
case 'value':
|
|
67
|
+
return {
|
|
68
|
+
kind: 'value',
|
|
69
|
+
v: outcome.value.toString('base64'),
|
|
70
|
+
sv: outcome.schemaVersion,
|
|
71
|
+
};
|
|
72
|
+
default:
|
|
73
|
+
return { kind: outcome.kind };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function loadOutcome(outcome) {
|
|
77
|
+
if (outcome.kind === 'value') {
|
|
78
|
+
return {
|
|
79
|
+
kind: 'value',
|
|
80
|
+
value: Buffer.from(outcome.v, 'base64'),
|
|
81
|
+
schemaVersion: outcome.sv,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return { kind: outcome.kind };
|
|
85
|
+
}
|
|
25
86
|
function collOf(db, handle) {
|
|
26
87
|
return handle >= 0 && handle < db.handles.length ? db.handles[handle] : null;
|
|
27
88
|
}
|
|
89
|
+
function collOfFamily(db, handle, ...families) {
|
|
90
|
+
const coll = collOf(db, handle);
|
|
91
|
+
if (coll === null)
|
|
92
|
+
return INVALID_HANDLE;
|
|
93
|
+
return families.includes(coll.family) ? coll : OP_NOT_ALLOWED_FOR_FAMILY;
|
|
94
|
+
}
|
|
95
|
+
var DbOp;
|
|
96
|
+
(function (DbOp) {
|
|
97
|
+
DbOp[DbOp["Get"] = 0] = "Get";
|
|
98
|
+
DbOp[DbOp["GetMany"] = 1] = "GetMany";
|
|
99
|
+
DbOp[DbOp["Exists"] = 2] = "Exists";
|
|
100
|
+
DbOp[DbOp["Create"] = 3] = "Create";
|
|
101
|
+
DbOp[DbOp["Patch"] = 4] = "Patch";
|
|
102
|
+
DbOp[DbOp["Delete"] = 5] = "Delete";
|
|
103
|
+
DbOp[DbOp["GetDelete"] = 6] = "GetDelete";
|
|
104
|
+
DbOp[DbOp["Enqueue"] = 7] = "Enqueue";
|
|
105
|
+
DbOp[DbOp["Append"] = 8] = "Append";
|
|
106
|
+
DbOp[DbOp["AppendOnce"] = 9] = "AppendOnce";
|
|
107
|
+
DbOp[DbOp["Latest"] = 10] = "Latest";
|
|
108
|
+
DbOp[DbOp["CounterGet"] = 11] = "CounterGet";
|
|
109
|
+
DbOp[DbOp["CounterAdd"] = 12] = "CounterAdd";
|
|
110
|
+
DbOp[DbOp["MembershipContains"] = 13] = "MembershipContains";
|
|
111
|
+
DbOp[DbOp["MembershipAdd"] = 14] = "MembershipAdd";
|
|
112
|
+
DbOp[DbOp["MembershipRemove"] = 15] = "MembershipRemove";
|
|
113
|
+
DbOp[DbOp["MembershipList"] = 16] = "MembershipList";
|
|
114
|
+
DbOp[DbOp["UniqueLookup"] = 17] = "UniqueLookup";
|
|
115
|
+
DbOp[DbOp["UniqueClaim"] = 18] = "UniqueClaim";
|
|
116
|
+
DbOp[DbOp["UniqueRelease"] = 19] = "UniqueRelease";
|
|
117
|
+
DbOp[DbOp["CapacityAvailable"] = 20] = "CapacityAvailable";
|
|
118
|
+
DbOp[DbOp["CapacityReserve"] = 21] = "CapacityReserve";
|
|
119
|
+
DbOp[DbOp["CapacityConfirm"] = 22] = "CapacityConfirm";
|
|
120
|
+
DbOp[DbOp["CapacityCancel"] = 23] = "CapacityCancel";
|
|
121
|
+
DbOp[DbOp["ViewGet"] = 24] = "ViewGet";
|
|
122
|
+
DbOp[DbOp["ViewPublish"] = 25] = "ViewPublish";
|
|
123
|
+
DbOp[DbOp["CapacitySetTotal"] = 26] = "CapacitySetTotal";
|
|
124
|
+
})(DbOp || (DbOp = {}));
|
|
125
|
+
function isReadOp(op) {
|
|
126
|
+
return (op === DbOp.Get ||
|
|
127
|
+
op === DbOp.GetMany ||
|
|
128
|
+
op === DbOp.Exists ||
|
|
129
|
+
op === DbOp.ViewGet ||
|
|
130
|
+
op === DbOp.CounterGet ||
|
|
131
|
+
op === DbOp.MembershipContains ||
|
|
132
|
+
op === DbOp.MembershipList ||
|
|
133
|
+
op === DbOp.UniqueLookup ||
|
|
134
|
+
op === DbOp.Latest ||
|
|
135
|
+
op === DbOp.CapacityAvailable);
|
|
136
|
+
}
|
|
137
|
+
function isScanOp(op) {
|
|
138
|
+
return op === DbOp.Latest || op === DbOp.MembershipList;
|
|
139
|
+
}
|
|
140
|
+
function kindAllows(kind, op) {
|
|
141
|
+
if (kind === DbFunctionKind.Query)
|
|
142
|
+
return isReadOp(op) && !isScanOp(op);
|
|
143
|
+
if (kind === DbFunctionKind.Action) {
|
|
144
|
+
return ((isReadOp(op) && !isScanOp(op)) ||
|
|
145
|
+
op === DbOp.Create ||
|
|
146
|
+
op === DbOp.Patch ||
|
|
147
|
+
op === DbOp.Delete ||
|
|
148
|
+
op === DbOp.GetDelete ||
|
|
149
|
+
op === DbOp.Enqueue ||
|
|
150
|
+
op === DbOp.Append ||
|
|
151
|
+
op === DbOp.AppendOnce ||
|
|
152
|
+
op === DbOp.CounterAdd ||
|
|
153
|
+
op === DbOp.MembershipAdd ||
|
|
154
|
+
op === DbOp.MembershipRemove ||
|
|
155
|
+
op === DbOp.UniqueClaim ||
|
|
156
|
+
op === DbOp.UniqueRelease ||
|
|
157
|
+
op === DbOp.CapacityReserve ||
|
|
158
|
+
op === DbOp.CapacityConfirm ||
|
|
159
|
+
op === DbOp.CapacityCancel);
|
|
160
|
+
}
|
|
161
|
+
if (kind === DbFunctionKind.Derive)
|
|
162
|
+
return (isReadOp(op) || op === DbOp.ViewPublish || op === DbOp.Append || op === DbOp.CounterAdd);
|
|
163
|
+
if (kind === DbFunctionKind.Job)
|
|
164
|
+
return true;
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
function collForOp(db, handle, op, ...families) {
|
|
168
|
+
const coll = collOfFamily(db, handle, ...families);
|
|
169
|
+
if (typeof coll === 'number')
|
|
170
|
+
return coll;
|
|
171
|
+
return kindAllows(db.functionKind, op) ? coll : OP_NOT_ALLOWED_IN_KIND;
|
|
172
|
+
}
|
|
28
173
|
export class DevDatabase {
|
|
29
174
|
store = new Map();
|
|
175
|
+
recordIdem = new Map();
|
|
176
|
+
uniqueIdem = new Map();
|
|
30
177
|
views = new Map();
|
|
31
178
|
members = new Map();
|
|
32
179
|
counters = new Map();
|
|
180
|
+
counterIdem = new Map();
|
|
33
181
|
events = new Map();
|
|
34
182
|
eventDedup = new Map();
|
|
35
183
|
capacity = new Map();
|
|
36
184
|
versions = new Map();
|
|
37
185
|
eventVersions = new Map();
|
|
38
186
|
memberVersions = new Map();
|
|
39
|
-
catalog =
|
|
187
|
+
catalog = { kind: 'no-section' };
|
|
40
188
|
persistPath = null;
|
|
41
189
|
setCatalog(wasm) {
|
|
42
190
|
this.catalog = parseCatalog(wasm);
|
|
43
191
|
}
|
|
192
|
+
currentSchemaVersion(coll) {
|
|
193
|
+
if (this.catalog.kind !== 'present')
|
|
194
|
+
return coll.schemaVersion;
|
|
195
|
+
return this.catalog.collections.get(coll.name)?.schemaVersion ?? coll.schemaVersion;
|
|
196
|
+
}
|
|
44
197
|
stampVersion(coll, sk) {
|
|
45
|
-
this.versions.set(sk, this.
|
|
198
|
+
this.versions.set(sk, this.currentSchemaVersion(coll));
|
|
199
|
+
}
|
|
200
|
+
recordIdemStart(coll, key, op, idem, requestHash) {
|
|
201
|
+
if (idem === null)
|
|
202
|
+
return { fresh: true };
|
|
203
|
+
const ik = idemKey(coll, key, op, idem);
|
|
204
|
+
const row = this.recordIdem.get(ik);
|
|
205
|
+
if (row === undefined) {
|
|
206
|
+
this.recordIdem.set(ik, { requestHash, outcome: null });
|
|
207
|
+
return { fresh: true };
|
|
208
|
+
}
|
|
209
|
+
if (row.requestHash !== requestHash)
|
|
210
|
+
return { fresh: false, status: CONFLICT };
|
|
211
|
+
if (row.outcome === null)
|
|
212
|
+
return { fresh: false, status: UNAVAILABLE };
|
|
213
|
+
return { fresh: false, status: 0, outcome: row.outcome };
|
|
214
|
+
}
|
|
215
|
+
recordIdemFinish(coll, key, op, idem, requestHash, outcome) {
|
|
216
|
+
if (idem === null)
|
|
217
|
+
return;
|
|
218
|
+
const ik = idemKey(coll, key, op, idem);
|
|
219
|
+
const row = this.recordIdem.get(ik);
|
|
220
|
+
if (row === undefined || row.requestHash !== requestHash)
|
|
221
|
+
return;
|
|
222
|
+
if (row.outcome === null)
|
|
223
|
+
row.outcome = outcome;
|
|
224
|
+
}
|
|
225
|
+
replayRecordOutcome(db, outcome) {
|
|
226
|
+
switch (outcome.kind) {
|
|
227
|
+
case 'unit':
|
|
228
|
+
return 0;
|
|
229
|
+
case 'value':
|
|
230
|
+
db.lastResult = outcome.value;
|
|
231
|
+
db.lastResultVersion = outcome.schemaVersion;
|
|
232
|
+
return outcome.value.length;
|
|
233
|
+
case 'absent':
|
|
234
|
+
case 'not_found':
|
|
235
|
+
return ABSENT;
|
|
236
|
+
case 'already_exists':
|
|
237
|
+
return ALREADY_EXISTS;
|
|
238
|
+
case 'conflict':
|
|
239
|
+
return CONFLICT;
|
|
240
|
+
}
|
|
46
241
|
}
|
|
47
242
|
configurePersistence(filePath) {
|
|
48
243
|
this.persistPath = filePath;
|
|
@@ -53,15 +248,26 @@ export class DevDatabase {
|
|
|
53
248
|
return;
|
|
54
249
|
const snap = {
|
|
55
250
|
store: {},
|
|
251
|
+
recordIdem: {},
|
|
252
|
+
uniqueIdem: {},
|
|
56
253
|
views: {},
|
|
57
254
|
members: {},
|
|
58
255
|
counters: {},
|
|
256
|
+
counterIdem: {},
|
|
59
257
|
events: {},
|
|
60
258
|
eventDedup: {},
|
|
61
259
|
capacity: {},
|
|
62
260
|
};
|
|
63
261
|
for (const [k, v] of this.store)
|
|
64
262
|
snap.store[k] = { v: v.toString('base64'), sv: this.versions.get(k) ?? 0 };
|
|
263
|
+
for (const [k, row] of this.recordIdem)
|
|
264
|
+
snap.recordIdem[k] = {
|
|
265
|
+
requestHash: row.requestHash,
|
|
266
|
+
state: row.outcome === null ? 'pending' : 'done',
|
|
267
|
+
outcome: row.outcome === null ? undefined : snapshotOutcome(row.outcome),
|
|
268
|
+
};
|
|
269
|
+
for (const [k, v] of this.uniqueIdem)
|
|
270
|
+
snap.uniqueIdem[k] = v;
|
|
65
271
|
for (const [k, v] of this.views)
|
|
66
272
|
snap.views[k] = { v: v.toString('base64'), sv: this.versions.get(k) ?? 0 };
|
|
67
273
|
for (const [k, m] of this.members) {
|
|
@@ -73,6 +279,8 @@ export class DevDatabase {
|
|
|
73
279
|
}
|
|
74
280
|
for (const [k, v] of this.counters)
|
|
75
281
|
snap.counters[k] = v.toString();
|
|
282
|
+
for (const [k, v] of this.counterIdem)
|
|
283
|
+
snap.counterIdem[k] = v.toString();
|
|
76
284
|
for (const [k, log] of this.events) {
|
|
77
285
|
const ver = this.eventVersions.get(k) ?? [];
|
|
78
286
|
snap.events[k] = log.map((b, i) => ({ v: b.toString('base64'), sv: ver[i] ?? 0 }));
|
|
@@ -112,6 +320,16 @@ export class DevDatabase {
|
|
|
112
320
|
this.store.set(k, Buffer.from(e.v, 'base64'));
|
|
113
321
|
this.versions.set(k, e.sv);
|
|
114
322
|
}
|
|
323
|
+
for (const [k, row] of Object.entries(snap.recordIdem ?? {})) {
|
|
324
|
+
this.recordIdem.set(k, {
|
|
325
|
+
requestHash: row.requestHash,
|
|
326
|
+
outcome: row.state === 'done' && row.outcome !== undefined
|
|
327
|
+
? loadOutcome(row.outcome)
|
|
328
|
+
: null,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
for (const [k, v] of Object.entries(snap.uniqueIdem ?? {}))
|
|
332
|
+
this.uniqueIdem.set(k, v);
|
|
115
333
|
for (const [k, e] of Object.entries(snap.views ?? {})) {
|
|
116
334
|
this.views.set(k, Buffer.from(e.v, 'base64'));
|
|
117
335
|
this.versions.set(k, e.sv);
|
|
@@ -128,6 +346,8 @@ export class DevDatabase {
|
|
|
128
346
|
}
|
|
129
347
|
for (const [k, v] of Object.entries(snap.counters ?? {}))
|
|
130
348
|
this.counters.set(k, BigInt(v));
|
|
349
|
+
for (const [k, v] of Object.entries(snap.counterIdem ?? {}))
|
|
350
|
+
this.counterIdem.set(k, BigInt(v));
|
|
131
351
|
for (const [k, log] of Object.entries(snap.events ?? {})) {
|
|
132
352
|
this.events.set(k, log.map((e) => Buffer.from(e.v, 'base64')));
|
|
133
353
|
this.eventVersions.set(k, log.map((e) => e.sv));
|
|
@@ -151,11 +371,14 @@ export class DevDatabase {
|
|
|
151
371
|
}
|
|
152
372
|
clear() {
|
|
153
373
|
this.store.clear();
|
|
374
|
+
this.recordIdem.clear();
|
|
375
|
+
this.uniqueIdem.clear();
|
|
154
376
|
this.versions.clear();
|
|
155
377
|
this.views.clear();
|
|
156
378
|
this.members.clear();
|
|
157
379
|
this.memberVersions.clear();
|
|
158
380
|
this.counters.clear();
|
|
381
|
+
this.counterIdem.clear();
|
|
159
382
|
this.eventVersions.clear();
|
|
160
383
|
this.events.clear();
|
|
161
384
|
this.eventDedup.clear();
|
|
@@ -184,8 +407,31 @@ export class DevDatabase {
|
|
|
184
407
|
if (nameLen < 0 || nameLen > MAX_NAME)
|
|
185
408
|
throw new Error('data: collection name too long');
|
|
186
409
|
const name = readCopy(ref, namePtr, nameLen).toString('utf8');
|
|
410
|
+
let coll;
|
|
411
|
+
switch (this.catalog.kind) {
|
|
412
|
+
case 'present': {
|
|
413
|
+
const found = this.catalog.collections.get(name);
|
|
414
|
+
if (found === undefined)
|
|
415
|
+
return SCHEMA_UNAVAILABLE;
|
|
416
|
+
coll = { ...found };
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
case 'malformed':
|
|
420
|
+
return SCHEMA_UNAVAILABLE;
|
|
421
|
+
case 'no-section':
|
|
422
|
+
coll = {
|
|
423
|
+
name,
|
|
424
|
+
family: CollectionFamily.Record,
|
|
425
|
+
schemaVersion: 0,
|
|
426
|
+
replication: 0,
|
|
427
|
+
placement: 0,
|
|
428
|
+
fillMaxWaitMs: DEFAULT_FILL_WAIT_MS,
|
|
429
|
+
fillAllowStale: true,
|
|
430
|
+
};
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
187
433
|
const handle = db.handles.length;
|
|
188
|
-
db.handles.push(
|
|
434
|
+
db.handles.push(coll);
|
|
189
435
|
const m = mem(ref);
|
|
190
436
|
if (outHandlePtr < 0 || outHandlePtr + 4 > m.length)
|
|
191
437
|
throw new Error('data: resolve out-handle out of bounds');
|
|
@@ -193,12 +439,12 @@ export class DevDatabase {
|
|
|
193
439
|
return 0;
|
|
194
440
|
}
|
|
195
441
|
get(ref, db, handle, keyPtr, keyLen) {
|
|
196
|
-
const coll =
|
|
197
|
-
if (coll ===
|
|
198
|
-
return
|
|
442
|
+
const coll = collForOp(db, handle, DbOp.Get, CollectionFamily.Record);
|
|
443
|
+
if (typeof coll === 'number')
|
|
444
|
+
return coll;
|
|
199
445
|
if (keyLen > MAX_KEY)
|
|
200
446
|
throw new Error('data: key too long');
|
|
201
|
-
const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
|
|
447
|
+
const sk = storeKey(coll.name, readKey(ref, keyPtr, keyLen));
|
|
202
448
|
const v = this.store.get(sk);
|
|
203
449
|
if (v === undefined)
|
|
204
450
|
return ABSENT;
|
|
@@ -207,11 +453,12 @@ export class DevDatabase {
|
|
|
207
453
|
return v.length;
|
|
208
454
|
}
|
|
209
455
|
getMany(ref, db, handle, keysPtr, keysLen) {
|
|
210
|
-
const coll =
|
|
211
|
-
if (coll ===
|
|
212
|
-
return
|
|
456
|
+
const coll = collForOp(db, handle, DbOp.GetMany, CollectionFamily.Record, CollectionFamily.View);
|
|
457
|
+
if (typeof coll === 'number')
|
|
458
|
+
return coll;
|
|
213
459
|
if (keysLen > MAX_VALUE)
|
|
214
460
|
throw new Error('data: keys blob too large');
|
|
461
|
+
const table = coll.family === CollectionFamily.View ? this.views : this.store;
|
|
215
462
|
const r = new DataReader(readCopy(ref, keysPtr, keysLen));
|
|
216
463
|
const count = r.readU32();
|
|
217
464
|
if (count > 1024)
|
|
@@ -222,81 +469,122 @@ export class DevDatabase {
|
|
|
222
469
|
const key = r.readBytes();
|
|
223
470
|
if (key.length > MAX_KEY)
|
|
224
471
|
throw new Error('data: key too long');
|
|
225
|
-
const sk = storeKey(coll, Buffer.from(key));
|
|
226
|
-
const v =
|
|
472
|
+
const sk = storeKey(coll.name, Buffer.from(key));
|
|
473
|
+
const v = table.get(sk);
|
|
227
474
|
if (v === undefined) {
|
|
228
475
|
w.writeU8(0);
|
|
229
476
|
}
|
|
230
477
|
else {
|
|
231
|
-
w.writeU8(1)
|
|
478
|
+
w.writeU8(1)
|
|
479
|
+
.writeU32(this.versions.get(sk) ?? 0)
|
|
480
|
+
.writeBytes(v);
|
|
232
481
|
}
|
|
233
482
|
}
|
|
234
483
|
db.lastResult = Buffer.from(w.toBytes());
|
|
235
484
|
return db.lastResult.length;
|
|
236
485
|
}
|
|
237
486
|
exists(ref, db, handle, keyPtr, keyLen) {
|
|
238
|
-
const coll =
|
|
239
|
-
if (coll ===
|
|
240
|
-
return
|
|
241
|
-
return this.store.has(storeKey(coll, readKey(ref, keyPtr, keyLen))) ? 1 : 0;
|
|
242
|
-
}
|
|
243
|
-
create(ref, db, handle, keyPtr, keyLen, valPtr, valLen) {
|
|
244
|
-
const coll =
|
|
245
|
-
if (coll ===
|
|
246
|
-
return
|
|
487
|
+
const coll = collForOp(db, handle, DbOp.Exists, CollectionFamily.Record);
|
|
488
|
+
if (typeof coll === 'number')
|
|
489
|
+
return coll;
|
|
490
|
+
return this.store.has(storeKey(coll.name, readKey(ref, keyPtr, keyLen))) ? 1 : 0;
|
|
491
|
+
}
|
|
492
|
+
create(ref, db, handle, keyPtr, keyLen, valPtr, valLen, idemPtr) {
|
|
493
|
+
const coll = collForOp(db, handle, DbOp.Create, CollectionFamily.Record);
|
|
494
|
+
if (typeof coll === 'number')
|
|
495
|
+
return coll;
|
|
247
496
|
if (keyLen > MAX_KEY || valLen > MAX_VALUE)
|
|
248
497
|
throw new Error('data: key/value too large');
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
this.
|
|
254
|
-
|
|
498
|
+
const key = readKey(ref, keyPtr, keyLen);
|
|
499
|
+
const value = readCopy(ref, valPtr, valLen);
|
|
500
|
+
const idem = readIdem(ref, idemPtr);
|
|
501
|
+
const requestHash = recordRequestHash(RECORD_OP_CREATE, key, value);
|
|
502
|
+
const start = this.recordIdemStart(coll, key, 'C', idem, requestHash);
|
|
503
|
+
if (!start.fresh)
|
|
504
|
+
return start.outcome ? this.replayRecordOutcome(db, start.outcome) : start.status;
|
|
505
|
+
const sk = storeKey(coll.name, key);
|
|
506
|
+
const outcome = this.store.has(sk)
|
|
507
|
+
? { kind: 'already_exists' }
|
|
508
|
+
: { kind: 'unit' };
|
|
509
|
+
if (outcome.kind === 'unit') {
|
|
510
|
+
this.store.set(sk, value);
|
|
511
|
+
this.stampVersion(coll, sk);
|
|
512
|
+
}
|
|
513
|
+
this.recordIdemFinish(coll, key, 'C', idem, requestHash, outcome);
|
|
514
|
+
return this.replayRecordOutcome(db, outcome);
|
|
255
515
|
}
|
|
256
|
-
patch(ref, db, handle, keyPtr, keyLen, patchPtr, patchLen) {
|
|
257
|
-
const coll =
|
|
258
|
-
if (coll ===
|
|
259
|
-
return
|
|
516
|
+
patch(ref, db, handle, keyPtr, keyLen, patchPtr, patchLen, idemPtr) {
|
|
517
|
+
const coll = collForOp(db, handle, DbOp.Patch, CollectionFamily.Record);
|
|
518
|
+
if (typeof coll === 'number')
|
|
519
|
+
return coll;
|
|
260
520
|
if (keyLen > MAX_KEY || patchLen > MAX_VALUE)
|
|
261
521
|
throw new Error('data: key/patch too large');
|
|
262
|
-
const
|
|
263
|
-
if (!this.store.has(sk))
|
|
264
|
-
return ABSENT;
|
|
522
|
+
const key = readKey(ref, keyPtr, keyLen);
|
|
265
523
|
const v = readCopy(ref, patchPtr, patchLen);
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
524
|
+
const idem = readIdem(ref, idemPtr);
|
|
525
|
+
const requestHash = recordRequestHash(RECORD_OP_PATCH, key, v);
|
|
526
|
+
const start = this.recordIdemStart(coll, key, 'P', idem, requestHash);
|
|
527
|
+
if (!start.fresh)
|
|
528
|
+
return start.outcome ? this.replayRecordOutcome(db, start.outcome) : start.status;
|
|
529
|
+
const sk = storeKey(coll.name, key);
|
|
530
|
+
const outcome = this.store.has(sk)
|
|
531
|
+
? { kind: 'value', value: v, schemaVersion: -1 }
|
|
532
|
+
: { kind: 'not_found' };
|
|
533
|
+
if (outcome.kind === 'value') {
|
|
534
|
+
this.store.set(sk, v);
|
|
535
|
+
this.stampVersion(coll, sk);
|
|
536
|
+
}
|
|
537
|
+
this.recordIdemFinish(coll, key, 'P', idem, requestHash, outcome);
|
|
538
|
+
return this.replayRecordOutcome(db, outcome);
|
|
271
539
|
}
|
|
272
|
-
delete(ref, db, handle, keyPtr, keyLen) {
|
|
273
|
-
const coll =
|
|
274
|
-
if (coll ===
|
|
275
|
-
return
|
|
276
|
-
const
|
|
540
|
+
delete(ref, db, handle, keyPtr, keyLen, idemPtr) {
|
|
541
|
+
const coll = collForOp(db, handle, DbOp.Delete, CollectionFamily.Record);
|
|
542
|
+
if (typeof coll === 'number')
|
|
543
|
+
return coll;
|
|
544
|
+
const key = readKey(ref, keyPtr, keyLen);
|
|
545
|
+
const idem = readIdem(ref, idemPtr);
|
|
546
|
+
const requestHash = recordRequestHash(RECORD_OP_DELETE, key, Buffer.alloc(0));
|
|
547
|
+
const start = this.recordIdemStart(coll, key, 'D', idem, requestHash);
|
|
548
|
+
if (!start.fresh)
|
|
549
|
+
return start.outcome ? this.replayRecordOutcome(db, start.outcome) : start.status;
|
|
550
|
+
const sk = storeKey(coll.name, key);
|
|
277
551
|
this.store.delete(sk);
|
|
278
552
|
this.versions.delete(sk);
|
|
279
|
-
|
|
553
|
+
const outcome = { kind: 'unit' };
|
|
554
|
+
this.recordIdemFinish(coll, key, 'D', idem, requestHash, outcome);
|
|
555
|
+
return this.replayRecordOutcome(db, outcome);
|
|
280
556
|
}
|
|
281
|
-
getDelete(ref, db, handle, keyPtr, keyLen) {
|
|
282
|
-
const coll =
|
|
283
|
-
if (coll ===
|
|
284
|
-
return
|
|
285
|
-
const
|
|
557
|
+
getDelete(ref, db, handle, keyPtr, keyLen, idemPtr) {
|
|
558
|
+
const coll = collForOp(db, handle, DbOp.GetDelete, CollectionFamily.Record);
|
|
559
|
+
if (typeof coll === 'number')
|
|
560
|
+
return coll;
|
|
561
|
+
const key = readKey(ref, keyPtr, keyLen);
|
|
562
|
+
const idem = readIdem(ref, idemPtr);
|
|
563
|
+
const requestHash = recordRequestHash(RECORD_OP_GET_DELETE, key, Buffer.alloc(0));
|
|
564
|
+
const start = this.recordIdemStart(coll, key, 'G', idem, requestHash);
|
|
565
|
+
if (!start.fresh)
|
|
566
|
+
return start.outcome ? this.replayRecordOutcome(db, start.outcome) : start.status;
|
|
567
|
+
const sk = storeKey(coll.name, key);
|
|
286
568
|
const v = this.store.get(sk);
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
569
|
+
const outcome = v === undefined
|
|
570
|
+
? { kind: 'absent' }
|
|
571
|
+
: {
|
|
572
|
+
kind: 'value',
|
|
573
|
+
value: v,
|
|
574
|
+
schemaVersion: this.versions.get(sk) ?? 0,
|
|
575
|
+
};
|
|
576
|
+
if (outcome.kind === 'value') {
|
|
577
|
+
this.store.delete(sk);
|
|
578
|
+
this.versions.delete(sk);
|
|
579
|
+
}
|
|
580
|
+
this.recordIdemFinish(coll, key, 'G', idem, requestHash, outcome);
|
|
581
|
+
return this.replayRecordOutcome(db, outcome);
|
|
294
582
|
}
|
|
295
583
|
uniqueLookup(ref, db, handle, keyPtr, keyLen) {
|
|
296
|
-
const coll =
|
|
297
|
-
if (coll ===
|
|
298
|
-
return
|
|
299
|
-
const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
|
|
584
|
+
const coll = collForOp(db, handle, DbOp.UniqueLookup, CollectionFamily.Unique);
|
|
585
|
+
if (typeof coll === 'number')
|
|
586
|
+
return coll;
|
|
587
|
+
const sk = storeKey(coll.name, readKey(ref, keyPtr, keyLen));
|
|
300
588
|
const v = this.store.get(sk);
|
|
301
589
|
if (v === undefined)
|
|
302
590
|
return ABSENT;
|
|
@@ -304,55 +592,59 @@ export class DevDatabase {
|
|
|
304
592
|
db.lastResultVersion = this.versions.get(sk) ?? 0;
|
|
305
593
|
return v.length;
|
|
306
594
|
}
|
|
307
|
-
uniqueClaim(ref, db, handle, keyPtr, keyLen, valPtr, valLen) {
|
|
308
|
-
const coll =
|
|
309
|
-
if (coll ===
|
|
310
|
-
return
|
|
595
|
+
uniqueClaim(ref, db, handle, keyPtr, keyLen, valPtr, valLen, idemPtr) {
|
|
596
|
+
const coll = collForOp(db, handle, DbOp.UniqueClaim, CollectionFamily.Unique);
|
|
597
|
+
if (typeof coll === 'number')
|
|
598
|
+
return coll;
|
|
311
599
|
if (keyLen > MAX_KEY || valLen > MAX_VALUE)
|
|
312
600
|
throw new Error('data: key/value too large');
|
|
313
|
-
const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
|
|
601
|
+
const sk = storeKey(coll.name, readKey(ref, keyPtr, keyLen));
|
|
314
602
|
const owner = readCopy(ref, valPtr, valLen);
|
|
603
|
+
const idem = readIdem(ref, idemPtr)?.toString('hex') ?? '';
|
|
315
604
|
const existing = this.store.get(sk);
|
|
316
605
|
if (existing === undefined) {
|
|
317
606
|
this.store.set(sk, owner);
|
|
607
|
+
this.uniqueIdem.set(sk, idem);
|
|
318
608
|
this.stampVersion(coll, sk);
|
|
319
609
|
return 0;
|
|
320
610
|
}
|
|
321
|
-
if (existing.equals(owner))
|
|
322
|
-
return 2;
|
|
611
|
+
if (existing.equals(owner)) {
|
|
612
|
+
return (this.uniqueIdem.get(sk) ?? '') === idem ? 2 : CONFLICT;
|
|
613
|
+
}
|
|
323
614
|
db.lastResult = existing;
|
|
324
615
|
return 1;
|
|
325
616
|
}
|
|
326
617
|
uniqueRelease(ref, db, handle, keyPtr, keyLen, valPtr, valLen) {
|
|
327
|
-
const coll =
|
|
328
|
-
if (coll ===
|
|
329
|
-
return
|
|
330
|
-
const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
|
|
618
|
+
const coll = collForOp(db, handle, DbOp.UniqueRelease, CollectionFamily.Unique);
|
|
619
|
+
if (typeof coll === 'number')
|
|
620
|
+
return coll;
|
|
621
|
+
const sk = storeKey(coll.name, readKey(ref, keyPtr, keyLen));
|
|
331
622
|
const existing = this.store.get(sk);
|
|
332
623
|
if (existing === undefined)
|
|
333
624
|
return 0;
|
|
334
625
|
if (!existing.equals(readCopy(ref, valPtr, valLen)))
|
|
335
626
|
return CONFLICT;
|
|
336
627
|
this.store.delete(sk);
|
|
628
|
+
this.uniqueIdem.delete(sk);
|
|
337
629
|
this.versions.delete(sk);
|
|
338
630
|
return 0;
|
|
339
631
|
}
|
|
340
632
|
membershipContains(ref, db, handle, setPtr, setLen, memberPtr, memberLen) {
|
|
341
|
-
const coll =
|
|
342
|
-
if (coll ===
|
|
343
|
-
return
|
|
344
|
-
const set = this.members.get(storeKey(coll, readKey(ref, setPtr, setLen)));
|
|
633
|
+
const coll = collForOp(db, handle, DbOp.MembershipContains, CollectionFamily.Membership);
|
|
634
|
+
if (typeof coll === 'number')
|
|
635
|
+
return coll;
|
|
636
|
+
const set = this.members.get(storeKey(coll.name, readKey(ref, setPtr, setLen)));
|
|
345
637
|
if (set === undefined)
|
|
346
638
|
return 0;
|
|
347
639
|
return set.has(readCopy(ref, memberPtr, memberLen).toString('latin1')) ? 1 : 0;
|
|
348
640
|
}
|
|
349
641
|
membershipAdd(ref, db, handle, setPtr, setLen, memberPtr, memberLen) {
|
|
350
|
-
const coll =
|
|
351
|
-
if (coll ===
|
|
352
|
-
return
|
|
642
|
+
const coll = collForOp(db, handle, DbOp.MembershipAdd, CollectionFamily.Membership);
|
|
643
|
+
if (typeof coll === 'number')
|
|
644
|
+
return coll;
|
|
353
645
|
if (setLen > MAX_KEY || memberLen > MAX_VALUE)
|
|
354
646
|
throw new Error('data: set/member too large');
|
|
355
|
-
const sk = storeKey(coll, readKey(ref, setPtr, setLen));
|
|
647
|
+
const sk = storeKey(coll.name, readKey(ref, setPtr, setLen));
|
|
356
648
|
const member = readCopy(ref, memberPtr, memberLen);
|
|
357
649
|
let set = this.members.get(sk);
|
|
358
650
|
if (set === undefined) {
|
|
@@ -366,24 +658,24 @@ export class DevDatabase {
|
|
|
366
658
|
mv = new Map();
|
|
367
659
|
this.memberVersions.set(sk, mv);
|
|
368
660
|
}
|
|
369
|
-
mv.set(ml, this.
|
|
661
|
+
mv.set(ml, this.currentSchemaVersion(coll));
|
|
370
662
|
return 0;
|
|
371
663
|
}
|
|
372
664
|
membershipRemove(ref, db, handle, setPtr, setLen, memberPtr, memberLen) {
|
|
373
|
-
const coll =
|
|
374
|
-
if (coll ===
|
|
375
|
-
return
|
|
376
|
-
const sk = storeKey(coll, readKey(ref, setPtr, setLen));
|
|
665
|
+
const coll = collForOp(db, handle, DbOp.MembershipRemove, CollectionFamily.Membership);
|
|
666
|
+
if (typeof coll === 'number')
|
|
667
|
+
return coll;
|
|
668
|
+
const sk = storeKey(coll.name, readKey(ref, setPtr, setLen));
|
|
377
669
|
const ml = readCopy(ref, memberPtr, memberLen).toString('latin1');
|
|
378
670
|
this.members.get(sk)?.delete(ml);
|
|
379
671
|
this.memberVersions.get(sk)?.delete(ml);
|
|
380
672
|
return 0;
|
|
381
673
|
}
|
|
382
674
|
membershipList(ref, db, handle, setPtr, setLen, limit) {
|
|
383
|
-
const coll =
|
|
384
|
-
if (coll ===
|
|
385
|
-
return
|
|
386
|
-
const sk = storeKey(coll, readKey(ref, setPtr, setLen));
|
|
675
|
+
const coll = collForOp(db, handle, DbOp.MembershipList, CollectionFamily.Membership);
|
|
676
|
+
if (typeof coll === 'number')
|
|
677
|
+
return coll;
|
|
678
|
+
const sk = storeKey(coll.name, readKey(ref, setPtr, setLen));
|
|
387
679
|
const set = this.members.get(sk);
|
|
388
680
|
const mv = this.memberVersions.get(sk);
|
|
389
681
|
const n = Math.max(0, Math.min(limit, 0xffff));
|
|
@@ -397,10 +689,10 @@ export class DevDatabase {
|
|
|
397
689
|
return db.lastResult.length;
|
|
398
690
|
}
|
|
399
691
|
viewGet(ref, db, handle, keyPtr, keyLen) {
|
|
400
|
-
const coll =
|
|
401
|
-
if (coll ===
|
|
402
|
-
return
|
|
403
|
-
const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
|
|
692
|
+
const coll = collForOp(db, handle, DbOp.ViewGet, CollectionFamily.View);
|
|
693
|
+
if (typeof coll === 'number')
|
|
694
|
+
return coll;
|
|
695
|
+
const sk = storeKey(coll.name, readKey(ref, keyPtr, keyLen));
|
|
404
696
|
const v = this.views.get(sk);
|
|
405
697
|
if (v === undefined)
|
|
406
698
|
return ABSENT;
|
|
@@ -409,44 +701,67 @@ export class DevDatabase {
|
|
|
409
701
|
return v.length;
|
|
410
702
|
}
|
|
411
703
|
viewPublish(ref, db, handle, keyPtr, keyLen, valPtr, valLen) {
|
|
412
|
-
const coll =
|
|
413
|
-
if (coll ===
|
|
414
|
-
return
|
|
704
|
+
const coll = collForOp(db, handle, DbOp.ViewPublish, CollectionFamily.View);
|
|
705
|
+
if (typeof coll === 'number')
|
|
706
|
+
return coll;
|
|
415
707
|
if (keyLen > MAX_KEY || valLen > MAX_VALUE)
|
|
416
708
|
throw new Error('data: key/view too large');
|
|
417
|
-
const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
|
|
709
|
+
const sk = storeKey(coll.name, readKey(ref, keyPtr, keyLen));
|
|
418
710
|
this.views.set(sk, readCopy(ref, valPtr, valLen));
|
|
419
711
|
this.stampVersion(coll, sk);
|
|
420
712
|
return 0;
|
|
421
713
|
}
|
|
422
714
|
counterGet(ref, db, handle, keyPtr, keyLen) {
|
|
423
|
-
const coll =
|
|
424
|
-
if (coll ===
|
|
425
|
-
return
|
|
426
|
-
const sum = this.counters.get(storeKey(coll, readKey(ref, keyPtr, keyLen))) ?? 0n;
|
|
715
|
+
const coll = collForOp(db, handle, DbOp.CounterGet, CollectionFamily.Counter);
|
|
716
|
+
if (typeof coll === 'number')
|
|
717
|
+
return coll;
|
|
718
|
+
const sum = this.counters.get(storeKey(coll.name, readKey(ref, keyPtr, keyLen))) ?? 0n;
|
|
427
719
|
const out = Buffer.alloc(8);
|
|
428
720
|
out.writeBigInt64LE(sum);
|
|
429
721
|
db.lastResult = out;
|
|
430
722
|
return out.length;
|
|
431
723
|
}
|
|
432
|
-
counterAdd(ref, db, handle, keyPtr, keyLen, delta) {
|
|
433
|
-
const coll =
|
|
434
|
-
if (coll ===
|
|
435
|
-
return
|
|
436
|
-
const
|
|
437
|
-
|
|
724
|
+
counterAdd(ref, db, handle, keyPtr, keyLen, delta, idemPtr) {
|
|
725
|
+
const coll = collForOp(db, handle, DbOp.CounterAdd, CollectionFamily.Counter);
|
|
726
|
+
if (typeof coll === 'number')
|
|
727
|
+
return coll;
|
|
728
|
+
const key = readKey(ref, keyPtr, keyLen);
|
|
729
|
+
const idem = readIdem(ref, idemPtr);
|
|
730
|
+
const d = BigInt(delta);
|
|
731
|
+
if (idem !== null) {
|
|
732
|
+
const ik = idemKey(coll, key, 'A', idem);
|
|
733
|
+
const seen = this.counterIdem.get(ik);
|
|
734
|
+
if (seen !== undefined)
|
|
735
|
+
return seen === d ? 0 : CONFLICT;
|
|
736
|
+
this.counterIdem.set(ik, d);
|
|
737
|
+
}
|
|
738
|
+
const sk = storeKey(coll.name, key);
|
|
739
|
+
this.counters.set(sk, satI64((this.counters.get(sk) ?? 0n) + d));
|
|
438
740
|
return 0;
|
|
439
741
|
}
|
|
440
|
-
append(ref, db, handle, keyPtr, keyLen, evPtr, evLen) {
|
|
441
|
-
const coll =
|
|
442
|
-
if (coll ===
|
|
443
|
-
return
|
|
742
|
+
append(ref, db, handle, keyPtr, keyLen, evPtr, evLen, idemPtr) {
|
|
743
|
+
const coll = collForOp(db, handle, DbOp.Append, CollectionFamily.Events);
|
|
744
|
+
if (typeof coll === 'number')
|
|
745
|
+
return coll;
|
|
444
746
|
if (keyLen > MAX_KEY || evLen > MAX_VALUE)
|
|
445
747
|
throw new Error('data: key/event too large');
|
|
446
|
-
const
|
|
748
|
+
const key = readKey(ref, keyPtr, keyLen);
|
|
749
|
+
const sk = storeKey(coll.name, key);
|
|
750
|
+
const idem = readIdem(ref, idemPtr);
|
|
751
|
+
if (idem !== null) {
|
|
752
|
+
let seen = this.eventDedup.get(sk);
|
|
753
|
+
if (seen === undefined) {
|
|
754
|
+
seen = new Set();
|
|
755
|
+
this.eventDedup.set(sk, seen);
|
|
756
|
+
}
|
|
757
|
+
const eventId = idem.toString('latin1');
|
|
758
|
+
if (seen.has(eventId))
|
|
759
|
+
return 0;
|
|
760
|
+
seen.add(eventId);
|
|
761
|
+
}
|
|
447
762
|
const log = this.events.get(sk);
|
|
448
763
|
const ev = readCopy(ref, evPtr, evLen);
|
|
449
|
-
const sv = this.
|
|
764
|
+
const sv = this.currentSchemaVersion(coll);
|
|
450
765
|
if (log === undefined) {
|
|
451
766
|
this.events.set(sk, [ev]);
|
|
452
767
|
this.eventVersions.set(sk, [sv]);
|
|
@@ -458,12 +773,12 @@ export class DevDatabase {
|
|
|
458
773
|
return 0;
|
|
459
774
|
}
|
|
460
775
|
appendOnce(ref, db, handle, keyPtr, keyLen, evidPtr, evidLen, evPtr, evLen) {
|
|
461
|
-
const coll =
|
|
462
|
-
if (coll ===
|
|
463
|
-
return
|
|
776
|
+
const coll = collForOp(db, handle, DbOp.AppendOnce, CollectionFamily.Events);
|
|
777
|
+
if (typeof coll === 'number')
|
|
778
|
+
return coll;
|
|
464
779
|
if (keyLen > MAX_KEY || evLen > MAX_VALUE)
|
|
465
780
|
throw new Error('data: key/event too large');
|
|
466
|
-
const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
|
|
781
|
+
const sk = storeKey(coll.name, readKey(ref, keyPtr, keyLen));
|
|
467
782
|
const evid = readCopy(ref, evidPtr, evidLen).toString('latin1');
|
|
468
783
|
let seen = this.eventDedup.get(sk);
|
|
469
784
|
if (seen === undefined) {
|
|
@@ -473,7 +788,7 @@ export class DevDatabase {
|
|
|
473
788
|
if (seen.has(evid))
|
|
474
789
|
return 0;
|
|
475
790
|
const ev = readCopy(ref, evPtr, evLen);
|
|
476
|
-
const sv = this.
|
|
791
|
+
const sv = this.currentSchemaVersion(coll);
|
|
477
792
|
const log = this.events.get(sk);
|
|
478
793
|
if (log === undefined) {
|
|
479
794
|
this.events.set(sk, [ev]);
|
|
@@ -486,24 +801,35 @@ export class DevDatabase {
|
|
|
486
801
|
seen.add(evid);
|
|
487
802
|
return 1;
|
|
488
803
|
}
|
|
489
|
-
enqueue(ref, db, handle, keyPtr, keyLen, valPtr, valLen) {
|
|
490
|
-
const coll =
|
|
491
|
-
if (coll ===
|
|
492
|
-
return
|
|
804
|
+
enqueue(ref, db, handle, keyPtr, keyLen, valPtr, valLen, idemPtr) {
|
|
805
|
+
const coll = collForOp(db, handle, DbOp.Enqueue, CollectionFamily.Record);
|
|
806
|
+
if (typeof coll === 'number')
|
|
807
|
+
return coll;
|
|
493
808
|
if (keyLen > MAX_KEY || valLen > MAX_VALUE)
|
|
494
809
|
throw new Error('data: key/value too large');
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
this.
|
|
500
|
-
|
|
810
|
+
const key = readKey(ref, keyPtr, keyLen);
|
|
811
|
+
const value = readCopy(ref, valPtr, valLen);
|
|
812
|
+
const idem = readIdem(ref, idemPtr);
|
|
813
|
+
const requestHash = recordRequestHash(RECORD_OP_ENQUEUE, key, value);
|
|
814
|
+
const start = this.recordIdemStart(coll, key, 'E', idem, requestHash);
|
|
815
|
+
if (!start.fresh)
|
|
816
|
+
return start.outcome ? this.replayRecordOutcome(db, start.outcome) : start.status;
|
|
817
|
+
const sk = storeKey(coll.name, key);
|
|
818
|
+
const outcome = this.store.has(sk)
|
|
819
|
+
? { kind: 'unit' }
|
|
820
|
+
: { kind: 'not_found' };
|
|
821
|
+
if (outcome.kind === 'unit') {
|
|
822
|
+
this.store.set(sk, value);
|
|
823
|
+
this.stampVersion(coll, sk);
|
|
824
|
+
}
|
|
825
|
+
this.recordIdemFinish(coll, key, 'E', idem, requestHash, outcome);
|
|
826
|
+
return this.replayRecordOutcome(db, outcome);
|
|
501
827
|
}
|
|
502
828
|
latest(ref, db, handle, keyPtr, keyLen, limit) {
|
|
503
|
-
const coll =
|
|
504
|
-
if (coll ===
|
|
505
|
-
return
|
|
506
|
-
const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
|
|
829
|
+
const coll = collForOp(db, handle, DbOp.Latest, CollectionFamily.Events);
|
|
830
|
+
if (typeof coll === 'number')
|
|
831
|
+
return coll;
|
|
832
|
+
const sk = storeKey(coll.name, readKey(ref, keyPtr, keyLen));
|
|
507
833
|
const log = this.events.get(sk) ?? [];
|
|
508
834
|
const vers = this.eventVersions.get(sk) ?? [];
|
|
509
835
|
const n = Math.max(0, Math.min(limit, 0xffff));
|
|
@@ -519,19 +845,19 @@ export class DevDatabase {
|
|
|
519
845
|
return db.lastResult.length;
|
|
520
846
|
}
|
|
521
847
|
capacitySetTotal(ref, db, handle, keyPtr, keyLen, total) {
|
|
522
|
-
const coll =
|
|
523
|
-
if (coll ===
|
|
524
|
-
return
|
|
525
|
-
const l = this.capLedger(storeKey(coll, readKey(ref, keyPtr, keyLen)));
|
|
848
|
+
const coll = collForOp(db, handle, DbOp.CapacitySetTotal, CollectionFamily.Capacity);
|
|
849
|
+
if (typeof coll === 'number')
|
|
850
|
+
return coll;
|
|
851
|
+
const l = this.capLedger(storeKey(coll.name, readKey(ref, keyPtr, keyLen)));
|
|
526
852
|
const t = BigInt(total);
|
|
527
853
|
l.total = satI64(t < 0n ? 0n : t);
|
|
528
854
|
return 0;
|
|
529
855
|
}
|
|
530
856
|
capacityAvailable(ref, db, handle, keyPtr, keyLen) {
|
|
531
|
-
const coll =
|
|
532
|
-
if (coll ===
|
|
533
|
-
return
|
|
534
|
-
const l = this.capLedger(storeKey(coll, readKey(ref, keyPtr, keyLen)));
|
|
857
|
+
const coll = collForOp(db, handle, DbOp.CapacityAvailable, CollectionFamily.Capacity);
|
|
858
|
+
if (typeof coll === 'number')
|
|
859
|
+
return coll;
|
|
860
|
+
const l = this.capLedger(storeKey(coll.name, readKey(ref, keyPtr, keyLen)));
|
|
535
861
|
this.capPrune(l, Date.now());
|
|
536
862
|
const avail = l.total - this.capReserved(l);
|
|
537
863
|
const out = Buffer.alloc(8);
|
|
@@ -539,20 +865,34 @@ export class DevDatabase {
|
|
|
539
865
|
db.lastResult = out;
|
|
540
866
|
return out.length;
|
|
541
867
|
}
|
|
542
|
-
capacityReserve(ref, db, handle, keyPtr, keyLen, amount, ttlMs) {
|
|
543
|
-
const coll =
|
|
544
|
-
if (coll ===
|
|
545
|
-
return
|
|
868
|
+
capacityReserve(ref, db, handle, keyPtr, keyLen, amount, ttlMs, idemPtr) {
|
|
869
|
+
const coll = collForOp(db, handle, DbOp.CapacityReserve, CollectionFamily.Capacity);
|
|
870
|
+
if (typeof coll === 'number')
|
|
871
|
+
return coll;
|
|
546
872
|
const want = BigInt(amount);
|
|
547
873
|
if (want <= 0n)
|
|
548
874
|
return CODEC_ERR;
|
|
549
|
-
const
|
|
875
|
+
const key = readKey(ref, keyPtr, keyLen);
|
|
876
|
+
const idem = readIdem(ref, idemPtr);
|
|
877
|
+
const ttl = Math.min(Math.max(0, Number(ttlMs)), MAX_RESERVATION_TTL_MS);
|
|
878
|
+
const requestedId = idem === null ? null : reservationIdFromIdem(coll, key, idem);
|
|
879
|
+
const l = this.capLedger(storeKey(coll.name, key));
|
|
550
880
|
const now = Date.now();
|
|
551
881
|
this.capPrune(l, now);
|
|
882
|
+
if (requestedId !== null) {
|
|
883
|
+
const existing = l.reservations.get(requestedId);
|
|
884
|
+
if (existing !== undefined) {
|
|
885
|
+
if (existing.amount !== want)
|
|
886
|
+
return CONFLICT;
|
|
887
|
+
const out = Buffer.alloc(8);
|
|
888
|
+
out.writeBigUInt64LE(requestedId);
|
|
889
|
+
db.lastResult = out;
|
|
890
|
+
return out.length;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
552
893
|
if (l.total - this.capReserved(l) < want || l.reservations.size >= MAX_RESERVATIONS)
|
|
553
894
|
return ABSENT;
|
|
554
|
-
const
|
|
555
|
-
const id = l.nextId++;
|
|
895
|
+
const id = requestedId ?? l.nextId++;
|
|
556
896
|
l.reservations.set(id, { amount: want, expiresMs: now + ttl, confirmed: false });
|
|
557
897
|
const out = Buffer.alloc(8);
|
|
558
898
|
out.writeBigUInt64LE(id);
|
|
@@ -560,10 +900,10 @@ export class DevDatabase {
|
|
|
560
900
|
return out.length;
|
|
561
901
|
}
|
|
562
902
|
capacityConfirm(ref, db, handle, keyPtr, keyLen, reservationId) {
|
|
563
|
-
const coll =
|
|
564
|
-
if (coll ===
|
|
565
|
-
return
|
|
566
|
-
const l = this.capLedger(storeKey(coll, readKey(ref, keyPtr, keyLen)));
|
|
903
|
+
const coll = collForOp(db, handle, DbOp.CapacityConfirm, CollectionFamily.Capacity);
|
|
904
|
+
if (typeof coll === 'number')
|
|
905
|
+
return coll;
|
|
906
|
+
const l = this.capLedger(storeKey(coll.name, readKey(ref, keyPtr, keyLen)));
|
|
567
907
|
this.capPrune(l, Date.now());
|
|
568
908
|
const r = l.reservations.get(BigInt(reservationId));
|
|
569
909
|
if (r === undefined)
|
|
@@ -572,10 +912,10 @@ export class DevDatabase {
|
|
|
572
912
|
return 1;
|
|
573
913
|
}
|
|
574
914
|
capacityCancel(ref, db, handle, keyPtr, keyLen, reservationId) {
|
|
575
|
-
const coll =
|
|
576
|
-
if (coll ===
|
|
577
|
-
return
|
|
578
|
-
const l = this.capLedger(storeKey(coll, readKey(ref, keyPtr, keyLen)));
|
|
915
|
+
const coll = collForOp(db, handle, DbOp.CapacityCancel, CollectionFamily.Capacity);
|
|
916
|
+
if (typeof coll === 'number')
|
|
917
|
+
return coll;
|
|
918
|
+
const l = this.capLedger(storeKey(coll.name, readKey(ref, keyPtr, keyLen)));
|
|
579
919
|
this.capPrune(l, Date.now());
|
|
580
920
|
const id = BigInt(reservationId);
|
|
581
921
|
const r = l.reservations.get(id);
|
|
@@ -602,11 +942,41 @@ export class DevDatabase {
|
|
|
602
942
|
}
|
|
603
943
|
resetForTests() {
|
|
604
944
|
this.clear();
|
|
605
|
-
this.catalog =
|
|
945
|
+
this.catalog = { kind: 'no-section' };
|
|
606
946
|
this.persistPath = null;
|
|
607
947
|
}
|
|
608
948
|
setCatalogForTests(entries) {
|
|
609
|
-
|
|
949
|
+
const collections = new Map();
|
|
950
|
+
for (const [name, entry] of Object.entries(entries)) {
|
|
951
|
+
const schemaVersion = typeof entry === 'number' ? entry : (entry.schemaVersion ?? 0);
|
|
952
|
+
const family = typeof entry === 'number'
|
|
953
|
+
? CollectionFamily.Record
|
|
954
|
+
: (entry.family ?? CollectionFamily.Record);
|
|
955
|
+
const replication = typeof entry === 'number' ? 0 : (entry.replication ?? 0);
|
|
956
|
+
const placement = typeof entry === 'number' ? 0 : (entry.placement ?? 0);
|
|
957
|
+
const fillMaxWaitMs = typeof entry === 'number'
|
|
958
|
+
? DEFAULT_FILL_WAIT_MS
|
|
959
|
+
: (entry.fillMaxWaitMs ?? DEFAULT_FILL_WAIT_MS);
|
|
960
|
+
const fillAllowStale = typeof entry === 'number' ? true : (entry.fillAllowStale ?? true);
|
|
961
|
+
if (!isCollectionFamily(family)) {
|
|
962
|
+
this.catalog = { kind: 'malformed' };
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
if (fillMaxWaitMs > MAX_FILL_WAIT_MS) {
|
|
966
|
+
this.catalog = { kind: 'malformed' };
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
collections.set(name, {
|
|
970
|
+
name,
|
|
971
|
+
family,
|
|
972
|
+
schemaVersion: schemaVersion >>> 0,
|
|
973
|
+
replication,
|
|
974
|
+
placement,
|
|
975
|
+
fillMaxWaitMs,
|
|
976
|
+
fillAllowStale,
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
this.catalog = { kind: 'present', collections };
|
|
610
980
|
}
|
|
611
981
|
}
|
|
612
982
|
export const devDb = new DevDatabase();
|
|
@@ -625,12 +995,12 @@ export function buildDatabaseImports(ref, db) {
|
|
|
625
995
|
'data.get': (handle, keyPtr, keyLen) => devDb.get(ref, db, handle, keyPtr, keyLen),
|
|
626
996
|
'data.get_many': (handle, keysPtr, keysLen) => devDb.getMany(ref, db, handle, keysPtr, keysLen),
|
|
627
997
|
'data.exists': (handle, keyPtr, keyLen) => devDb.exists(ref, db, handle, keyPtr, keyLen),
|
|
628
|
-
'data.create': (handle, keyPtr, keyLen, valPtr, valLen,
|
|
629
|
-
'data.patch': (handle, keyPtr, keyLen, patchPtr, patchLen,
|
|
630
|
-
'data.delete': (handle, keyPtr, keyLen,
|
|
631
|
-
'data.get_delete': (handle, keyPtr, keyLen,
|
|
998
|
+
'data.create': (handle, keyPtr, keyLen, valPtr, valLen, idemPtr) => devDb.create(ref, db, handle, keyPtr, keyLen, valPtr, valLen, idemPtr),
|
|
999
|
+
'data.patch': (handle, keyPtr, keyLen, patchPtr, patchLen, idemPtr) => devDb.patch(ref, db, handle, keyPtr, keyLen, patchPtr, patchLen, idemPtr),
|
|
1000
|
+
'data.delete': (handle, keyPtr, keyLen, idemPtr) => devDb.delete(ref, db, handle, keyPtr, keyLen, idemPtr),
|
|
1001
|
+
'data.get_delete': (handle, keyPtr, keyLen, idemPtr) => devDb.getDelete(ref, db, handle, keyPtr, keyLen, idemPtr),
|
|
632
1002
|
'data.unique_lookup': (handle, keyPtr, keyLen) => devDb.uniqueLookup(ref, db, handle, keyPtr, keyLen),
|
|
633
|
-
'data.unique_claim': (handle, keyPtr, keyLen, valPtr, valLen,
|
|
1003
|
+
'data.unique_claim': (handle, keyPtr, keyLen, valPtr, valLen, idemPtr) => devDb.uniqueClaim(ref, db, handle, keyPtr, keyLen, valPtr, valLen, idemPtr),
|
|
634
1004
|
'data.unique_release': (handle, keyPtr, keyLen, valPtr, valLen, _idemPtr) => devDb.uniqueRelease(ref, db, handle, keyPtr, keyLen, valPtr, valLen),
|
|
635
1005
|
'data.membership_contains': (handle, setPtr, setLen, memberPtr, memberLen) => devDb.membershipContains(ref, db, handle, setPtr, setLen, memberPtr, memberLen),
|
|
636
1006
|
'data.membership_add': (handle, setPtr, setLen, memberPtr, memberLen, _idemPtr) => devDb.membershipAdd(ref, db, handle, setPtr, setLen, memberPtr, memberLen),
|
|
@@ -639,19 +1009,19 @@ export function buildDatabaseImports(ref, db) {
|
|
|
639
1009
|
'data.view_get': (handle, keyPtr, keyLen) => devDb.viewGet(ref, db, handle, keyPtr, keyLen),
|
|
640
1010
|
'data.view_publish': (handle, keyPtr, keyLen, valPtr, valLen, _idemPtr) => devDb.viewPublish(ref, db, handle, keyPtr, keyLen, valPtr, valLen),
|
|
641
1011
|
'data.counter_get': (handle, keyPtr, keyLen) => devDb.counterGet(ref, db, handle, keyPtr, keyLen),
|
|
642
|
-
'data.counter_add': (handle, keyPtr, keyLen, delta,
|
|
643
|
-
'data.append': (handle, keyPtr, keyLen, evPtr, evLen,
|
|
1012
|
+
'data.counter_add': (handle, keyPtr, keyLen, delta, idemPtr) => devDb.counterAdd(ref, db, handle, keyPtr, keyLen, delta, idemPtr),
|
|
1013
|
+
'data.append': (handle, keyPtr, keyLen, evPtr, evLen, idemPtr) => devDb.append(ref, db, handle, keyPtr, keyLen, evPtr, evLen, idemPtr),
|
|
644
1014
|
'data.append_once': (handle, keyPtr, keyLen, evidPtr, evidLen, evPtr, evLen) => devDb.appendOnce(ref, db, handle, keyPtr, keyLen, evidPtr, evidLen, evPtr, evLen),
|
|
645
|
-
'data.enqueue': (handle, keyPtr, keyLen, valPtr, valLen,
|
|
1015
|
+
'data.enqueue': (handle, keyPtr, keyLen, valPtr, valLen, idemPtr) => devDb.enqueue(ref, db, handle, keyPtr, keyLen, valPtr, valLen, idemPtr),
|
|
646
1016
|
'data.latest': (handle, keyPtr, keyLen, limit) => devDb.latest(ref, db, handle, keyPtr, keyLen, limit),
|
|
647
1017
|
'data.capacity_set_total': (handle, keyPtr, keyLen, total, _idemPtr) => devDb.capacitySetTotal(ref, db, handle, keyPtr, keyLen, total),
|
|
648
1018
|
'data.capacity_available': (handle, keyPtr, keyLen) => devDb.capacityAvailable(ref, db, handle, keyPtr, keyLen),
|
|
649
|
-
'data.capacity_reserve': (handle, keyPtr, keyLen, amount, ttlMs,
|
|
1019
|
+
'data.capacity_reserve': (handle, keyPtr, keyLen, amount, ttlMs, idemPtr) => devDb.capacityReserve(ref, db, handle, keyPtr, keyLen, amount, ttlMs, idemPtr),
|
|
650
1020
|
'data.capacity_confirm': (handle, keyPtr, keyLen, reservationId, _idemPtr) => devDb.capacityConfirm(ref, db, handle, keyPtr, keyLen, reservationId),
|
|
651
1021
|
'data.capacity_cancel': (handle, keyPtr, keyLen, reservationId, _idemPtr) => devDb.capacityCancel(ref, db, handle, keyPtr, keyLen, reservationId),
|
|
652
1022
|
'data.take_result': (outPtr, outCap) => devDb.takeResult(ref, db, outPtr, outCap),
|
|
653
1023
|
'data.result_schema_version': () => devDb.resultSchemaVersion(db),
|
|
654
|
-
'data.write_allowed': () => 1,
|
|
1024
|
+
'data.write_allowed': () => (kindAllows(db.functionKind, DbOp.Patch) ? 1 : 0),
|
|
655
1025
|
};
|
|
656
1026
|
}
|
|
657
1027
|
export function __resetDbForTests() {
|