toiljs 0.0.59 → 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.
Files changed (158) hide show
  1. package/.github/workflows/ci.yml +31 -0
  2. package/CHANGELOG.md +15 -0
  3. package/build/cli/.tsbuildinfo +1 -1
  4. package/build/cli/index.js +311 -118
  5. package/build/client/.tsbuildinfo +1 -1
  6. package/build/client/index.d.ts +1 -1
  7. package/build/client/index.js +1 -1
  8. package/build/client/routing/mount.js +12 -1
  9. package/build/client/ssr/markers.d.ts +1 -0
  10. package/build/client/ssr/markers.js +3 -0
  11. package/build/compiler/.tsbuildinfo +1 -1
  12. package/build/compiler/config.d.ts +21 -0
  13. package/build/compiler/config.js +35 -0
  14. package/build/compiler/docs.d.ts +2 -1
  15. package/build/compiler/docs.js +33 -304
  16. package/build/compiler/index.d.ts +13 -0
  17. package/build/compiler/index.js +113 -21
  18. package/build/compiler/template-build.d.ts +21 -1
  19. package/build/compiler/template-build.js +110 -26
  20. package/build/compiler/toil-docs.generated.d.ts +1 -0
  21. package/build/compiler/toil-docs.generated.js +20 -0
  22. package/build/devserver/.tsbuildinfo +1 -1
  23. package/build/devserver/daemon/catalog.d.ts +26 -0
  24. package/build/devserver/daemon/catalog.js +48 -0
  25. package/build/devserver/daemon/cron.d.ts +4 -0
  26. package/build/devserver/daemon/cron.js +50 -0
  27. package/build/devserver/daemon/host.d.ts +37 -0
  28. package/build/devserver/daemon/host.js +94 -0
  29. package/build/devserver/daemon/index.d.ts +34 -0
  30. package/build/devserver/daemon/index.js +241 -0
  31. package/build/devserver/db/catalog.d.ts +2 -0
  32. package/build/devserver/db/catalog.js +80 -0
  33. package/build/devserver/db/database.d.ts +80 -0
  34. package/build/devserver/db/database.js +1032 -0
  35. package/build/devserver/db/index.d.ts +3 -0
  36. package/build/devserver/db/index.js +3 -0
  37. package/build/devserver/db/routeKinds.d.ts +8 -0
  38. package/build/devserver/db/routeKinds.js +139 -0
  39. package/build/devserver/db/types.d.ts +121 -0
  40. package/build/devserver/db/types.js +52 -0
  41. package/build/devserver/email/index.js +1 -1
  42. package/build/devserver/index.d.ts +19 -24
  43. package/build/devserver/index.js +11 -165
  44. package/build/devserver/mstore/store.d.ts +18 -0
  45. package/build/devserver/mstore/store.js +82 -0
  46. package/build/devserver/{host.d.ts → runtime/host.d.ts} +7 -1
  47. package/build/devserver/{host.js → runtime/host.js} +51 -7
  48. package/build/devserver/{module.d.ts → runtime/module.d.ts} +2 -1
  49. package/build/devserver/{module.js → runtime/module.js} +34 -1
  50. package/build/devserver/server.d.ts +23 -0
  51. package/build/devserver/server.js +223 -0
  52. package/build/devserver/ssr.d.ts +25 -0
  53. package/build/devserver/ssr.js +114 -0
  54. package/build/devserver/wasm/sections.d.ts +2 -0
  55. package/build/devserver/wasm/sections.js +42 -0
  56. package/build/devserver/wasm/surface.d.ts +18 -0
  57. package/build/devserver/wasm/surface.js +41 -0
  58. package/docs/README.md +4 -4
  59. package/docs/auth-todo.md +6 -6
  60. package/docs/caching.md +5 -5
  61. package/docs/cli.md +15 -0
  62. package/docs/client.md +40 -0
  63. package/docs/crypto.md +4 -4
  64. package/docs/data.md +6 -6
  65. package/docs/email.md +28 -28
  66. package/docs/environment.md +10 -10
  67. package/docs/index.md +26 -0
  68. package/docs/ratelimit.md +10 -10
  69. package/docs/routing.md +2 -2
  70. package/docs/server.md +61 -0
  71. package/docs/ssr.md +561 -113
  72. package/docs/styling.md +22 -0
  73. package/docs/time.md +3 -3
  74. package/eslint.config.js +10 -1
  75. package/examples/basic/client/components/Header.tsx +3 -0
  76. package/examples/basic/client/routes/features/actions.tsx +0 -2
  77. package/examples/basic/client/routes/hello.tsx +89 -19
  78. package/examples/basic/client/styles/main.css +48 -0
  79. package/examples/basic/server/SsrHelloRender.ts +97 -0
  80. package/examples/basic/server/main.ts +5 -0
  81. package/examples/basic/server/migrations/GuestEntry.migration.ts +39 -0
  82. package/examples/basic/server/streams/Echo.ts +49 -0
  83. package/package.json +12 -10
  84. package/scripts/gen-toil-docs.mjs +96 -0
  85. package/server/runtime/time.ts +3 -3
  86. package/src/cli/create.ts +40 -3
  87. package/src/cli/db.ts +158 -0
  88. package/src/cli/diagnostics.ts +19 -0
  89. package/src/cli/doctor.ts +20 -0
  90. package/src/cli/index.ts +10 -0
  91. package/src/cli/update.ts +58 -0
  92. package/src/client/index.ts +1 -1
  93. package/src/client/routing/mount.tsx +18 -2
  94. package/src/client/ssr/markers.tsx +22 -0
  95. package/src/compiler/config.ts +88 -2
  96. package/src/compiler/docs.ts +47 -308
  97. package/src/compiler/index.ts +236 -32
  98. package/src/compiler/ssr-codegen.ts +1 -1
  99. package/src/compiler/template-build.ts +247 -46
  100. package/src/compiler/toil-docs.generated.ts +26 -0
  101. package/src/devserver/daemon/catalog.ts +120 -0
  102. package/src/devserver/daemon/cron.ts +87 -0
  103. package/src/devserver/daemon/host.ts +224 -0
  104. package/src/devserver/daemon/index.ts +349 -0
  105. package/src/devserver/db/catalog.ts +108 -0
  106. package/src/devserver/db/database.ts +1633 -0
  107. package/src/devserver/db/index.ts +18 -0
  108. package/src/devserver/db/routeKinds.ts +147 -0
  109. package/src/devserver/db/types.ts +139 -0
  110. package/src/devserver/email/index.ts +1 -1
  111. package/src/devserver/index.ts +31 -287
  112. package/src/devserver/mstore/store.ts +121 -0
  113. package/src/devserver/{host.ts → runtime/host.ts} +98 -7
  114. package/src/devserver/{module.ts → runtime/module.ts} +47 -1
  115. package/src/devserver/server.ts +393 -0
  116. package/src/devserver/ssr.ts +166 -0
  117. package/src/devserver/wasm/sections.ts +59 -0
  118. package/src/devserver/wasm/surface.ts +88 -0
  119. package/test/daemon-build.test.ts +198 -0
  120. package/test/daemon-catalog.test.ts +265 -0
  121. package/test/daemon-emulation.test.ts +216 -0
  122. package/test/db.test.ts +0 -0
  123. package/test/devserver-database.test.ts +510 -14
  124. package/test/devserver-pqauth.test.ts +1 -1
  125. package/test/devserver-secrets.test.ts +5 -1
  126. package/test/doctor.test.ts +13 -0
  127. package/test/email-preview.test.ts +6 -1
  128. package/test/example-guestbook.test.ts +43 -1
  129. package/test/fixtures/daemon-app.ts +56 -0
  130. package/test/global-setup.ts +17 -0
  131. package/test/pqauth-e2e.test.ts +1 -1
  132. package/test/ssr-render.test.ts +94 -27
  133. package/test/ssr-template.test.tsx +44 -1
  134. package/vitest.config.ts +3 -0
  135. package/build/devserver/database.d.ts +0 -8
  136. package/build/devserver/database.js +0 -418
  137. package/src/devserver/database.ts +0 -618
  138. /package/build/devserver/{dotenv.d.ts → config/dotenv.d.ts} +0 -0
  139. /package/build/devserver/{dotenv.js → config/dotenv.js} +0 -0
  140. /package/build/devserver/{env.d.ts → config/env.d.ts} +0 -0
  141. /package/build/devserver/{env.js → config/env.js} +0 -0
  142. /package/build/devserver/{ratelimit.d.ts → config/ratelimit.d.ts} +0 -0
  143. /package/build/devserver/{ratelimit.js → config/ratelimit.js} +0 -0
  144. /package/build/devserver/{cache.d.ts → http/cache.d.ts} +0 -0
  145. /package/build/devserver/{cache.js → http/cache.js} +0 -0
  146. /package/build/devserver/{envelope.d.ts → http/envelope.d.ts} +0 -0
  147. /package/build/devserver/{envelope.js → http/envelope.js} +0 -0
  148. /package/build/devserver/{proxy.d.ts → http/proxy.d.ts} +0 -0
  149. /package/build/devserver/{proxy.js → http/proxy.js} +0 -0
  150. /package/build/devserver/{crypto.d.ts → runtime/crypto.d.ts} +0 -0
  151. /package/build/devserver/{crypto.js → runtime/crypto.js} +0 -0
  152. /package/src/devserver/{dotenv.ts → config/dotenv.ts} +0 -0
  153. /package/src/devserver/{env.ts → config/env.ts} +0 -0
  154. /package/src/devserver/{ratelimit.ts → config/ratelimit.ts} +0 -0
  155. /package/src/devserver/{cache.ts → http/cache.ts} +0 -0
  156. /package/src/devserver/{envelope.ts → http/envelope.ts} +0 -0
  157. /package/src/devserver/{proxy.ts → http/proxy.ts} +0 -0
  158. /package/src/devserver/{crypto.ts → runtime/crypto.ts} +0 -0
