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
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import fs from 'node:fs';
|
|
20
|
+
import { createHash } from 'node:crypto';
|
|
20
21
|
import path from 'node:path';
|
|
21
22
|
|
|
22
23
|
import { DataReader, DataWriter } from 'toiljs/io';
|
|
@@ -27,19 +28,31 @@ import {
|
|
|
27
28
|
ALREADY_EXISTS,
|
|
28
29
|
type CapLedger,
|
|
29
30
|
CODEC_ERR,
|
|
31
|
+
CollectionFamily,
|
|
30
32
|
CONFLICT,
|
|
33
|
+
type DbCatalogState,
|
|
31
34
|
type DbDevState,
|
|
32
35
|
type DbSnapshot,
|
|
36
|
+
DbFunctionKind,
|
|
37
|
+
DEFAULT_FILL_WAIT_MS,
|
|
38
|
+
type DevCollectionHandle,
|
|
33
39
|
INVALID_HANDLE,
|
|
34
40
|
MAX_KEY,
|
|
41
|
+
MAX_FILL_WAIT_MS,
|
|
35
42
|
MAX_NAME,
|
|
36
43
|
MAX_RESERVATION_TTL_MS,
|
|
37
44
|
MAX_RESERVATIONS,
|
|
38
45
|
MAX_VALUE,
|
|
46
|
+
OP_NOT_ALLOWED_FOR_FAMILY,
|
|
47
|
+
OP_NOT_ALLOWED_IN_KIND,
|
|
48
|
+
type RecordOutcomeSnapshot,
|
|
39
49
|
type Reservation,
|
|
40
50
|
satI64,
|
|
51
|
+
SCHEMA_UNAVAILABLE,
|
|
41
52
|
TOO_MANY_KEYS,
|
|
42
53
|
TOO_SMALL,
|
|
54
|
+
UNAVAILABLE,
|
|
55
|
+
isCollectionFamily,
|
|
43
56
|
} from './types.js';
|
|
44
57
|
|
|
45
58
|
// ---- schema versions: the dev equivalent of the edge binding the row's
|
|
@@ -73,10 +86,201 @@ function storeKey(collection: string, key: Buffer): string {
|
|
|
73
86
|
return collection + '\0' + key.toString('latin1');
|
|
74
87
|
}
|
|
75
88
|
|
|
76
|
-
|
|
89
|
+
type RecordOutcome =
|
|
90
|
+
| { kind: 'unit' }
|
|
91
|
+
| { kind: 'value'; value: Buffer; schemaVersion: number }
|
|
92
|
+
| { kind: 'absent' }
|
|
93
|
+
| { kind: 'already_exists' }
|
|
94
|
+
| { kind: 'not_found' }
|
|
95
|
+
| { kind: 'conflict' };
|
|
96
|
+
|
|
97
|
+
interface RecordIdemRow {
|
|
98
|
+
requestHash: string;
|
|
99
|
+
outcome: RecordOutcome | null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function readIdem(ref: MemoryRef, ptr: number): Buffer | null {
|
|
103
|
+
if (ptr === 0) return null;
|
|
104
|
+
return readCopy(ref, ptr, 16);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function u64le(n: number): Buffer {
|
|
108
|
+
const b = Buffer.allocUnsafe(8);
|
|
109
|
+
b.writeBigUInt64LE(BigInt(n));
|
|
110
|
+
return b;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const RECORD_OP_CREATE = 4;
|
|
114
|
+
const RECORD_OP_PATCH = 5;
|
|
115
|
+
const RECORD_OP_DELETE = 6;
|
|
116
|
+
const RECORD_OP_GET_DELETE = 7;
|
|
117
|
+
const RECORD_OP_ENQUEUE = 8;
|
|
118
|
+
|
|
119
|
+
function recordRequestHash(op: number, key: Buffer, value: Buffer): string {
|
|
120
|
+
return createHash('sha256')
|
|
121
|
+
.update('toildb/record-idempotency/request/v1')
|
|
122
|
+
.update(Buffer.from([op]))
|
|
123
|
+
.update(u64le(key.length))
|
|
124
|
+
.update(key)
|
|
125
|
+
.update(u64le(value.length))
|
|
126
|
+
.update(value)
|
|
127
|
+
.digest('hex');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function idemKey(coll: DevCollectionHandle, key: Buffer, op: string, idem: Buffer): string {
|
|
131
|
+
return `${storeKey(coll.name, key)}\0${op}\0${idem.toString('hex')}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function reservationIdFromIdem(coll: DevCollectionHandle, key: Buffer, idem: Buffer): bigint {
|
|
135
|
+
const digest = createHash('sha256')
|
|
136
|
+
.update('toildb/capacity-reservation-id/v1')
|
|
137
|
+
.update(coll.name)
|
|
138
|
+
.update('\0')
|
|
139
|
+
.update(key)
|
|
140
|
+
.update(idem)
|
|
141
|
+
.digest();
|
|
142
|
+
return digest.readBigUInt64LE(0) | (1n << 63n);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function snapshotOutcome(outcome: RecordOutcome): RecordOutcomeSnapshot {
|
|
146
|
+
switch (outcome.kind) {
|
|
147
|
+
case 'value':
|
|
148
|
+
return {
|
|
149
|
+
kind: 'value',
|
|
150
|
+
v: outcome.value.toString('base64'),
|
|
151
|
+
sv: outcome.schemaVersion,
|
|
152
|
+
};
|
|
153
|
+
default:
|
|
154
|
+
return { kind: outcome.kind };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function loadOutcome(outcome: RecordOutcomeSnapshot): RecordOutcome {
|
|
159
|
+
if (outcome.kind === 'value') {
|
|
160
|
+
return {
|
|
161
|
+
kind: 'value',
|
|
162
|
+
value: Buffer.from(outcome.v, 'base64'),
|
|
163
|
+
schemaVersion: outcome.sv,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return { kind: outcome.kind };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function collOf(db: DbDevState, handle: number): DevCollectionHandle | null {
|
|
77
170
|
return handle >= 0 && handle < db.handles.length ? db.handles[handle] : null;
|
|
78
171
|
}
|
|
79
172
|
|
|
173
|
+
function collOfFamily(
|
|
174
|
+
db: DbDevState,
|
|
175
|
+
handle: number,
|
|
176
|
+
...families: CollectionFamily[]
|
|
177
|
+
): DevCollectionHandle | number {
|
|
178
|
+
const coll = collOf(db, handle);
|
|
179
|
+
if (coll === null) return INVALID_HANDLE;
|
|
180
|
+
return families.includes(coll.family) ? coll : OP_NOT_ALLOWED_FOR_FAMILY;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
enum DbOp {
|
|
184
|
+
Get,
|
|
185
|
+
GetMany,
|
|
186
|
+
Exists,
|
|
187
|
+
Create,
|
|
188
|
+
Patch,
|
|
189
|
+
Delete,
|
|
190
|
+
GetDelete,
|
|
191
|
+
Enqueue,
|
|
192
|
+
Append,
|
|
193
|
+
AppendOnce,
|
|
194
|
+
Latest,
|
|
195
|
+
CounterGet,
|
|
196
|
+
CounterAdd,
|
|
197
|
+
MembershipContains,
|
|
198
|
+
MembershipAdd,
|
|
199
|
+
MembershipRemove,
|
|
200
|
+
MembershipList,
|
|
201
|
+
UniqueLookup,
|
|
202
|
+
UniqueClaim,
|
|
203
|
+
UniqueRelease,
|
|
204
|
+
CapacityAvailable,
|
|
205
|
+
CapacityReserve,
|
|
206
|
+
CapacityConfirm,
|
|
207
|
+
CapacityCancel,
|
|
208
|
+
ViewGet,
|
|
209
|
+
ViewPublish,
|
|
210
|
+
CapacitySetTotal,
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function isReadOp(op: DbOp): boolean {
|
|
214
|
+
return (
|
|
215
|
+
op === DbOp.Get ||
|
|
216
|
+
op === DbOp.GetMany ||
|
|
217
|
+
op === DbOp.Exists ||
|
|
218
|
+
op === DbOp.ViewGet ||
|
|
219
|
+
op === DbOp.CounterGet ||
|
|
220
|
+
op === DbOp.MembershipContains ||
|
|
221
|
+
op === DbOp.MembershipList ||
|
|
222
|
+
op === DbOp.UniqueLookup ||
|
|
223
|
+
op === DbOp.Latest ||
|
|
224
|
+
op === DbOp.CapacityAvailable
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function isScanOp(op: DbOp): boolean {
|
|
229
|
+
return op === DbOp.Latest || op === DbOp.MembershipList;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function kindAllows(kind: DbFunctionKind, op: DbOp): boolean {
|
|
233
|
+
if (kind === DbFunctionKind.Query) return isReadOp(op) && !isScanOp(op);
|
|
234
|
+
if (kind === DbFunctionKind.Action) {
|
|
235
|
+
return (
|
|
236
|
+
(isReadOp(op) && !isScanOp(op)) ||
|
|
237
|
+
op === DbOp.Create ||
|
|
238
|
+
op === DbOp.Patch ||
|
|
239
|
+
op === DbOp.Delete ||
|
|
240
|
+
op === DbOp.GetDelete ||
|
|
241
|
+
op === DbOp.Enqueue ||
|
|
242
|
+
op === DbOp.Append ||
|
|
243
|
+
op === DbOp.AppendOnce ||
|
|
244
|
+
op === DbOp.CounterAdd ||
|
|
245
|
+
op === DbOp.MembershipAdd ||
|
|
246
|
+
op === DbOp.MembershipRemove ||
|
|
247
|
+
op === DbOp.UniqueClaim ||
|
|
248
|
+
op === DbOp.UniqueRelease ||
|
|
249
|
+
op === DbOp.CapacityReserve ||
|
|
250
|
+
op === DbOp.CapacityConfirm ||
|
|
251
|
+
op === DbOp.CapacityCancel
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
if (kind === DbFunctionKind.Derive)
|
|
255
|
+
return (
|
|
256
|
+
isReadOp(op) || op === DbOp.ViewPublish || op === DbOp.Append || op === DbOp.CounterAdd
|
|
257
|
+
);
|
|
258
|
+
if (kind === DbFunctionKind.Job) return true;
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function collForOp(
|
|
263
|
+
db: DbDevState,
|
|
264
|
+
handle: number,
|
|
265
|
+
op: DbOp,
|
|
266
|
+
...families: CollectionFamily[]
|
|
267
|
+
): DevCollectionHandle | number {
|
|
268
|
+
const coll = collOfFamily(db, handle, ...families);
|
|
269
|
+
if (typeof coll === 'number') return coll;
|
|
270
|
+
return kindAllows(db.functionKind, op) ? coll : OP_NOT_ALLOWED_IN_KIND;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
type CatalogSeedEntry =
|
|
274
|
+
| number
|
|
275
|
+
| {
|
|
276
|
+
family?: number;
|
|
277
|
+
schemaVersion?: number;
|
|
278
|
+
replication?: number;
|
|
279
|
+
placement?: number;
|
|
280
|
+
fillMaxWaitMs?: number;
|
|
281
|
+
fillAllowStale?: boolean;
|
|
282
|
+
};
|
|
283
|
+
|
|
80
284
|
/**
|
|
81
285
|
* The single-process dev data store: the seven ToilDB families, their per-row
|
|
82
286
|
* schema_versions, the loaded wasm's catalog, and optional on-disk persistence.
|
|
@@ -85,19 +289,24 @@ function collOf(db: DbDevState, handle: number): string | null {
|
|
|
85
289
|
export class DevDatabase {
|
|
86
290
|
/** Process-lifetime store: `"collection\0keyLatin1"` -> value. Shared across dispatches. */
|
|
87
291
|
private readonly store = new Map<string, Buffer>();
|
|
292
|
+
/** Record-family idempotency claims/outcomes: collection+key+op+idem -> row. */
|
|
293
|
+
private readonly recordIdem = new Map<string, RecordIdemRow>();
|
|
294
|
+
/** Unique-claim request idempotency bytes: `"collection\0key"` -> hex idem. */
|
|
295
|
+
private readonly uniqueIdem = new Map<string, string>();
|
|
88
296
|
/** View family: `"collection\0key"` -> the latest published view blob. */
|
|
89
297
|
private readonly views = new Map<string, Buffer>();
|
|
90
298
|
/** Membership family: `"collection\0setKey"` -> (memberLatin1 -> member bytes). */
|
|
91
299
|
private readonly members = new Map<string, Map<string, Buffer>>();
|
|
92
300
|
/** Counter family: `"collection\0key"` -> saturating i64 sum of deltas. */
|
|
93
301
|
private readonly counters = new Map<string, bigint>();
|
|
302
|
+
/** Counter idempotency: collection+key+idem -> original delta. */
|
|
303
|
+
private readonly counterIdem = new Map<string, bigint>();
|
|
94
304
|
/** Events family: `"collection\0key"` -> append-ordered event blobs (oldest first). */
|
|
95
305
|
private readonly events = new Map<string, Buffer[]>();
|
|
96
306
|
/** append_once dedup: `"collection\0key"` -> set of eventIds already appended. */
|
|
97
307
|
private readonly eventDedup = new Map<string, Set<string>>();
|
|
98
308
|
/** Capacity family: `"collection\0key"` -> an escrow ledger (ceiling + reservations). */
|
|
99
309
|
private readonly capacity = new Map<string, CapLedger>();
|
|
100
|
-
|
|
101
310
|
/** `"collection\0key"` -> the schema_version the record/view/unique-owner was last
|
|
102
311
|
* written under (single-value families; the edge stores it per StoredValue). */
|
|
103
312
|
private readonly versions = new Map<string, number>();
|
|
@@ -105,22 +314,79 @@ export class DevDatabase {
|
|
|
105
314
|
private readonly eventVersions = new Map<string, number[]>();
|
|
106
315
|
/** Per-member schema_version: `sk` -> (memberLatin1 -> version), parallel to `members`. */
|
|
107
316
|
private readonly memberVersions = new Map<string, Map<string, number>>();
|
|
108
|
-
/**
|
|
109
|
-
private catalog =
|
|
317
|
+
/** The decoded catalog from the loaded wasm, including family + current schema_version. */
|
|
318
|
+
private catalog: DbCatalogState = { kind: 'no-section' };
|
|
110
319
|
|
|
111
320
|
// ---- on-disk persistence: dev data + its versions survive restarts, so a
|
|
112
321
|
// developer can write rows, evolve a @data type, restart, and watch the @migrate
|
|
113
322
|
// run. Delete the file to reset the dev database. JSON with base64 buffers.
|
|
114
323
|
private persistPath: string | null = null;
|
|
115
324
|
|
|
116
|
-
/** (Re)load the
|
|
117
|
-
*
|
|
325
|
+
/** (Re)load the catalog capability metadata from a server wasm. The module
|
|
326
|
+
* loader calls this on every (re)compile so writes stamp the live version. */
|
|
118
327
|
setCatalog(wasm: Buffer): void {
|
|
119
328
|
this.catalog = parseCatalog(wasm);
|
|
120
329
|
}
|
|
121
330
|
|
|
122
|
-
private
|
|
123
|
-
|
|
331
|
+
private currentSchemaVersion(coll: DevCollectionHandle): number {
|
|
332
|
+
if (this.catalog.kind !== 'present') return coll.schemaVersion;
|
|
333
|
+
return this.catalog.collections.get(coll.name)?.schemaVersion ?? coll.schemaVersion;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private stampVersion(coll: DevCollectionHandle, sk: string): void {
|
|
337
|
+
this.versions.set(sk, this.currentSchemaVersion(coll)); // stamp the value type's current version
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private recordIdemStart(
|
|
341
|
+
coll: DevCollectionHandle,
|
|
342
|
+
key: Buffer,
|
|
343
|
+
op: string,
|
|
344
|
+
idem: Buffer | null,
|
|
345
|
+
requestHash: string,
|
|
346
|
+
): { fresh: true } | { fresh: false; status: number; outcome?: RecordOutcome } {
|
|
347
|
+
if (idem === null) return { fresh: true };
|
|
348
|
+
const ik = idemKey(coll, key, op, idem);
|
|
349
|
+
const row = this.recordIdem.get(ik);
|
|
350
|
+
if (row === undefined) {
|
|
351
|
+
this.recordIdem.set(ik, { requestHash, outcome: null });
|
|
352
|
+
return { fresh: true };
|
|
353
|
+
}
|
|
354
|
+
if (row.requestHash !== requestHash) return { fresh: false, status: CONFLICT };
|
|
355
|
+
if (row.outcome === null) return { fresh: false, status: UNAVAILABLE };
|
|
356
|
+
return { fresh: false, status: 0, outcome: row.outcome };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private recordIdemFinish(
|
|
360
|
+
coll: DevCollectionHandle,
|
|
361
|
+
key: Buffer,
|
|
362
|
+
op: string,
|
|
363
|
+
idem: Buffer | null,
|
|
364
|
+
requestHash: string,
|
|
365
|
+
outcome: RecordOutcome,
|
|
366
|
+
): void {
|
|
367
|
+
if (idem === null) return;
|
|
368
|
+
const ik = idemKey(coll, key, op, idem);
|
|
369
|
+
const row = this.recordIdem.get(ik);
|
|
370
|
+
if (row === undefined || row.requestHash !== requestHash) return;
|
|
371
|
+
if (row.outcome === null) row.outcome = outcome;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private replayRecordOutcome(db: DbDevState, outcome: RecordOutcome): number {
|
|
375
|
+
switch (outcome.kind) {
|
|
376
|
+
case 'unit':
|
|
377
|
+
return 0;
|
|
378
|
+
case 'value':
|
|
379
|
+
db.lastResult = outcome.value;
|
|
380
|
+
db.lastResultVersion = outcome.schemaVersion;
|
|
381
|
+
return outcome.value.length;
|
|
382
|
+
case 'absent':
|
|
383
|
+
case 'not_found':
|
|
384
|
+
return ABSENT;
|
|
385
|
+
case 'already_exists':
|
|
386
|
+
return ALREADY_EXISTS;
|
|
387
|
+
case 'conflict':
|
|
388
|
+
return CONFLICT;
|
|
389
|
+
}
|
|
124
390
|
}
|
|
125
391
|
|
|
126
392
|
/** Point the dev DB at an on-disk file and load any existing snapshot. Call once at
|
|
@@ -136,15 +402,25 @@ export class DevDatabase {
|
|
|
136
402
|
if (this.persistPath === null) return;
|
|
137
403
|
const snap: DbSnapshot = {
|
|
138
404
|
store: {},
|
|
405
|
+
recordIdem: {},
|
|
406
|
+
uniqueIdem: {},
|
|
139
407
|
views: {},
|
|
140
408
|
members: {},
|
|
141
409
|
counters: {},
|
|
410
|
+
counterIdem: {},
|
|
142
411
|
events: {},
|
|
143
412
|
eventDedup: {},
|
|
144
413
|
capacity: {},
|
|
145
414
|
};
|
|
146
415
|
for (const [k, v] of this.store)
|
|
147
416
|
snap.store[k] = { v: v.toString('base64'), sv: this.versions.get(k) ?? 0 };
|
|
417
|
+
for (const [k, row] of this.recordIdem)
|
|
418
|
+
snap.recordIdem![k] = {
|
|
419
|
+
requestHash: row.requestHash,
|
|
420
|
+
state: row.outcome === null ? 'pending' : 'done',
|
|
421
|
+
outcome: row.outcome === null ? undefined : snapshotOutcome(row.outcome),
|
|
422
|
+
};
|
|
423
|
+
for (const [k, v] of this.uniqueIdem) snap.uniqueIdem![k] = v;
|
|
148
424
|
for (const [k, v] of this.views)
|
|
149
425
|
snap.views[k] = { v: v.toString('base64'), sv: this.versions.get(k) ?? 0 };
|
|
150
426
|
for (const [k, m] of this.members) {
|
|
@@ -154,6 +430,7 @@ export class DevDatabase {
|
|
|
154
430
|
snap.members[k] = o;
|
|
155
431
|
}
|
|
156
432
|
for (const [k, v] of this.counters) snap.counters[k] = v.toString();
|
|
433
|
+
for (const [k, v] of this.counterIdem) snap.counterIdem![k] = v.toString();
|
|
157
434
|
for (const [k, log] of this.events) {
|
|
158
435
|
const ver = this.eventVersions.get(k) ?? [];
|
|
159
436
|
snap.events[k] = log.map((b, i) => ({ v: b.toString('base64'), sv: ver[i] ?? 0 }));
|
|
@@ -194,6 +471,16 @@ export class DevDatabase {
|
|
|
194
471
|
this.store.set(k, Buffer.from(e.v, 'base64'));
|
|
195
472
|
this.versions.set(k, e.sv);
|
|
196
473
|
}
|
|
474
|
+
for (const [k, row] of Object.entries(snap.recordIdem ?? {})) {
|
|
475
|
+
this.recordIdem.set(k, {
|
|
476
|
+
requestHash: row.requestHash,
|
|
477
|
+
outcome:
|
|
478
|
+
row.state === 'done' && row.outcome !== undefined
|
|
479
|
+
? loadOutcome(row.outcome)
|
|
480
|
+
: null,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
for (const [k, v] of Object.entries(snap.uniqueIdem ?? {})) this.uniqueIdem.set(k, v);
|
|
197
484
|
for (const [k, e] of Object.entries(snap.views ?? {})) {
|
|
198
485
|
this.views.set(k, Buffer.from(e.v, 'base64'));
|
|
199
486
|
this.versions.set(k, e.sv);
|
|
@@ -209,6 +496,8 @@ export class DevDatabase {
|
|
|
209
496
|
this.memberVersions.set(k, ver);
|
|
210
497
|
}
|
|
211
498
|
for (const [k, v] of Object.entries(snap.counters ?? {})) this.counters.set(k, BigInt(v));
|
|
499
|
+
for (const [k, v] of Object.entries(snap.counterIdem ?? {}))
|
|
500
|
+
this.counterIdem.set(k, BigInt(v));
|
|
212
501
|
for (const [k, log] of Object.entries(snap.events ?? {})) {
|
|
213
502
|
this.events.set(
|
|
214
503
|
k,
|
|
@@ -239,11 +528,14 @@ export class DevDatabase {
|
|
|
239
528
|
|
|
240
529
|
private clear(): void {
|
|
241
530
|
this.store.clear();
|
|
531
|
+
this.recordIdem.clear();
|
|
532
|
+
this.uniqueIdem.clear();
|
|
242
533
|
this.versions.clear();
|
|
243
534
|
this.views.clear();
|
|
244
535
|
this.members.clear();
|
|
245
536
|
this.memberVersions.clear();
|
|
246
537
|
this.counters.clear();
|
|
538
|
+
this.counterIdem.clear();
|
|
247
539
|
this.eventVersions.clear();
|
|
248
540
|
this.events.clear();
|
|
249
541
|
this.eventDedup.clear();
|
|
@@ -284,8 +576,30 @@ export class DevDatabase {
|
|
|
284
576
|
): number {
|
|
285
577
|
if (nameLen < 0 || nameLen > MAX_NAME) throw new Error('data: collection name too long');
|
|
286
578
|
const name = readCopy(ref, namePtr, nameLen).toString('utf8');
|
|
579
|
+
let coll: DevCollectionHandle;
|
|
580
|
+
switch (this.catalog.kind) {
|
|
581
|
+
case 'present': {
|
|
582
|
+
const found = this.catalog.collections.get(name);
|
|
583
|
+
if (found === undefined) return SCHEMA_UNAVAILABLE;
|
|
584
|
+
coll = { ...found };
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
case 'malformed':
|
|
588
|
+
return SCHEMA_UNAVAILABLE;
|
|
589
|
+
case 'no-section':
|
|
590
|
+
coll = {
|
|
591
|
+
name,
|
|
592
|
+
family: CollectionFamily.Record,
|
|
593
|
+
schemaVersion: 0,
|
|
594
|
+
replication: 0,
|
|
595
|
+
placement: 0,
|
|
596
|
+
fillMaxWaitMs: DEFAULT_FILL_WAIT_MS,
|
|
597
|
+
fillAllowStale: true,
|
|
598
|
+
};
|
|
599
|
+
break;
|
|
600
|
+
}
|
|
287
601
|
const handle = db.handles.length;
|
|
288
|
-
db.handles.push(
|
|
602
|
+
db.handles.push(coll);
|
|
289
603
|
const m = mem(ref);
|
|
290
604
|
if (outHandlePtr < 0 || outHandlePtr + 4 > m.length)
|
|
291
605
|
throw new Error('data: resolve out-handle out of bounds');
|
|
@@ -294,10 +608,10 @@ export class DevDatabase {
|
|
|
294
608
|
}
|
|
295
609
|
|
|
296
610
|
get(ref: MemoryRef, db: DbDevState, handle: number, keyPtr: number, keyLen: number): number {
|
|
297
|
-
const coll =
|
|
298
|
-
if (coll ===
|
|
611
|
+
const coll = collForOp(db, handle, DbOp.Get, CollectionFamily.Record);
|
|
612
|
+
if (typeof coll === 'number') return coll;
|
|
299
613
|
if (keyLen > MAX_KEY) throw new Error('data: key too long');
|
|
300
|
-
const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
|
|
614
|
+
const sk = storeKey(coll.name, readKey(ref, keyPtr, keyLen));
|
|
301
615
|
const v = this.store.get(sk);
|
|
302
616
|
if (v === undefined) return ABSENT;
|
|
303
617
|
db.lastResult = v;
|
|
@@ -315,9 +629,16 @@ export class DevDatabase {
|
|
|
315
629
|
keysPtr: number,
|
|
316
630
|
keysLen: number,
|
|
317
631
|
): number {
|
|
318
|
-
const coll =
|
|
319
|
-
|
|
632
|
+
const coll = collForOp(
|
|
633
|
+
db,
|
|
634
|
+
handle,
|
|
635
|
+
DbOp.GetMany,
|
|
636
|
+
CollectionFamily.Record,
|
|
637
|
+
CollectionFamily.View,
|
|
638
|
+
);
|
|
639
|
+
if (typeof coll === 'number') return coll;
|
|
320
640
|
if (keysLen > MAX_VALUE) throw new Error('data: keys blob too large');
|
|
641
|
+
const table = coll.family === CollectionFamily.View ? this.views : this.store;
|
|
321
642
|
// Keys blob: u32 count, then per key a u32-length-prefixed blob. The shared
|
|
322
643
|
// DataReader is bounds-safe (empty past end), so a malformed/truncated blob
|
|
323
644
|
// can't over-read; cap each key at MAX_KEY like the edge's prepare_key.
|
|
@@ -332,12 +653,14 @@ export class DevDatabase {
|
|
|
332
653
|
for (let i = 0; i < count; i++) {
|
|
333
654
|
const key = r.readBytes();
|
|
334
655
|
if (key.length > MAX_KEY) throw new Error('data: key too long');
|
|
335
|
-
const sk = storeKey(coll, Buffer.from(key));
|
|
336
|
-
const v =
|
|
656
|
+
const sk = storeKey(coll.name, Buffer.from(key));
|
|
657
|
+
const v = table.get(sk);
|
|
337
658
|
if (v === undefined) {
|
|
338
659
|
w.writeU8(0);
|
|
339
660
|
} else {
|
|
340
|
-
w.writeU8(1)
|
|
661
|
+
w.writeU8(1)
|
|
662
|
+
.writeU32(this.versions.get(sk) ?? 0)
|
|
663
|
+
.writeBytes(v);
|
|
341
664
|
}
|
|
342
665
|
}
|
|
343
666
|
db.lastResult = Buffer.from(w.toBytes());
|
|
@@ -345,9 +668,9 @@ export class DevDatabase {
|
|
|
345
668
|
}
|
|
346
669
|
|
|
347
670
|
exists(ref: MemoryRef, db: DbDevState, handle: number, keyPtr: number, keyLen: number): number {
|
|
348
|
-
const coll =
|
|
349
|
-
if (coll ===
|
|
350
|
-
return this.store.has(storeKey(coll, readKey(ref, keyPtr, keyLen))) ? 1 : 0;
|
|
671
|
+
const coll = collForOp(db, handle, DbOp.Exists, CollectionFamily.Record);
|
|
672
|
+
if (typeof coll === 'number') return coll;
|
|
673
|
+
return this.store.has(storeKey(coll.name, readKey(ref, keyPtr, keyLen))) ? 1 : 0;
|
|
351
674
|
}
|
|
352
675
|
|
|
353
676
|
create(
|
|
@@ -358,15 +681,28 @@ export class DevDatabase {
|
|
|
358
681
|
keyLen: number,
|
|
359
682
|
valPtr: number,
|
|
360
683
|
valLen: number,
|
|
684
|
+
idemPtr: number,
|
|
361
685
|
): number {
|
|
362
|
-
const coll =
|
|
363
|
-
if (coll ===
|
|
686
|
+
const coll = collForOp(db, handle, DbOp.Create, CollectionFamily.Record);
|
|
687
|
+
if (typeof coll === 'number') return coll;
|
|
364
688
|
if (keyLen > MAX_KEY || valLen > MAX_VALUE) throw new Error('data: key/value too large');
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
689
|
+
const key = readKey(ref, keyPtr, keyLen);
|
|
690
|
+
const value = readCopy(ref, valPtr, valLen);
|
|
691
|
+
const idem = readIdem(ref, idemPtr);
|
|
692
|
+
const requestHash = recordRequestHash(RECORD_OP_CREATE, key, value);
|
|
693
|
+
const start = this.recordIdemStart(coll, key, 'C', idem, requestHash);
|
|
694
|
+
if (!start.fresh)
|
|
695
|
+
return start.outcome ? this.replayRecordOutcome(db, start.outcome) : start.status;
|
|
696
|
+
const sk = storeKey(coll.name, key);
|
|
697
|
+
const outcome: RecordOutcome = this.store.has(sk)
|
|
698
|
+
? { kind: 'already_exists' }
|
|
699
|
+
: { kind: 'unit' };
|
|
700
|
+
if (outcome.kind === 'unit') {
|
|
701
|
+
this.store.set(sk, value);
|
|
702
|
+
this.stampVersion(coll, sk); // stamp the value type's current schema version
|
|
703
|
+
}
|
|
704
|
+
this.recordIdemFinish(coll, key, 'C', idem, requestHash, outcome);
|
|
705
|
+
return this.replayRecordOutcome(db, outcome);
|
|
370
706
|
}
|
|
371
707
|
|
|
372
708
|
patch(
|
|
@@ -377,27 +713,52 @@ export class DevDatabase {
|
|
|
377
713
|
keyLen: number,
|
|
378
714
|
patchPtr: number,
|
|
379
715
|
patchLen: number,
|
|
716
|
+
idemPtr: number,
|
|
380
717
|
): number {
|
|
381
|
-
const coll =
|
|
382
|
-
if (coll ===
|
|
718
|
+
const coll = collForOp(db, handle, DbOp.Patch, CollectionFamily.Record);
|
|
719
|
+
if (typeof coll === 'number') return coll;
|
|
383
720
|
if (keyLen > MAX_KEY || patchLen > MAX_VALUE) throw new Error('data: key/patch too large');
|
|
384
|
-
const
|
|
385
|
-
if (!this.store.has(sk)) return ABSENT; // NotFound -> ABSENT on the edge
|
|
721
|
+
const key = readKey(ref, keyPtr, keyLen);
|
|
386
722
|
const v = readCopy(ref, patchPtr, patchLen);
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
723
|
+
const idem = readIdem(ref, idemPtr);
|
|
724
|
+
const requestHash = recordRequestHash(RECORD_OP_PATCH, key, v);
|
|
725
|
+
const start = this.recordIdemStart(coll, key, 'P', idem, requestHash);
|
|
726
|
+
if (!start.fresh)
|
|
727
|
+
return start.outcome ? this.replayRecordOutcome(db, start.outcome) : start.status;
|
|
728
|
+
const sk = storeKey(coll.name, key);
|
|
729
|
+
const outcome: RecordOutcome = this.store.has(sk)
|
|
730
|
+
? { kind: 'value', value: v, schemaVersion: -1 }
|
|
731
|
+
: { kind: 'not_found' };
|
|
732
|
+
if (outcome.kind === 'value') {
|
|
733
|
+
this.store.set(sk, v);
|
|
734
|
+
this.stampVersion(coll, sk); // a patch rewrites the row at the current version
|
|
735
|
+
}
|
|
736
|
+
this.recordIdemFinish(coll, key, 'P', idem, requestHash, outcome);
|
|
737
|
+
return this.replayRecordOutcome(db, outcome);
|
|
392
738
|
}
|
|
393
739
|
|
|
394
|
-
delete(
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
740
|
+
delete(
|
|
741
|
+
ref: MemoryRef,
|
|
742
|
+
db: DbDevState,
|
|
743
|
+
handle: number,
|
|
744
|
+
keyPtr: number,
|
|
745
|
+
keyLen: number,
|
|
746
|
+
idemPtr: number,
|
|
747
|
+
): number {
|
|
748
|
+
const coll = collForOp(db, handle, DbOp.Delete, CollectionFamily.Record);
|
|
749
|
+
if (typeof coll === 'number') return coll;
|
|
750
|
+
const key = readKey(ref, keyPtr, keyLen);
|
|
751
|
+
const idem = readIdem(ref, idemPtr);
|
|
752
|
+
const requestHash = recordRequestHash(RECORD_OP_DELETE, key, Buffer.alloc(0));
|
|
753
|
+
const start = this.recordIdemStart(coll, key, 'D', idem, requestHash);
|
|
754
|
+
if (!start.fresh)
|
|
755
|
+
return start.outcome ? this.replayRecordOutcome(db, start.outcome) : start.status;
|
|
756
|
+
const sk = storeKey(coll.name, key);
|
|
398
757
|
this.store.delete(sk);
|
|
399
758
|
this.versions.delete(sk);
|
|
400
|
-
|
|
759
|
+
const outcome: RecordOutcome = { kind: 'unit' };
|
|
760
|
+
this.recordIdemFinish(coll, key, 'D', idem, requestHash, outcome);
|
|
761
|
+
return this.replayRecordOutcome(db, outcome);
|
|
401
762
|
}
|
|
402
763
|
|
|
403
764
|
// Atomic fetch-and-delete (consume-once); deletes only on a real read.
|
|
@@ -407,17 +768,32 @@ export class DevDatabase {
|
|
|
407
768
|
handle: number,
|
|
408
769
|
keyPtr: number,
|
|
409
770
|
keyLen: number,
|
|
771
|
+
idemPtr: number,
|
|
410
772
|
): number {
|
|
411
|
-
const coll =
|
|
412
|
-
if (coll ===
|
|
413
|
-
const
|
|
773
|
+
const coll = collForOp(db, handle, DbOp.GetDelete, CollectionFamily.Record);
|
|
774
|
+
if (typeof coll === 'number') return coll;
|
|
775
|
+
const key = readKey(ref, keyPtr, keyLen);
|
|
776
|
+
const idem = readIdem(ref, idemPtr);
|
|
777
|
+
const requestHash = recordRequestHash(RECORD_OP_GET_DELETE, key, Buffer.alloc(0));
|
|
778
|
+
const start = this.recordIdemStart(coll, key, 'G', idem, requestHash);
|
|
779
|
+
if (!start.fresh)
|
|
780
|
+
return start.outcome ? this.replayRecordOutcome(db, start.outcome) : start.status;
|
|
781
|
+
const sk = storeKey(coll.name, key);
|
|
414
782
|
const v = this.store.get(sk);
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
783
|
+
const outcome: RecordOutcome =
|
|
784
|
+
v === undefined
|
|
785
|
+
? { kind: 'absent' }
|
|
786
|
+
: {
|
|
787
|
+
kind: 'value',
|
|
788
|
+
value: v,
|
|
789
|
+
schemaVersion: this.versions.get(sk) ?? 0,
|
|
790
|
+
};
|
|
791
|
+
if (outcome.kind === 'value') {
|
|
792
|
+
this.store.delete(sk);
|
|
793
|
+
this.versions.delete(sk);
|
|
794
|
+
}
|
|
795
|
+
this.recordIdemFinish(coll, key, 'G', idem, requestHash, outcome);
|
|
796
|
+
return this.replayRecordOutcome(db, outcome);
|
|
421
797
|
}
|
|
422
798
|
|
|
423
799
|
// --- unique family (lookup / claim / release) ---
|
|
@@ -429,9 +805,9 @@ export class DevDatabase {
|
|
|
429
805
|
keyPtr: number,
|
|
430
806
|
keyLen: number,
|
|
431
807
|
): number {
|
|
432
|
-
const coll =
|
|
433
|
-
if (coll ===
|
|
434
|
-
const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
|
|
808
|
+
const coll = collForOp(db, handle, DbOp.UniqueLookup, CollectionFamily.Unique);
|
|
809
|
+
if (typeof coll === 'number') return coll;
|
|
810
|
+
const sk = storeKey(coll.name, readKey(ref, keyPtr, keyLen));
|
|
435
811
|
const v = this.store.get(sk);
|
|
436
812
|
if (v === undefined) return ABSENT;
|
|
437
813
|
db.lastResult = v;
|
|
@@ -448,19 +824,24 @@ export class DevDatabase {
|
|
|
448
824
|
keyLen: number,
|
|
449
825
|
valPtr: number,
|
|
450
826
|
valLen: number,
|
|
827
|
+
idemPtr: number,
|
|
451
828
|
): number {
|
|
452
|
-
const coll =
|
|
453
|
-
if (coll ===
|
|
829
|
+
const coll = collForOp(db, handle, DbOp.UniqueClaim, CollectionFamily.Unique);
|
|
830
|
+
if (typeof coll === 'number') return coll;
|
|
454
831
|
if (keyLen > MAX_KEY || valLen > MAX_VALUE) throw new Error('data: key/value too large');
|
|
455
|
-
const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
|
|
832
|
+
const sk = storeKey(coll.name, readKey(ref, keyPtr, keyLen));
|
|
456
833
|
const owner = readCopy(ref, valPtr, valLen);
|
|
834
|
+
const idem = readIdem(ref, idemPtr)?.toString('hex') ?? '';
|
|
457
835
|
const existing = this.store.get(sk);
|
|
458
836
|
if (existing === undefined) {
|
|
459
837
|
this.store.set(sk, owner);
|
|
838
|
+
this.uniqueIdem.set(sk, idem);
|
|
460
839
|
this.stampVersion(coll, sk);
|
|
461
840
|
return 0; // Claimed
|
|
462
841
|
}
|
|
463
|
-
if (existing.equals(owner))
|
|
842
|
+
if (existing.equals(owner)) {
|
|
843
|
+
return (this.uniqueIdem.get(sk) ?? '') === idem ? 2 : CONFLICT;
|
|
844
|
+
}
|
|
464
845
|
db.lastResult = existing;
|
|
465
846
|
return 1; // AlreadyClaimed (current owner stashed)
|
|
466
847
|
}
|
|
@@ -474,13 +855,14 @@ export class DevDatabase {
|
|
|
474
855
|
valPtr: number,
|
|
475
856
|
valLen: number,
|
|
476
857
|
): number {
|
|
477
|
-
const coll =
|
|
478
|
-
if (coll ===
|
|
479
|
-
const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
|
|
858
|
+
const coll = collForOp(db, handle, DbOp.UniqueRelease, CollectionFamily.Unique);
|
|
859
|
+
if (typeof coll === 'number') return coll;
|
|
860
|
+
const sk = storeKey(coll.name, readKey(ref, keyPtr, keyLen));
|
|
480
861
|
const existing = this.store.get(sk);
|
|
481
862
|
if (existing === undefined) return 0; // idempotent
|
|
482
863
|
if (!existing.equals(readCopy(ref, valPtr, valLen))) return CONFLICT; // not the owner
|
|
483
864
|
this.store.delete(sk);
|
|
865
|
+
this.uniqueIdem.delete(sk);
|
|
484
866
|
this.versions.delete(sk);
|
|
485
867
|
return 0;
|
|
486
868
|
}
|
|
@@ -496,9 +878,9 @@ export class DevDatabase {
|
|
|
496
878
|
memberPtr: number,
|
|
497
879
|
memberLen: number,
|
|
498
880
|
): number {
|
|
499
|
-
const coll =
|
|
500
|
-
if (coll ===
|
|
501
|
-
const set = this.members.get(storeKey(coll, readKey(ref, setPtr, setLen)));
|
|
881
|
+
const coll = collForOp(db, handle, DbOp.MembershipContains, CollectionFamily.Membership);
|
|
882
|
+
if (typeof coll === 'number') return coll;
|
|
883
|
+
const set = this.members.get(storeKey(coll.name, readKey(ref, setPtr, setLen)));
|
|
502
884
|
if (set === undefined) return 0;
|
|
503
885
|
return set.has(readCopy(ref, memberPtr, memberLen).toString('latin1')) ? 1 : 0;
|
|
504
886
|
}
|
|
@@ -512,11 +894,11 @@ export class DevDatabase {
|
|
|
512
894
|
memberPtr: number,
|
|
513
895
|
memberLen: number,
|
|
514
896
|
): number {
|
|
515
|
-
const coll =
|
|
516
|
-
if (coll ===
|
|
897
|
+
const coll = collForOp(db, handle, DbOp.MembershipAdd, CollectionFamily.Membership);
|
|
898
|
+
if (typeof coll === 'number') return coll;
|
|
517
899
|
if (setLen > MAX_KEY || memberLen > MAX_VALUE)
|
|
518
900
|
throw new Error('data: set/member too large');
|
|
519
|
-
const sk = storeKey(coll, readKey(ref, setPtr, setLen));
|
|
901
|
+
const sk = storeKey(coll.name, readKey(ref, setPtr, setLen));
|
|
520
902
|
const member = readCopy(ref, memberPtr, memberLen);
|
|
521
903
|
let set = this.members.get(sk);
|
|
522
904
|
if (set === undefined) {
|
|
@@ -530,7 +912,7 @@ export class DevDatabase {
|
|
|
530
912
|
mv = new Map();
|
|
531
913
|
this.memberVersions.set(sk, mv);
|
|
532
914
|
}
|
|
533
|
-
mv.set(ml, this.
|
|
915
|
+
mv.set(ml, this.currentSchemaVersion(coll));
|
|
534
916
|
return 0;
|
|
535
917
|
}
|
|
536
918
|
|
|
@@ -543,9 +925,9 @@ export class DevDatabase {
|
|
|
543
925
|
memberPtr: number,
|
|
544
926
|
memberLen: number,
|
|
545
927
|
): number {
|
|
546
|
-
const coll =
|
|
547
|
-
if (coll ===
|
|
548
|
-
const sk = storeKey(coll, readKey(ref, setPtr, setLen));
|
|
928
|
+
const coll = collForOp(db, handle, DbOp.MembershipRemove, CollectionFamily.Membership);
|
|
929
|
+
if (typeof coll === 'number') return coll;
|
|
930
|
+
const sk = storeKey(coll.name, readKey(ref, setPtr, setLen));
|
|
549
931
|
const ml = readCopy(ref, memberPtr, memberLen).toString('latin1');
|
|
550
932
|
this.members.get(sk)?.delete(ml);
|
|
551
933
|
this.memberVersions.get(sk)?.delete(ml);
|
|
@@ -562,9 +944,9 @@ export class DevDatabase {
|
|
|
562
944
|
setLen: number,
|
|
563
945
|
limit: number,
|
|
564
946
|
): number {
|
|
565
|
-
const coll =
|
|
566
|
-
if (coll ===
|
|
567
|
-
const sk = storeKey(coll, readKey(ref, setPtr, setLen));
|
|
947
|
+
const coll = collForOp(db, handle, DbOp.MembershipList, CollectionFamily.Membership);
|
|
948
|
+
if (typeof coll === 'number') return coll;
|
|
949
|
+
const sk = storeKey(coll.name, readKey(ref, setPtr, setLen));
|
|
568
950
|
const set = this.members.get(sk);
|
|
569
951
|
const mv = this.memberVersions.get(sk);
|
|
570
952
|
const n = Math.max(0, Math.min(limit, 0xffff));
|
|
@@ -590,9 +972,9 @@ export class DevDatabase {
|
|
|
590
972
|
keyPtr: number,
|
|
591
973
|
keyLen: number,
|
|
592
974
|
): number {
|
|
593
|
-
const coll =
|
|
594
|
-
if (coll ===
|
|
595
|
-
const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
|
|
975
|
+
const coll = collForOp(db, handle, DbOp.ViewGet, CollectionFamily.View);
|
|
976
|
+
if (typeof coll === 'number') return coll;
|
|
977
|
+
const sk = storeKey(coll.name, readKey(ref, keyPtr, keyLen));
|
|
596
978
|
const v = this.views.get(sk);
|
|
597
979
|
if (v === undefined) return ABSENT;
|
|
598
980
|
db.lastResult = v;
|
|
@@ -610,10 +992,10 @@ export class DevDatabase {
|
|
|
610
992
|
valPtr: number,
|
|
611
993
|
valLen: number,
|
|
612
994
|
): number {
|
|
613
|
-
const coll =
|
|
614
|
-
if (coll ===
|
|
995
|
+
const coll = collForOp(db, handle, DbOp.ViewPublish, CollectionFamily.View);
|
|
996
|
+
if (typeof coll === 'number') return coll;
|
|
615
997
|
if (keyLen > MAX_KEY || valLen > MAX_VALUE) throw new Error('data: key/view too large');
|
|
616
|
-
const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
|
|
998
|
+
const sk = storeKey(coll.name, readKey(ref, keyPtr, keyLen));
|
|
617
999
|
this.views.set(sk, readCopy(ref, valPtr, valLen));
|
|
618
1000
|
this.stampVersion(coll, sk);
|
|
619
1001
|
return 0;
|
|
@@ -630,9 +1012,9 @@ export class DevDatabase {
|
|
|
630
1012
|
keyPtr: number,
|
|
631
1013
|
keyLen: number,
|
|
632
1014
|
): number {
|
|
633
|
-
const coll =
|
|
634
|
-
if (coll ===
|
|
635
|
-
const sum = this.counters.get(storeKey(coll, readKey(ref, keyPtr, keyLen))) ?? 0n;
|
|
1015
|
+
const coll = collForOp(db, handle, DbOp.CounterGet, CollectionFamily.Counter);
|
|
1016
|
+
if (typeof coll === 'number') return coll;
|
|
1017
|
+
const sum = this.counters.get(storeKey(coll.name, readKey(ref, keyPtr, keyLen))) ?? 0n;
|
|
636
1018
|
const out = Buffer.alloc(8);
|
|
637
1019
|
out.writeBigInt64LE(sum);
|
|
638
1020
|
db.lastResult = out;
|
|
@@ -648,11 +1030,21 @@ export class DevDatabase {
|
|
|
648
1030
|
keyPtr: number,
|
|
649
1031
|
keyLen: number,
|
|
650
1032
|
delta: number | bigint,
|
|
1033
|
+
idemPtr: number,
|
|
651
1034
|
): number {
|
|
652
|
-
const coll =
|
|
653
|
-
if (coll ===
|
|
654
|
-
const
|
|
655
|
-
|
|
1035
|
+
const coll = collForOp(db, handle, DbOp.CounterAdd, CollectionFamily.Counter);
|
|
1036
|
+
if (typeof coll === 'number') return coll;
|
|
1037
|
+
const key = readKey(ref, keyPtr, keyLen);
|
|
1038
|
+
const idem = readIdem(ref, idemPtr);
|
|
1039
|
+
const d = BigInt(delta);
|
|
1040
|
+
if (idem !== null) {
|
|
1041
|
+
const ik = idemKey(coll, key, 'A', idem);
|
|
1042
|
+
const seen = this.counterIdem.get(ik);
|
|
1043
|
+
if (seen !== undefined) return seen === d ? 0 : CONFLICT;
|
|
1044
|
+
this.counterIdem.set(ik, d);
|
|
1045
|
+
}
|
|
1046
|
+
const sk = storeKey(coll.name, key);
|
|
1047
|
+
this.counters.set(sk, satI64((this.counters.get(sk) ?? 0n) + d));
|
|
656
1048
|
return 0;
|
|
657
1049
|
}
|
|
658
1050
|
|
|
@@ -666,14 +1058,27 @@ export class DevDatabase {
|
|
|
666
1058
|
keyLen: number,
|
|
667
1059
|
evPtr: number,
|
|
668
1060
|
evLen: number,
|
|
1061
|
+
idemPtr: number,
|
|
669
1062
|
): number {
|
|
670
|
-
const coll =
|
|
671
|
-
if (coll ===
|
|
1063
|
+
const coll = collForOp(db, handle, DbOp.Append, CollectionFamily.Events);
|
|
1064
|
+
if (typeof coll === 'number') return coll;
|
|
672
1065
|
if (keyLen > MAX_KEY || evLen > MAX_VALUE) throw new Error('data: key/event too large');
|
|
673
|
-
const
|
|
1066
|
+
const key = readKey(ref, keyPtr, keyLen);
|
|
1067
|
+
const sk = storeKey(coll.name, key);
|
|
1068
|
+
const idem = readIdem(ref, idemPtr);
|
|
1069
|
+
if (idem !== null) {
|
|
1070
|
+
let seen = this.eventDedup.get(sk);
|
|
1071
|
+
if (seen === undefined) {
|
|
1072
|
+
seen = new Set();
|
|
1073
|
+
this.eventDedup.set(sk, seen);
|
|
1074
|
+
}
|
|
1075
|
+
const eventId = idem.toString('latin1');
|
|
1076
|
+
if (seen.has(eventId)) return 0;
|
|
1077
|
+
seen.add(eventId);
|
|
1078
|
+
}
|
|
674
1079
|
const log = this.events.get(sk);
|
|
675
1080
|
const ev = readCopy(ref, evPtr, evLen);
|
|
676
|
-
const sv = this.
|
|
1081
|
+
const sv = this.currentSchemaVersion(coll);
|
|
677
1082
|
if (log === undefined) {
|
|
678
1083
|
this.events.set(sk, [ev]);
|
|
679
1084
|
this.eventVersions.set(sk, [sv]);
|
|
@@ -697,10 +1102,10 @@ export class DevDatabase {
|
|
|
697
1102
|
evPtr: number,
|
|
698
1103
|
evLen: number,
|
|
699
1104
|
): number {
|
|
700
|
-
const coll =
|
|
701
|
-
if (coll ===
|
|
1105
|
+
const coll = collForOp(db, handle, DbOp.AppendOnce, CollectionFamily.Events);
|
|
1106
|
+
if (typeof coll === 'number') return coll;
|
|
702
1107
|
if (keyLen > MAX_KEY || evLen > MAX_VALUE) throw new Error('data: key/event too large');
|
|
703
|
-
const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
|
|
1108
|
+
const sk = storeKey(coll.name, readKey(ref, keyPtr, keyLen));
|
|
704
1109
|
const evid = readCopy(ref, evidPtr, evidLen).toString('latin1');
|
|
705
1110
|
let seen = this.eventDedup.get(sk);
|
|
706
1111
|
if (seen === undefined) {
|
|
@@ -709,7 +1114,7 @@ export class DevDatabase {
|
|
|
709
1114
|
}
|
|
710
1115
|
if (seen.has(evid)) return 0; // already appended under this id
|
|
711
1116
|
const ev = readCopy(ref, evPtr, evLen);
|
|
712
|
-
const sv = this.
|
|
1117
|
+
const sv = this.currentSchemaVersion(coll);
|
|
713
1118
|
const log = this.events.get(sk);
|
|
714
1119
|
if (log === undefined) {
|
|
715
1120
|
this.events.set(sk, [ev]);
|
|
@@ -733,15 +1138,28 @@ export class DevDatabase {
|
|
|
733
1138
|
keyLen: number,
|
|
734
1139
|
valPtr: number,
|
|
735
1140
|
valLen: number,
|
|
1141
|
+
idemPtr: number,
|
|
736
1142
|
): number {
|
|
737
|
-
const coll =
|
|
738
|
-
if (coll ===
|
|
1143
|
+
const coll = collForOp(db, handle, DbOp.Enqueue, CollectionFamily.Record);
|
|
1144
|
+
if (typeof coll === 'number') return coll;
|
|
739
1145
|
if (keyLen > MAX_KEY || valLen > MAX_VALUE) throw new Error('data: key/value too large');
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
1146
|
+
const key = readKey(ref, keyPtr, keyLen);
|
|
1147
|
+
const value = readCopy(ref, valPtr, valLen);
|
|
1148
|
+
const idem = readIdem(ref, idemPtr);
|
|
1149
|
+
const requestHash = recordRequestHash(RECORD_OP_ENQUEUE, key, value);
|
|
1150
|
+
const start = this.recordIdemStart(coll, key, 'E', idem, requestHash);
|
|
1151
|
+
if (!start.fresh)
|
|
1152
|
+
return start.outcome ? this.replayRecordOutcome(db, start.outcome) : start.status;
|
|
1153
|
+
const sk = storeKey(coll.name, key);
|
|
1154
|
+
const outcome: RecordOutcome = this.store.has(sk)
|
|
1155
|
+
? { kind: 'unit' }
|
|
1156
|
+
: { kind: 'not_found' };
|
|
1157
|
+
if (outcome.kind === 'unit') {
|
|
1158
|
+
this.store.set(sk, value);
|
|
1159
|
+
this.stampVersion(coll, sk);
|
|
1160
|
+
}
|
|
1161
|
+
this.recordIdemFinish(coll, key, 'E', idem, requestHash, outcome);
|
|
1162
|
+
return this.replayRecordOutcome(db, outcome);
|
|
745
1163
|
}
|
|
746
1164
|
|
|
747
1165
|
// Frame the newest-`limit` events as `u32 count` then per event a
|
|
@@ -755,9 +1173,9 @@ export class DevDatabase {
|
|
|
755
1173
|
keyLen: number,
|
|
756
1174
|
limit: number,
|
|
757
1175
|
): number {
|
|
758
|
-
const coll =
|
|
759
|
-
if (coll ===
|
|
760
|
-
const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
|
|
1176
|
+
const coll = collForOp(db, handle, DbOp.Latest, CollectionFamily.Events);
|
|
1177
|
+
if (typeof coll === 'number') return coll;
|
|
1178
|
+
const sk = storeKey(coll.name, readKey(ref, keyPtr, keyLen));
|
|
761
1179
|
const log = this.events.get(sk) ?? [];
|
|
762
1180
|
const vers = this.eventVersions.get(sk) ?? [];
|
|
763
1181
|
const n = Math.max(0, Math.min(limit, 0xffff));
|
|
@@ -787,9 +1205,9 @@ export class DevDatabase {
|
|
|
787
1205
|
keyLen: number,
|
|
788
1206
|
total: number | bigint,
|
|
789
1207
|
): number {
|
|
790
|
-
const coll =
|
|
791
|
-
if (coll ===
|
|
792
|
-
const l = this.capLedger(storeKey(coll, readKey(ref, keyPtr, keyLen)));
|
|
1208
|
+
const coll = collForOp(db, handle, DbOp.CapacitySetTotal, CollectionFamily.Capacity);
|
|
1209
|
+
if (typeof coll === 'number') return coll;
|
|
1210
|
+
const l = this.capLedger(storeKey(coll.name, readKey(ref, keyPtr, keyLen)));
|
|
793
1211
|
const t = BigInt(total);
|
|
794
1212
|
l.total = satI64(t < 0n ? 0n : t);
|
|
795
1213
|
return 0;
|
|
@@ -803,9 +1221,9 @@ export class DevDatabase {
|
|
|
803
1221
|
keyPtr: number,
|
|
804
1222
|
keyLen: number,
|
|
805
1223
|
): number {
|
|
806
|
-
const coll =
|
|
807
|
-
if (coll ===
|
|
808
|
-
const l = this.capLedger(storeKey(coll, readKey(ref, keyPtr, keyLen)));
|
|
1224
|
+
const coll = collForOp(db, handle, DbOp.CapacityAvailable, CollectionFamily.Capacity);
|
|
1225
|
+
if (typeof coll === 'number') return coll;
|
|
1226
|
+
const l = this.capLedger(storeKey(coll.name, readKey(ref, keyPtr, keyLen)));
|
|
809
1227
|
this.capPrune(l, Date.now());
|
|
810
1228
|
const avail = l.total - this.capReserved(l);
|
|
811
1229
|
const out = Buffer.alloc(8);
|
|
@@ -827,18 +1245,32 @@ export class DevDatabase {
|
|
|
827
1245
|
keyLen: number,
|
|
828
1246
|
amount: number | bigint,
|
|
829
1247
|
ttlMs: number | bigint,
|
|
1248
|
+
idemPtr: number,
|
|
830
1249
|
): number {
|
|
831
|
-
const coll =
|
|
832
|
-
if (coll ===
|
|
1250
|
+
const coll = collForOp(db, handle, DbOp.CapacityReserve, CollectionFamily.Capacity);
|
|
1251
|
+
if (typeof coll === 'number') return coll;
|
|
833
1252
|
const want = BigInt(amount);
|
|
834
1253
|
if (want <= 0n) return CODEC_ERR; // BadAmount (edge: -1006)
|
|
835
|
-
const
|
|
1254
|
+
const key = readKey(ref, keyPtr, keyLen);
|
|
1255
|
+
const idem = readIdem(ref, idemPtr);
|
|
1256
|
+
const ttl = Math.min(Math.max(0, Number(ttlMs)), MAX_RESERVATION_TTL_MS);
|
|
1257
|
+
const requestedId = idem === null ? null : reservationIdFromIdem(coll, key, idem);
|
|
1258
|
+
const l = this.capLedger(storeKey(coll.name, key));
|
|
836
1259
|
const now = Date.now();
|
|
837
1260
|
this.capPrune(l, now);
|
|
1261
|
+
if (requestedId !== null) {
|
|
1262
|
+
const existing = l.reservations.get(requestedId);
|
|
1263
|
+
if (existing !== undefined) {
|
|
1264
|
+
if (existing.amount !== want) return CONFLICT;
|
|
1265
|
+
const out = Buffer.alloc(8);
|
|
1266
|
+
out.writeBigUInt64LE(requestedId);
|
|
1267
|
+
db.lastResult = out;
|
|
1268
|
+
return out.length;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
838
1271
|
if (l.total - this.capReserved(l) < want || l.reservations.size >= MAX_RESERVATIONS)
|
|
839
1272
|
return ABSENT; // never oversell; bound the reservation count
|
|
840
|
-
const
|
|
841
|
-
const id = l.nextId++;
|
|
1273
|
+
const id = requestedId ?? l.nextId++;
|
|
842
1274
|
l.reservations.set(id, { amount: want, expiresMs: now + ttl, confirmed: false });
|
|
843
1275
|
const out = Buffer.alloc(8);
|
|
844
1276
|
out.writeBigUInt64LE(id);
|
|
@@ -857,9 +1289,9 @@ export class DevDatabase {
|
|
|
857
1289
|
keyLen: number,
|
|
858
1290
|
reservationId: number | bigint,
|
|
859
1291
|
): number {
|
|
860
|
-
const coll =
|
|
861
|
-
if (coll ===
|
|
862
|
-
const l = this.capLedger(storeKey(coll, readKey(ref, keyPtr, keyLen)));
|
|
1292
|
+
const coll = collForOp(db, handle, DbOp.CapacityConfirm, CollectionFamily.Capacity);
|
|
1293
|
+
if (typeof coll === 'number') return coll;
|
|
1294
|
+
const l = this.capLedger(storeKey(coll.name, readKey(ref, keyPtr, keyLen)));
|
|
863
1295
|
this.capPrune(l, Date.now());
|
|
864
1296
|
const r = l.reservations.get(BigInt(reservationId));
|
|
865
1297
|
if (r === undefined) return 0;
|
|
@@ -877,9 +1309,9 @@ export class DevDatabase {
|
|
|
877
1309
|
keyLen: number,
|
|
878
1310
|
reservationId: number | bigint,
|
|
879
1311
|
): number {
|
|
880
|
-
const coll =
|
|
881
|
-
if (coll ===
|
|
882
|
-
const l = this.capLedger(storeKey(coll, readKey(ref, keyPtr, keyLen)));
|
|
1312
|
+
const coll = collForOp(db, handle, DbOp.CapacityCancel, CollectionFamily.Capacity);
|
|
1313
|
+
if (typeof coll === 'number') return coll;
|
|
1314
|
+
const l = this.capLedger(storeKey(coll.name, readKey(ref, keyPtr, keyLen)));
|
|
883
1315
|
this.capPrune(l, Date.now());
|
|
884
1316
|
const id = BigInt(reservationId);
|
|
885
1317
|
const r = l.reservations.get(id);
|
|
@@ -919,13 +1351,46 @@ export class DevDatabase {
|
|
|
919
1351
|
/** Test-only: clear the stores + catalog + persistence between unit tests. */
|
|
920
1352
|
resetForTests(): void {
|
|
921
1353
|
this.clear();
|
|
922
|
-
this.catalog =
|
|
1354
|
+
this.catalog = { kind: 'no-section' };
|
|
923
1355
|
this.persistPath = null;
|
|
924
1356
|
}
|
|
925
1357
|
|
|
926
|
-
/** Test-only: seed the catalog
|
|
927
|
-
setCatalogForTests(entries: Record<string,
|
|
928
|
-
|
|
1358
|
+
/** Test-only: seed the catalog directly. Number values default to record-family entries. */
|
|
1359
|
+
setCatalogForTests(entries: Record<string, CatalogSeedEntry>): void {
|
|
1360
|
+
const collections = new Map<string, DevCollectionHandle>();
|
|
1361
|
+
for (const [name, entry] of Object.entries(entries)) {
|
|
1362
|
+
const schemaVersion = typeof entry === 'number' ? entry : (entry.schemaVersion ?? 0);
|
|
1363
|
+
const family =
|
|
1364
|
+
typeof entry === 'number'
|
|
1365
|
+
? CollectionFamily.Record
|
|
1366
|
+
: (entry.family ?? CollectionFamily.Record);
|
|
1367
|
+
const replication = typeof entry === 'number' ? 0 : (entry.replication ?? 0);
|
|
1368
|
+
const placement = typeof entry === 'number' ? 0 : (entry.placement ?? 0);
|
|
1369
|
+
const fillMaxWaitMs =
|
|
1370
|
+
typeof entry === 'number'
|
|
1371
|
+
? DEFAULT_FILL_WAIT_MS
|
|
1372
|
+
: (entry.fillMaxWaitMs ?? DEFAULT_FILL_WAIT_MS);
|
|
1373
|
+
const fillAllowStale =
|
|
1374
|
+
typeof entry === 'number' ? true : (entry.fillAllowStale ?? true);
|
|
1375
|
+
if (!isCollectionFamily(family)) {
|
|
1376
|
+
this.catalog = { kind: 'malformed' };
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
if (fillMaxWaitMs > MAX_FILL_WAIT_MS) {
|
|
1380
|
+
this.catalog = { kind: 'malformed' };
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
collections.set(name, {
|
|
1384
|
+
name,
|
|
1385
|
+
family,
|
|
1386
|
+
schemaVersion: schemaVersion >>> 0,
|
|
1387
|
+
replication,
|
|
1388
|
+
placement,
|
|
1389
|
+
fillMaxWaitMs,
|
|
1390
|
+
fillAllowStale,
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
this.catalog = { kind: 'present', collections };
|
|
929
1394
|
}
|
|
930
1395
|
}
|
|
931
1396
|
|
|
@@ -978,8 +1443,8 @@ export function buildDatabaseImports(
|
|
|
978
1443
|
keyLen: number,
|
|
979
1444
|
valPtr: number,
|
|
980
1445
|
valLen: number,
|
|
981
|
-
|
|
982
|
-
): number => devDb.create(ref, db, handle, keyPtr, keyLen, valPtr, valLen),
|
|
1446
|
+
idemPtr: number,
|
|
1447
|
+
): number => devDb.create(ref, db, handle, keyPtr, keyLen, valPtr, valLen, idemPtr),
|
|
983
1448
|
|
|
984
1449
|
'data.patch': (
|
|
985
1450
|
handle: number,
|
|
@@ -987,18 +1452,18 @@ export function buildDatabaseImports(
|
|
|
987
1452
|
keyLen: number,
|
|
988
1453
|
patchPtr: number,
|
|
989
1454
|
patchLen: number,
|
|
990
|
-
|
|
991
|
-
): number => devDb.patch(ref, db, handle, keyPtr, keyLen, patchPtr, patchLen),
|
|
1455
|
+
idemPtr: number,
|
|
1456
|
+
): number => devDb.patch(ref, db, handle, keyPtr, keyLen, patchPtr, patchLen, idemPtr),
|
|
992
1457
|
|
|
993
|
-
'data.delete': (handle: number, keyPtr: number, keyLen: number,
|
|
994
|
-
devDb.delete(ref, db, handle, keyPtr, keyLen),
|
|
1458
|
+
'data.delete': (handle: number, keyPtr: number, keyLen: number, idemPtr: number): number =>
|
|
1459
|
+
devDb.delete(ref, db, handle, keyPtr, keyLen, idemPtr),
|
|
995
1460
|
|
|
996
1461
|
'data.get_delete': (
|
|
997
1462
|
handle: number,
|
|
998
1463
|
keyPtr: number,
|
|
999
1464
|
keyLen: number,
|
|
1000
|
-
|
|
1001
|
-
): number => devDb.getDelete(ref, db, handle, keyPtr, keyLen),
|
|
1465
|
+
idemPtr: number,
|
|
1466
|
+
): number => devDb.getDelete(ref, db, handle, keyPtr, keyLen, idemPtr),
|
|
1002
1467
|
|
|
1003
1468
|
'data.unique_lookup': (handle: number, keyPtr: number, keyLen: number): number =>
|
|
1004
1469
|
devDb.uniqueLookup(ref, db, handle, keyPtr, keyLen),
|
|
@@ -1009,8 +1474,8 @@ export function buildDatabaseImports(
|
|
|
1009
1474
|
keyLen: number,
|
|
1010
1475
|
valPtr: number,
|
|
1011
1476
|
valLen: number,
|
|
1012
|
-
|
|
1013
|
-
): number => devDb.uniqueClaim(ref, db, handle, keyPtr, keyLen, valPtr, valLen),
|
|
1477
|
+
idemPtr: number,
|
|
1478
|
+
): number => devDb.uniqueClaim(ref, db, handle, keyPtr, keyLen, valPtr, valLen, idemPtr),
|
|
1014
1479
|
|
|
1015
1480
|
'data.unique_release': (
|
|
1016
1481
|
handle: number,
|
|
@@ -1075,8 +1540,8 @@ export function buildDatabaseImports(
|
|
|
1075
1540
|
keyPtr: number,
|
|
1076
1541
|
keyLen: number,
|
|
1077
1542
|
delta: number | bigint,
|
|
1078
|
-
|
|
1079
|
-
): number => devDb.counterAdd(ref, db, handle, keyPtr, keyLen, delta),
|
|
1543
|
+
idemPtr: number,
|
|
1544
|
+
): number => devDb.counterAdd(ref, db, handle, keyPtr, keyLen, delta, idemPtr),
|
|
1080
1545
|
|
|
1081
1546
|
'data.append': (
|
|
1082
1547
|
handle: number,
|
|
@@ -1084,8 +1549,8 @@ export function buildDatabaseImports(
|
|
|
1084
1549
|
keyLen: number,
|
|
1085
1550
|
evPtr: number,
|
|
1086
1551
|
evLen: number,
|
|
1087
|
-
|
|
1088
|
-
): number => devDb.append(ref, db, handle, keyPtr, keyLen, evPtr, evLen),
|
|
1552
|
+
idemPtr: number,
|
|
1553
|
+
): number => devDb.append(ref, db, handle, keyPtr, keyLen, evPtr, evLen, idemPtr),
|
|
1089
1554
|
|
|
1090
1555
|
'data.append_once': (
|
|
1091
1556
|
handle: number,
|
|
@@ -1104,8 +1569,8 @@ export function buildDatabaseImports(
|
|
|
1104
1569
|
keyLen: number,
|
|
1105
1570
|
valPtr: number,
|
|
1106
1571
|
valLen: number,
|
|
1107
|
-
|
|
1108
|
-
): number => devDb.enqueue(ref, db, handle, keyPtr, keyLen, valPtr, valLen),
|
|
1572
|
+
idemPtr: number,
|
|
1573
|
+
): number => devDb.enqueue(ref, db, handle, keyPtr, keyLen, valPtr, valLen, idemPtr),
|
|
1109
1574
|
|
|
1110
1575
|
'data.latest': (handle: number, keyPtr: number, keyLen: number, limit: number): number =>
|
|
1111
1576
|
devDb.latest(ref, db, handle, keyPtr, keyLen, limit),
|
|
@@ -1127,8 +1592,8 @@ export function buildDatabaseImports(
|
|
|
1127
1592
|
keyLen: number,
|
|
1128
1593
|
amount: number | bigint,
|
|
1129
1594
|
ttlMs: number | bigint,
|
|
1130
|
-
|
|
1131
|
-
): number => devDb.capacityReserve(ref, db, handle, keyPtr, keyLen, amount, ttlMs),
|
|
1595
|
+
idemPtr: number,
|
|
1596
|
+
): number => devDb.capacityReserve(ref, db, handle, keyPtr, keyLen, amount, ttlMs, idemPtr),
|
|
1132
1597
|
|
|
1133
1598
|
'data.capacity_confirm': (
|
|
1134
1599
|
handle: number,
|
|
@@ -1151,10 +1616,9 @@ export function buildDatabaseImports(
|
|
|
1151
1616
|
|
|
1152
1617
|
'data.result_schema_version': (): bigint => devDb.resultSchemaVersion(db),
|
|
1153
1618
|
|
|
1154
|
-
// `data.write_allowed() -> i32`: 1 if the current call may
|
|
1155
|
-
// rewrite-on-read
|
|
1156
|
-
|
|
1157
|
-
'data.write_allowed': (): number => 1,
|
|
1619
|
+
// `data.write_allowed() -> i32`: 1 if the current call may issue the
|
|
1620
|
+
// record patch used by rewrite-on-read migration convergence.
|
|
1621
|
+
'data.write_allowed': (): number => (kindAllows(db.functionKind, DbOp.Patch) ? 1 : 0),
|
|
1158
1622
|
};
|
|
1159
1623
|
}
|
|
1160
1624
|
|
|
@@ -1163,7 +1627,7 @@ export function __resetDbForTests(): void {
|
|
|
1163
1627
|
devDb.resetForTests();
|
|
1164
1628
|
}
|
|
1165
1629
|
|
|
1166
|
-
/** Test-only: seed the catalog
|
|
1167
|
-
export function __setDbCatalogForTests(entries: Record<string,
|
|
1630
|
+
/** Test-only: seed the catalog directly. Number values default to record-family entries. */
|
|
1631
|
+
export function __setDbCatalogForTests(entries: Record<string, CatalogSeedEntry>): void {
|
|
1168
1632
|
devDb.setCatalogForTests(entries);
|
|
1169
1633
|
}
|