@@ -0,0 +1,1032 @@
1
+ import fs from 'node:fs';
2
+ import { createHash } from 'node:crypto';
3
+ import path from 'node:path';
4
+ import { DataReader, DataWriter } from 'toiljs/io';
5
+ import { parseCatalog } from './catalog.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';
7
+ function mem(ref) {
8
+ if (!ref.memory)
9
+ throw new Error('data host import called before memory was bound');
10
+ return Buffer.from(ref.memory.buffer);
11
+ }
12
+ function readCopy(ref, ptr, len) {
13
+ const m = mem(ref);
14
+ if (ptr < 0 || len < 0 || ptr + len > m.length)
15
+ throw new Error(`data read out of bounds: ptr=${String(ptr)} len=${String(len)}`);
16
+ return Buffer.from(m.subarray(ptr, ptr + len));
17
+ }
18
+ function readKey(ref, ptr, len) {
19
+ if (len > MAX_KEY)
20
+ throw new Error('data: key too long');
21
+ return readCopy(ref, ptr, len);
22
+ }
23
+ function storeKey(collection, key) {
24
+ return collection + '\0' + key.toString('latin1');
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
+ }
86
+ function collOf(db, handle) {
87
+ return handle >= 0 && handle < db.handles.length ? db.handles[handle] : null;
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
+ }
173
+ export class DevDatabase {
174
+ store = new Map();
175
+ recordIdem = new Map();
176
+ uniqueIdem = new Map();
177
+ views = new Map();
178
+ members = new Map();
179
+ counters = new Map();
180
+ counterIdem = new Map();
181
+ events = new Map();
182
+ eventDedup = new Map();
183
+ capacity = new Map();
184
+ versions = new Map();
185
+ eventVersions = new Map();
186
+ memberVersions = new Map();
187
+ catalog = { kind: 'no-section' };
188
+ persistPath = null;
189
+ setCatalog(wasm) {
190
+ this.catalog = parseCatalog(wasm);
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
+ }
197
+ stampVersion(coll, sk) {
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
+ }
241
+ }
242
+ configurePersistence(filePath) {
243
+ this.persistPath = filePath;
244
+ this.load();
245
+ }
246
+ persist() {
247
+ if (this.persistPath === null)
248
+ return;
249
+ const snap = {
250
+ store: {},
251
+ recordIdem: {},
252
+ uniqueIdem: {},
253
+ views: {},
254
+ members: {},
255
+ counters: {},
256
+ counterIdem: {},
257
+ events: {},
258
+ eventDedup: {},
259
+ capacity: {},
260
+ };
261
+ for (const [k, v] of this.store)
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;
271
+ for (const [k, v] of this.views)
272
+ snap.views[k] = { v: v.toString('base64'), sv: this.versions.get(k) ?? 0 };
273
+ for (const [k, m] of this.members) {
274
+ const o = {};
275
+ const mv = this.memberVersions.get(k);
276
+ for (const [mk, mvb] of m)
277
+ o[mk] = { v: mvb.toString('base64'), sv: mv?.get(mk) ?? 0 };
278
+ snap.members[k] = o;
279
+ }
280
+ for (const [k, v] of this.counters)
281
+ snap.counters[k] = v.toString();
282
+ for (const [k, v] of this.counterIdem)
283
+ snap.counterIdem[k] = v.toString();
284
+ for (const [k, log] of this.events) {
285
+ const ver = this.eventVersions.get(k) ?? [];
286
+ snap.events[k] = log.map((b, i) => ({ v: b.toString('base64'), sv: ver[i] ?? 0 }));
287
+ }
288
+ for (const [k, s] of this.eventDedup)
289
+ snap.eventDedup[k] = [...s];
290
+ for (const [k, l] of this.capacity)
291
+ snap.capacity[k] = {
292
+ total: l.total.toString(),
293
+ nextId: l.nextId.toString(),
294
+ reservations: [...l.reservations].map(([id, r]) => [
295
+ id.toString(),
296
+ { amount: r.amount.toString(), expiresMs: r.expiresMs, confirmed: r.confirmed },
297
+ ]),
298
+ };
299
+ try {
300
+ fs.mkdirSync(path.dirname(this.persistPath), { recursive: true });
301
+ const tmp = `${this.persistPath}.${process.pid}.tmp`;
302
+ fs.writeFileSync(tmp, JSON.stringify(snap));
303
+ fs.renameSync(tmp, this.persistPath);
304
+ }
305
+ catch {
306
+ }
307
+ }
308
+ load() {
309
+ if (this.persistPath === null)
310
+ return;
311
+ let snap;
312
+ try {
313
+ snap = JSON.parse(fs.readFileSync(this.persistPath, 'utf8'));
314
+ }
315
+ catch {
316
+ return;
317
+ }
318
+ this.clear();
319
+ for (const [k, e] of Object.entries(snap.store ?? {})) {
320
+ this.store.set(k, Buffer.from(e.v, 'base64'));
321
+ this.versions.set(k, e.sv);
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);
333
+ for (const [k, e] of Object.entries(snap.views ?? {})) {
334
+ this.views.set(k, Buffer.from(e.v, 'base64'));
335
+ this.versions.set(k, e.sv);
336
+ }
337
+ for (const [k, m] of Object.entries(snap.members ?? {})) {
338
+ const map = new Map();
339
+ const ver = new Map();
340
+ for (const [mk, e] of Object.entries(m)) {
341
+ map.set(mk, Buffer.from(e.v, 'base64'));
342
+ ver.set(mk, e.sv);
343
+ }
344
+ this.members.set(k, map);
345
+ this.memberVersions.set(k, ver);
346
+ }
347
+ for (const [k, v] of Object.entries(snap.counters ?? {}))
348
+ this.counters.set(k, BigInt(v));
349
+ for (const [k, v] of Object.entries(snap.counterIdem ?? {}))
350
+ this.counterIdem.set(k, BigInt(v));
351
+ for (const [k, log] of Object.entries(snap.events ?? {})) {
352
+ this.events.set(k, log.map((e) => Buffer.from(e.v, 'base64')));
353
+ this.eventVersions.set(k, log.map((e) => e.sv));
354
+ }
355
+ for (const [k, ids] of Object.entries(snap.eventDedup ?? {}))
356
+ this.eventDedup.set(k, new Set(ids));
357
+ for (const [k, l] of Object.entries(snap.capacity ?? {})) {
358
+ const res = new Map();
359
+ for (const [id, r] of l.reservations)
360
+ res.set(BigInt(id), {
361
+ amount: BigInt(r.amount),
362
+ expiresMs: r.expiresMs,
363
+ confirmed: r.confirmed,
364
+ });
365
+ this.capacity.set(k, {
366
+ total: BigInt(l.total),
367
+ nextId: BigInt(l.nextId),
368
+ reservations: res,
369
+ });
370
+ }
371
+ }
372
+ clear() {
373
+ this.store.clear();
374
+ this.recordIdem.clear();
375
+ this.uniqueIdem.clear();
376
+ this.versions.clear();
377
+ this.views.clear();
378
+ this.members.clear();
379
+ this.memberVersions.clear();
380
+ this.counters.clear();
381
+ this.counterIdem.clear();
382
+ this.eventVersions.clear();
383
+ this.events.clear();
384
+ this.eventDedup.clear();
385
+ this.capacity.clear();
386
+ }
387
+ capLedger(sk) {
388
+ let l = this.capacity.get(sk);
389
+ if (l === undefined) {
390
+ l = { total: 0n, reservations: new Map(), nextId: 1n };
391
+ this.capacity.set(sk, l);
392
+ }
393
+ return l;
394
+ }
395
+ capPrune(l, nowMs) {
396
+ for (const [id, r] of l.reservations)
397
+ if (!r.confirmed && r.expiresMs <= nowMs)
398
+ l.reservations.delete(id);
399
+ }
400
+ capReserved(l) {
401
+ let sum = 0n;
402
+ for (const r of l.reservations.values())
403
+ sum += r.amount;
404
+ return sum;
405
+ }
406
+ resolveCollection(ref, db, namePtr, nameLen, outHandlePtr) {
407
+ if (nameLen < 0 || nameLen > MAX_NAME)
408
+ throw new Error('data: collection name too long');
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
+ }
433
+ const handle = db.handles.length;
434
+ db.handles.push(coll);
435
+ const m = mem(ref);
436
+ if (outHandlePtr < 0 || outHandlePtr + 4 > m.length)
437
+ throw new Error('data: resolve out-handle out of bounds');
438
+ m.writeUInt32LE(handle, outHandlePtr);
439
+ return 0;
440
+ }
441
+ get(ref, db, handle, keyPtr, keyLen) {
442
+ const coll = collForOp(db, handle, DbOp.Get, CollectionFamily.Record);
443
+ if (typeof coll === 'number')
444
+ return coll;
445
+ if (keyLen > MAX_KEY)
446
+ throw new Error('data: key too long');
447
+ const sk = storeKey(coll.name, readKey(ref, keyPtr, keyLen));
448
+ const v = this.store.get(sk);
449
+ if (v === undefined)
450
+ return ABSENT;
451
+ db.lastResult = v;
452
+ db.lastResultVersion = this.versions.get(sk) ?? 0;
453
+ return v.length;
454
+ }
455
+ getMany(ref, db, handle, keysPtr, keysLen) {
456
+ const coll = collForOp(db, handle, DbOp.GetMany, CollectionFamily.Record, CollectionFamily.View);
457
+ if (typeof coll === 'number')
458
+ return coll;
459
+ if (keysLen > MAX_VALUE)
460
+ throw new Error('data: keys blob too large');
461
+ const table = coll.family === CollectionFamily.View ? this.views : this.store;
462
+ const r = new DataReader(readCopy(ref, keysPtr, keysLen));
463
+ const count = r.readU32();
464
+ if (count > 1024)
465
+ return TOO_MANY_KEYS;
466
+ const w = new DataWriter();
467
+ w.writeU32(count);
468
+ for (let i = 0; i < count; i++) {
469
+ const key = r.readBytes();
470
+ if (key.length > MAX_KEY)
471
+ throw new Error('data: key too long');
472
+ const sk = storeKey(coll.name, Buffer.from(key));
473
+ const v = table.get(sk);
474
+ if (v === undefined) {
475
+ w.writeU8(0);
476
+ }
477
+ else {
478
+ w.writeU8(1)
479
+ .writeU32(this.versions.get(sk) ?? 0)
480
+ .writeBytes(v);
481
+ }
482
+ }
483
+ db.lastResult = Buffer.from(w.toBytes());
484
+ return db.lastResult.length;
485
+ }
486
+ exists(ref, db, handle, keyPtr, keyLen) {
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;
496
+ if (keyLen > MAX_KEY || valLen > MAX_VALUE)
497
+ throw new Error('data: key/value too large');
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);
515
+ }
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;
520
+ if (keyLen > MAX_KEY || patchLen > MAX_VALUE)
521
+ throw new Error('data: key/patch too large');
522
+ const key = readKey(ref, keyPtr, keyLen);
523
+ const v = readCopy(ref, patchPtr, patchLen);
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);
539
+ }
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);
551
+ this.store.delete(sk);
552
+ this.versions.delete(sk);
553
+ const outcome = { kind: 'unit' };
554
+ this.recordIdemFinish(coll, key, 'D', idem, requestHash, outcome);
555
+ return this.replayRecordOutcome(db, outcome);
556
+ }
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);
568
+ const v = this.store.get(sk);
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);
582
+ }
583
+ uniqueLookup(ref, db, handle, 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));
588
+ const v = this.store.get(sk);
589
+ if (v === undefined)
590
+ return ABSENT;
591
+ db.lastResult = v;
592
+ db.lastResultVersion = this.versions.get(sk) ?? 0;
593
+ return v.length;
594
+ }
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;
599
+ if (keyLen > MAX_KEY || valLen > MAX_VALUE)
600
+ throw new Error('data: key/value too large');
601
+ const sk = storeKey(coll.name, readKey(ref, keyPtr, keyLen));
602
+ const owner = readCopy(ref, valPtr, valLen);
603
+ const idem = readIdem(ref, idemPtr)?.toString('hex') ?? '';
604
+ const existing = this.store.get(sk);
605
+ if (existing === undefined) {
606
+ this.store.set(sk, owner);
607
+ this.uniqueIdem.set(sk, idem);
608
+ this.stampVersion(coll, sk);
609
+ return 0;
610
+ }
611
+ if (existing.equals(owner)) {
612
+ return (this.uniqueIdem.get(sk) ?? '') === idem ? 2 : CONFLICT;
613
+ }
614
+ db.lastResult = existing;
615
+ return 1;
616
+ }
617
+ uniqueRelease(ref, db, handle, keyPtr, keyLen, valPtr, valLen) {
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));
622
+ const existing = this.store.get(sk);
623
+ if (existing === undefined)
624
+ return 0;
625
+ if (!existing.equals(readCopy(ref, valPtr, valLen)))
626
+ return CONFLICT;
627
+ this.store.delete(sk);
628
+ this.uniqueIdem.delete(sk);
629
+ this.versions.delete(sk);
630
+ return 0;
631
+ }
632
+ membershipContains(ref, db, handle, setPtr, setLen, memberPtr, memberLen) {
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)));
637
+ if (set === undefined)
638
+ return 0;
639
+ return set.has(readCopy(ref, memberPtr, memberLen).toString('latin1')) ? 1 : 0;
640
+ }
641
+ membershipAdd(ref, db, handle, setPtr, setLen, memberPtr, memberLen) {
642
+ const coll = collForOp(db, handle, DbOp.MembershipAdd, CollectionFamily.Membership);
643
+ if (typeof coll === 'number')
644
+ return coll;
645
+ if (setLen > MAX_KEY || memberLen > MAX_VALUE)
646
+ throw new Error('data: set/member too large');
647
+ const sk = storeKey(coll.name, readKey(ref, setPtr, setLen));
648
+ const member = readCopy(ref, memberPtr, memberLen);
649
+ let set = this.members.get(sk);
650
+ if (set === undefined) {
651
+ set = new Map();
652
+ this.members.set(sk, set);
653
+ }
654
+ const ml = member.toString('latin1');
655
+ set.set(ml, member);
656
+ let mv = this.memberVersions.get(sk);
657
+ if (mv === undefined) {
658
+ mv = new Map();
659
+ this.memberVersions.set(sk, mv);
660
+ }
661
+ mv.set(ml, this.currentSchemaVersion(coll));
662
+ return 0;
663
+ }
664
+ membershipRemove(ref, db, handle, setPtr, setLen, memberPtr, memberLen) {
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));
669
+ const ml = readCopy(ref, memberPtr, memberLen).toString('latin1');
670
+ this.members.get(sk)?.delete(ml);
671
+ this.memberVersions.get(sk)?.delete(ml);
672
+ return 0;
673
+ }
674
+ membershipList(ref, db, handle, setPtr, setLen, limit) {
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));
679
+ const set = this.members.get(sk);
680
+ const mv = this.memberVersions.get(sk);
681
+ const n = Math.max(0, Math.min(limit, 0xffff));
682
+ const members = set === undefined ? [] : Array.from(set.values()).sort(Buffer.compare).slice(0, n);
683
+ const w = new DataWriter();
684
+ w.writeU32(members.length);
685
+ for (const m of members) {
686
+ w.writeU32(mv?.get(m.toString('latin1')) ?? 0).writeBytes(m);
687
+ }
688
+ db.lastResult = Buffer.from(w.toBytes());
689
+ return db.lastResult.length;
690
+ }
691
+ viewGet(ref, db, handle, 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));
696
+ const v = this.views.get(sk);
697
+ if (v === undefined)
698
+ return ABSENT;
699
+ db.lastResult = v;
700
+ db.lastResultVersion = this.versions.get(sk) ?? 0;
701
+ return v.length;
702
+ }
703
+ viewPublish(ref, db, handle, keyPtr, keyLen, valPtr, valLen) {
704
+ const coll = collForOp(db, handle, DbOp.ViewPublish, CollectionFamily.View);
705
+ if (typeof coll === 'number')
706
+ return coll;
707
+ if (keyLen > MAX_KEY || valLen > MAX_VALUE)
708
+ throw new Error('data: key/view too large');
709
+ const sk = storeKey(coll.name, readKey(ref, keyPtr, keyLen));
710
+ this.views.set(sk, readCopy(ref, valPtr, valLen));
711
+ this.stampVersion(coll, sk);
712
+ return 0;
713
+ }
714
+ counterGet(ref, db, handle, keyPtr, keyLen) {
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;
719
+ const out = Buffer.alloc(8);
720
+ out.writeBigInt64LE(sum);
721
+ db.lastResult = out;
722
+ return out.length;
723
+ }
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));
740
+ return 0;
741
+ }
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;
746
+ if (keyLen > MAX_KEY || evLen > MAX_VALUE)
747
+ throw new Error('data: key/event too large');
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
+ }
762
+ const log = this.events.get(sk);
763
+ const ev = readCopy(ref, evPtr, evLen);
764
+ const sv = this.currentSchemaVersion(coll);
765
+ if (log === undefined) {
766
+ this.events.set(sk, [ev]);
767
+ this.eventVersions.set(sk, [sv]);
768
+ }
769
+ else {
770
+ log.push(ev);
771
+ (this.eventVersions.get(sk) ?? this.eventVersions.set(sk, []).get(sk)).push(sv);
772
+ }
773
+ return 0;
774
+ }
775
+ appendOnce(ref, db, handle, keyPtr, keyLen, evidPtr, evidLen, evPtr, evLen) {
776
+ const coll = collForOp(db, handle, DbOp.AppendOnce, CollectionFamily.Events);
777
+ if (typeof coll === 'number')
778
+ return coll;
779
+ if (keyLen > MAX_KEY || evLen > MAX_VALUE)
780
+ throw new Error('data: key/event too large');
781
+ const sk = storeKey(coll.name, readKey(ref, keyPtr, keyLen));
782
+ const evid = readCopy(ref, evidPtr, evidLen).toString('latin1');
783
+ let seen = this.eventDedup.get(sk);
784
+ if (seen === undefined) {
785
+ seen = new Set();
786
+ this.eventDedup.set(sk, seen);
787
+ }
788
+ if (seen.has(evid))
789
+ return 0;
790
+ const ev = readCopy(ref, evPtr, evLen);
791
+ const sv = this.currentSchemaVersion(coll);
792
+ const log = this.events.get(sk);
793
+ if (log === undefined) {
794
+ this.events.set(sk, [ev]);
795
+ this.eventVersions.set(sk, [sv]);
796
+ }
797
+ else {
798
+ log.push(ev);
799
+ (this.eventVersions.get(sk) ?? this.eventVersions.set(sk, []).get(sk)).push(sv);
800
+ }
801
+ seen.add(evid);
802
+ return 1;
803
+ }
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;
808
+ if (keyLen > MAX_KEY || valLen > MAX_VALUE)
809
+ throw new Error('data: key/value too large');
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);
827
+ }
828
+ latest(ref, db, handle, keyPtr, keyLen, limit) {
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));
833
+ const log = this.events.get(sk) ?? [];
834
+ const vers = this.eventVersions.get(sk) ?? [];
835
+ const n = Math.max(0, Math.min(limit, 0xffff));
836
+ const start = Math.max(0, log.length - n);
837
+ const newest = log.slice(start).reverse();
838
+ const newestVers = vers.slice(start).reverse();
839
+ const w = new DataWriter();
840
+ w.writeU32(newest.length);
841
+ for (let i = 0; i < newest.length; i++) {
842
+ w.writeU32(newestVers[i] ?? 0).writeBytes(newest[i]);
843
+ }
844
+ db.lastResult = Buffer.from(w.toBytes());
845
+ return db.lastResult.length;
846
+ }
847
+ capacitySetTotal(ref, db, handle, keyPtr, keyLen, total) {
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)));
852
+ const t = BigInt(total);
853
+ l.total = satI64(t < 0n ? 0n : t);
854
+ return 0;
855
+ }
856
+ capacityAvailable(ref, db, handle, 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)));
861
+ this.capPrune(l, Date.now());
862
+ const avail = l.total - this.capReserved(l);
863
+ const out = Buffer.alloc(8);
864
+ out.writeBigInt64LE(avail < 0n ? 0n : avail);
865
+ db.lastResult = out;
866
+ return out.length;
867
+ }
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;
872
+ const want = BigInt(amount);
873
+ if (want <= 0n)
874
+ return CODEC_ERR;
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));
880
+ const now = Date.now();
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
+ }
893
+ if (l.total - this.capReserved(l) < want || l.reservations.size >= MAX_RESERVATIONS)
894
+ return ABSENT;
895
+ const id = requestedId ?? l.nextId++;
896
+ l.reservations.set(id, { amount: want, expiresMs: now + ttl, confirmed: false });
897
+ const out = Buffer.alloc(8);
898
+ out.writeBigUInt64LE(id);
899
+ db.lastResult = out;
900
+ return out.length;
901
+ }
902
+ capacityConfirm(ref, db, handle, keyPtr, keyLen, reservationId) {
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)));
907
+ this.capPrune(l, Date.now());
908
+ const r = l.reservations.get(BigInt(reservationId));
909
+ if (r === undefined)
910
+ return 0;
911
+ r.confirmed = true;
912
+ return 1;
913
+ }
914
+ capacityCancel(ref, db, handle, keyPtr, keyLen, reservationId) {
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)));
919
+ this.capPrune(l, Date.now());
920
+ const id = BigInt(reservationId);
921
+ const r = l.reservations.get(id);
922
+ if (r === undefined || r.confirmed)
923
+ return 0;
924
+ l.reservations.delete(id);
925
+ return 1;
926
+ }
927
+ takeResult(ref, db, outPtr, outCap) {
928
+ const v = db.lastResult;
929
+ if (v === null)
930
+ return 0;
931
+ if (v.length > outCap)
932
+ return TOO_SMALL;
933
+ const m = mem(ref);
934
+ if (outPtr < 0 || outPtr + v.length > m.length)
935
+ throw new Error('data: take_result out of bounds');
936
+ v.copy(m, outPtr);
937
+ db.lastResult = null;
938
+ return v.length;
939
+ }
940
+ resultSchemaVersion(db) {
941
+ return BigInt(db.lastResultVersion);
942
+ }
943
+ resetForTests() {
944
+ this.clear();
945
+ this.catalog = { kind: 'no-section' };
946
+ this.persistPath = null;
947
+ }
948
+ setCatalogForTests(entries) {
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 };
980
+ }
981
+ }
982
+ export const devDb = new DevDatabase();
983
+ export function setDbCatalog(wasm) {
984
+ devDb.setCatalog(wasm);
985
+ }
986
+ export function configureDbPersistence(filePath) {
987
+ devDb.configurePersistence(filePath);
988
+ }
989
+ export function persistDb() {
990
+ devDb.persist();
991
+ }
992
+ export function buildDatabaseImports(ref, db) {
993
+ return {
994
+ 'data.resolve_collection': (namePtr, nameLen, outHandlePtr) => devDb.resolveCollection(ref, db, namePtr, nameLen, outHandlePtr),
995
+ 'data.get': (handle, keyPtr, keyLen) => devDb.get(ref, db, handle, keyPtr, keyLen),
996
+ 'data.get_many': (handle, keysPtr, keysLen) => devDb.getMany(ref, db, handle, keysPtr, keysLen),
997
+ 'data.exists': (handle, keyPtr, keyLen) => devDb.exists(ref, db, 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),
1002
+ 'data.unique_lookup': (handle, keyPtr, keyLen) => devDb.uniqueLookup(ref, db, handle, keyPtr, keyLen),
1003
+ 'data.unique_claim': (handle, keyPtr, keyLen, valPtr, valLen, idemPtr) => devDb.uniqueClaim(ref, db, handle, keyPtr, keyLen, valPtr, valLen, idemPtr),
1004
+ 'data.unique_release': (handle, keyPtr, keyLen, valPtr, valLen, _idemPtr) => devDb.uniqueRelease(ref, db, handle, keyPtr, keyLen, valPtr, valLen),
1005
+ 'data.membership_contains': (handle, setPtr, setLen, memberPtr, memberLen) => devDb.membershipContains(ref, db, handle, setPtr, setLen, memberPtr, memberLen),
1006
+ 'data.membership_add': (handle, setPtr, setLen, memberPtr, memberLen, _idemPtr) => devDb.membershipAdd(ref, db, handle, setPtr, setLen, memberPtr, memberLen),
1007
+ 'data.membership_remove': (handle, setPtr, setLen, memberPtr, memberLen, _idemPtr) => devDb.membershipRemove(ref, db, handle, setPtr, setLen, memberPtr, memberLen),
1008
+ 'data.membership_list': (handle, setPtr, setLen, limit) => devDb.membershipList(ref, db, handle, setPtr, setLen, limit),
1009
+ 'data.view_get': (handle, keyPtr, keyLen) => devDb.viewGet(ref, db, handle, keyPtr, keyLen),
1010
+ 'data.view_publish': (handle, keyPtr, keyLen, valPtr, valLen, _idemPtr) => devDb.viewPublish(ref, db, handle, keyPtr, keyLen, valPtr, valLen),
1011
+ 'data.counter_get': (handle, keyPtr, keyLen) => devDb.counterGet(ref, db, handle, keyPtr, keyLen),
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),
1014
+ 'data.append_once': (handle, keyPtr, keyLen, evidPtr, evidLen, evPtr, evLen) => devDb.appendOnce(ref, db, handle, keyPtr, keyLen, evidPtr, evidLen, evPtr, evLen),
1015
+ 'data.enqueue': (handle, keyPtr, keyLen, valPtr, valLen, idemPtr) => devDb.enqueue(ref, db, handle, keyPtr, keyLen, valPtr, valLen, idemPtr),
1016
+ 'data.latest': (handle, keyPtr, keyLen, limit) => devDb.latest(ref, db, handle, keyPtr, keyLen, limit),
1017
+ 'data.capacity_set_total': (handle, keyPtr, keyLen, total, _idemPtr) => devDb.capacitySetTotal(ref, db, handle, keyPtr, keyLen, total),
1018
+ 'data.capacity_available': (handle, keyPtr, keyLen) => devDb.capacityAvailable(ref, db, handle, keyPtr, keyLen),
1019
+ 'data.capacity_reserve': (handle, keyPtr, keyLen, amount, ttlMs, idemPtr) => devDb.capacityReserve(ref, db, handle, keyPtr, keyLen, amount, ttlMs, idemPtr),
1020
+ 'data.capacity_confirm': (handle, keyPtr, keyLen, reservationId, _idemPtr) => devDb.capacityConfirm(ref, db, handle, keyPtr, keyLen, reservationId),
1021
+ 'data.capacity_cancel': (handle, keyPtr, keyLen, reservationId, _idemPtr) => devDb.capacityCancel(ref, db, handle, keyPtr, keyLen, reservationId),
1022
+ 'data.take_result': (outPtr, outCap) => devDb.takeResult(ref, db, outPtr, outCap),
1023
+ 'data.result_schema_version': () => devDb.resultSchemaVersion(db),
1024
+ 'data.write_allowed': () => (kindAllows(db.functionKind, DbOp.Patch) ? 1 : 0),
1025
+ };
1026
+ }
1027
+ export function __resetDbForTests() {
1028
+ devDb.resetForTests();
1029
+ }
1030
+ export function __setDbCatalogForTests(entries) {
1031
+ devDb.setCatalogForTests(entries);
1032
+ }