toiljs 0.0.58 → 0.0.60

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 (72) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/build/cli/.tsbuildinfo +1 -1
  3. package/build/cli/index.js +309 -116
  4. package/build/compiler/.tsbuildinfo +1 -1
  5. package/build/devserver/.tsbuildinfo +1 -1
  6. package/build/devserver/db/catalog.d.ts +1 -0
  7. package/build/devserver/db/catalog.js +80 -0
  8. package/build/devserver/db/database.d.ts +64 -0
  9. package/build/devserver/db/database.js +662 -0
  10. package/build/devserver/db/index.d.ts +3 -0
  11. package/build/devserver/db/index.js +3 -0
  12. package/build/devserver/db/types.d.ts +58 -0
  13. package/build/devserver/db/types.js +20 -0
  14. package/build/devserver/email/index.js +1 -1
  15. package/build/devserver/index.d.ts +9 -24
  16. package/build/devserver/index.js +4 -165
  17. package/build/devserver/{host.d.ts → runtime/host.d.ts} +1 -1
  18. package/build/devserver/{host.js → runtime/host.js} +6 -6
  19. package/build/devserver/{module.d.ts → runtime/module.d.ts} +1 -1
  20. package/build/devserver/{module.js → runtime/module.js} +8 -1
  21. package/build/devserver/server.d.ts +17 -0
  22. package/build/devserver/server.js +164 -0
  23. package/docs/time.md +2 -2
  24. package/examples/basic/server/migrations/GuestEntry.migration.ts +39 -0
  25. package/package.json +5 -2
  26. package/server/runtime/time.ts +3 -3
  27. package/src/cli/create.ts +38 -1
  28. package/src/cli/db.ts +158 -0
  29. package/src/cli/diagnostics.ts +19 -0
  30. package/src/cli/doctor.ts +20 -0
  31. package/src/cli/index.ts +10 -0
  32. package/src/cli/update.ts +58 -0
  33. package/src/devserver/db/catalog.ts +100 -0
  34. package/src/devserver/db/database.ts +1169 -0
  35. package/src/devserver/db/index.ts +18 -0
  36. package/src/devserver/db/types.ts +76 -0
  37. package/src/devserver/email/index.ts +1 -1
  38. package/src/devserver/index.ts +19 -287
  39. package/src/devserver/{host.ts → runtime/host.ts} +6 -6
  40. package/src/devserver/{module.ts → runtime/module.ts} +13 -1
  41. package/src/devserver/server.ts +292 -0
  42. package/test/db.test.ts +0 -0
  43. package/test/devserver-database.test.ts +114 -9
  44. package/test/devserver-pqauth.test.ts +1 -1
  45. package/test/devserver-secrets.test.ts +5 -1
  46. package/test/doctor.test.ts +13 -0
  47. package/test/example-guestbook.test.ts +43 -1
  48. package/test/pqauth-e2e.test.ts +1 -1
  49. package/build/devserver/database.d.ts +0 -8
  50. package/build/devserver/database.js +0 -418
  51. package/src/devserver/database.ts +0 -618
  52. /package/build/devserver/{dotenv.d.ts → config/dotenv.d.ts} +0 -0
  53. /package/build/devserver/{dotenv.js → config/dotenv.js} +0 -0
  54. /package/build/devserver/{env.d.ts → config/env.d.ts} +0 -0
  55. /package/build/devserver/{env.js → config/env.js} +0 -0
  56. /package/build/devserver/{ratelimit.d.ts → config/ratelimit.d.ts} +0 -0
  57. /package/build/devserver/{ratelimit.js → config/ratelimit.js} +0 -0
  58. /package/build/devserver/{cache.d.ts → http/cache.d.ts} +0 -0
  59. /package/build/devserver/{cache.js → http/cache.js} +0 -0
  60. /package/build/devserver/{envelope.d.ts → http/envelope.d.ts} +0 -0
  61. /package/build/devserver/{envelope.js → http/envelope.js} +0 -0
  62. /package/build/devserver/{proxy.d.ts → http/proxy.d.ts} +0 -0
  63. /package/build/devserver/{proxy.js → http/proxy.js} +0 -0
  64. /package/build/devserver/{crypto.d.ts → runtime/crypto.d.ts} +0 -0
  65. /package/build/devserver/{crypto.js → runtime/crypto.js} +0 -0
  66. /package/src/devserver/{dotenv.ts → config/dotenv.ts} +0 -0
  67. /package/src/devserver/{env.ts → config/env.ts} +0 -0
  68. /package/src/devserver/{ratelimit.ts → config/ratelimit.ts} +0 -0
  69. /package/src/devserver/{cache.ts → http/cache.ts} +0 -0
  70. /package/src/devserver/{envelope.ts → http/envelope.ts} +0 -0
  71. /package/src/devserver/{proxy.ts → http/proxy.ts} +0 -0
  72. /package/src/devserver/{crypto.ts → runtime/crypto.ts} +0 -0
@@ -0,0 +1,662 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { DataReader, DataWriter } from 'toiljs/io';
4
+ import { parseCatalog } from './catalog.js';
5
+ import { ABSENT, ALREADY_EXISTS, CODEC_ERR, CONFLICT, INVALID_HANDLE, MAX_KEY, MAX_NAME, MAX_RESERVATION_TTL_MS, MAX_RESERVATIONS, MAX_VALUE, satI64, TOO_MANY_KEYS, TOO_SMALL, } from './types.js';
6
+ function mem(ref) {
7
+ if (!ref.memory)
8
+ throw new Error('data host import called before memory was bound');
9
+ return Buffer.from(ref.memory.buffer);
10
+ }
11
+ function readCopy(ref, ptr, len) {
12
+ const m = mem(ref);
13
+ if (ptr < 0 || len < 0 || ptr + len > m.length)
14
+ throw new Error(`data read out of bounds: ptr=${String(ptr)} len=${String(len)}`);
15
+ return Buffer.from(m.subarray(ptr, ptr + len));
16
+ }
17
+ function readKey(ref, ptr, len) {
18
+ if (len > MAX_KEY)
19
+ throw new Error('data: key too long');
20
+ return readCopy(ref, ptr, len);
21
+ }
22
+ function storeKey(collection, key) {
23
+ return collection + '\0' + key.toString('latin1');
24
+ }
25
+ function collOf(db, handle) {
26
+ return handle >= 0 && handle < db.handles.length ? db.handles[handle] : null;
27
+ }
28
+ export class DevDatabase {
29
+ store = new Map();
30
+ views = new Map();
31
+ members = new Map();
32
+ counters = new Map();
33
+ events = new Map();
34
+ eventDedup = new Map();
35
+ capacity = new Map();
36
+ versions = new Map();
37
+ eventVersions = new Map();
38
+ memberVersions = new Map();
39
+ catalog = new Map();
40
+ persistPath = null;
41
+ setCatalog(wasm) {
42
+ this.catalog = parseCatalog(wasm);
43
+ }
44
+ stampVersion(coll, sk) {
45
+ this.versions.set(sk, this.catalog.get(coll) ?? 0);
46
+ }
47
+ configurePersistence(filePath) {
48
+ this.persistPath = filePath;
49
+ this.load();
50
+ }
51
+ persist() {
52
+ if (this.persistPath === null)
53
+ return;
54
+ const snap = {
55
+ store: {},
56
+ views: {},
57
+ members: {},
58
+ counters: {},
59
+ events: {},
60
+ eventDedup: {},
61
+ capacity: {},
62
+ };
63
+ for (const [k, v] of this.store)
64
+ snap.store[k] = { v: v.toString('base64'), sv: this.versions.get(k) ?? 0 };
65
+ for (const [k, v] of this.views)
66
+ snap.views[k] = { v: v.toString('base64'), sv: this.versions.get(k) ?? 0 };
67
+ for (const [k, m] of this.members) {
68
+ const o = {};
69
+ const mv = this.memberVersions.get(k);
70
+ for (const [mk, mvb] of m)
71
+ o[mk] = { v: mvb.toString('base64'), sv: mv?.get(mk) ?? 0 };
72
+ snap.members[k] = o;
73
+ }
74
+ for (const [k, v] of this.counters)
75
+ snap.counters[k] = v.toString();
76
+ for (const [k, log] of this.events) {
77
+ const ver = this.eventVersions.get(k) ?? [];
78
+ snap.events[k] = log.map((b, i) => ({ v: b.toString('base64'), sv: ver[i] ?? 0 }));
79
+ }
80
+ for (const [k, s] of this.eventDedup)
81
+ snap.eventDedup[k] = [...s];
82
+ for (const [k, l] of this.capacity)
83
+ snap.capacity[k] = {
84
+ total: l.total.toString(),
85
+ nextId: l.nextId.toString(),
86
+ reservations: [...l.reservations].map(([id, r]) => [
87
+ id.toString(),
88
+ { amount: r.amount.toString(), expiresMs: r.expiresMs, confirmed: r.confirmed },
89
+ ]),
90
+ };
91
+ try {
92
+ fs.mkdirSync(path.dirname(this.persistPath), { recursive: true });
93
+ const tmp = `${this.persistPath}.${process.pid}.tmp`;
94
+ fs.writeFileSync(tmp, JSON.stringify(snap));
95
+ fs.renameSync(tmp, this.persistPath);
96
+ }
97
+ catch {
98
+ }
99
+ }
100
+ load() {
101
+ if (this.persistPath === null)
102
+ return;
103
+ let snap;
104
+ try {
105
+ snap = JSON.parse(fs.readFileSync(this.persistPath, 'utf8'));
106
+ }
107
+ catch {
108
+ return;
109
+ }
110
+ this.clear();
111
+ for (const [k, e] of Object.entries(snap.store ?? {})) {
112
+ this.store.set(k, Buffer.from(e.v, 'base64'));
113
+ this.versions.set(k, e.sv);
114
+ }
115
+ for (const [k, e] of Object.entries(snap.views ?? {})) {
116
+ this.views.set(k, Buffer.from(e.v, 'base64'));
117
+ this.versions.set(k, e.sv);
118
+ }
119
+ for (const [k, m] of Object.entries(snap.members ?? {})) {
120
+ const map = new Map();
121
+ const ver = new Map();
122
+ for (const [mk, e] of Object.entries(m)) {
123
+ map.set(mk, Buffer.from(e.v, 'base64'));
124
+ ver.set(mk, e.sv);
125
+ }
126
+ this.members.set(k, map);
127
+ this.memberVersions.set(k, ver);
128
+ }
129
+ for (const [k, v] of Object.entries(snap.counters ?? {}))
130
+ this.counters.set(k, BigInt(v));
131
+ for (const [k, log] of Object.entries(snap.events ?? {})) {
132
+ this.events.set(k, log.map((e) => Buffer.from(e.v, 'base64')));
133
+ this.eventVersions.set(k, log.map((e) => e.sv));
134
+ }
135
+ for (const [k, ids] of Object.entries(snap.eventDedup ?? {}))
136
+ this.eventDedup.set(k, new Set(ids));
137
+ for (const [k, l] of Object.entries(snap.capacity ?? {})) {
138
+ const res = new Map();
139
+ for (const [id, r] of l.reservations)
140
+ res.set(BigInt(id), {
141
+ amount: BigInt(r.amount),
142
+ expiresMs: r.expiresMs,
143
+ confirmed: r.confirmed,
144
+ });
145
+ this.capacity.set(k, {
146
+ total: BigInt(l.total),
147
+ nextId: BigInt(l.nextId),
148
+ reservations: res,
149
+ });
150
+ }
151
+ }
152
+ clear() {
153
+ this.store.clear();
154
+ this.versions.clear();
155
+ this.views.clear();
156
+ this.members.clear();
157
+ this.memberVersions.clear();
158
+ this.counters.clear();
159
+ this.eventVersions.clear();
160
+ this.events.clear();
161
+ this.eventDedup.clear();
162
+ this.capacity.clear();
163
+ }
164
+ capLedger(sk) {
165
+ let l = this.capacity.get(sk);
166
+ if (l === undefined) {
167
+ l = { total: 0n, reservations: new Map(), nextId: 1n };
168
+ this.capacity.set(sk, l);
169
+ }
170
+ return l;
171
+ }
172
+ capPrune(l, nowMs) {
173
+ for (const [id, r] of l.reservations)
174
+ if (!r.confirmed && r.expiresMs <= nowMs)
175
+ l.reservations.delete(id);
176
+ }
177
+ capReserved(l) {
178
+ let sum = 0n;
179
+ for (const r of l.reservations.values())
180
+ sum += r.amount;
181
+ return sum;
182
+ }
183
+ resolveCollection(ref, db, namePtr, nameLen, outHandlePtr) {
184
+ if (nameLen < 0 || nameLen > MAX_NAME)
185
+ throw new Error('data: collection name too long');
186
+ const name = readCopy(ref, namePtr, nameLen).toString('utf8');
187
+ const handle = db.handles.length;
188
+ db.handles.push(name);
189
+ const m = mem(ref);
190
+ if (outHandlePtr < 0 || outHandlePtr + 4 > m.length)
191
+ throw new Error('data: resolve out-handle out of bounds');
192
+ m.writeUInt32LE(handle, outHandlePtr);
193
+ return 0;
194
+ }
195
+ get(ref, db, handle, keyPtr, keyLen) {
196
+ const coll = collOf(db, handle);
197
+ if (coll === null)
198
+ return INVALID_HANDLE;
199
+ if (keyLen > MAX_KEY)
200
+ throw new Error('data: key too long');
201
+ const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
202
+ const v = this.store.get(sk);
203
+ if (v === undefined)
204
+ return ABSENT;
205
+ db.lastResult = v;
206
+ db.lastResultVersion = this.versions.get(sk) ?? 0;
207
+ return v.length;
208
+ }
209
+ getMany(ref, db, handle, keysPtr, keysLen) {
210
+ const coll = collOf(db, handle);
211
+ if (coll === null)
212
+ return INVALID_HANDLE;
213
+ if (keysLen > MAX_VALUE)
214
+ throw new Error('data: keys blob too large');
215
+ const r = new DataReader(readCopy(ref, keysPtr, keysLen));
216
+ const count = r.readU32();
217
+ if (count > 1024)
218
+ return TOO_MANY_KEYS;
219
+ const w = new DataWriter();
220
+ w.writeU32(count);
221
+ for (let i = 0; i < count; i++) {
222
+ const key = r.readBytes();
223
+ if (key.length > MAX_KEY)
224
+ throw new Error('data: key too long');
225
+ const sk = storeKey(coll, Buffer.from(key));
226
+ const v = this.store.get(sk);
227
+ if (v === undefined) {
228
+ w.writeU8(0);
229
+ }
230
+ else {
231
+ w.writeU8(1).writeU32(this.versions.get(sk) ?? 0).writeBytes(v);
232
+ }
233
+ }
234
+ db.lastResult = Buffer.from(w.toBytes());
235
+ return db.lastResult.length;
236
+ }
237
+ exists(ref, db, handle, keyPtr, keyLen) {
238
+ const coll = collOf(db, handle);
239
+ if (coll === null)
240
+ return INVALID_HANDLE;
241
+ return this.store.has(storeKey(coll, readKey(ref, keyPtr, keyLen))) ? 1 : 0;
242
+ }
243
+ create(ref, db, handle, keyPtr, keyLen, valPtr, valLen) {
244
+ const coll = collOf(db, handle);
245
+ if (coll === null)
246
+ return INVALID_HANDLE;
247
+ if (keyLen > MAX_KEY || valLen > MAX_VALUE)
248
+ throw new Error('data: key/value too large');
249
+ const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
250
+ if (this.store.has(sk))
251
+ return ALREADY_EXISTS;
252
+ this.store.set(sk, readCopy(ref, valPtr, valLen));
253
+ this.stampVersion(coll, sk);
254
+ return 0;
255
+ }
256
+ patch(ref, db, handle, keyPtr, keyLen, patchPtr, patchLen) {
257
+ const coll = collOf(db, handle);
258
+ if (coll === null)
259
+ return INVALID_HANDLE;
260
+ if (keyLen > MAX_KEY || patchLen > MAX_VALUE)
261
+ throw new Error('data: key/patch too large');
262
+ const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
263
+ if (!this.store.has(sk))
264
+ return ABSENT;
265
+ const v = readCopy(ref, patchPtr, patchLen);
266
+ this.store.set(sk, v);
267
+ this.stampVersion(coll, sk);
268
+ db.lastResult = v;
269
+ db.lastResultVersion = -1;
270
+ return v.length;
271
+ }
272
+ delete(ref, db, handle, keyPtr, keyLen) {
273
+ const coll = collOf(db, handle);
274
+ if (coll === null)
275
+ return INVALID_HANDLE;
276
+ const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
277
+ this.store.delete(sk);
278
+ this.versions.delete(sk);
279
+ return 0;
280
+ }
281
+ getDelete(ref, db, handle, keyPtr, keyLen) {
282
+ const coll = collOf(db, handle);
283
+ if (coll === null)
284
+ return INVALID_HANDLE;
285
+ const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
286
+ const v = this.store.get(sk);
287
+ if (v === undefined)
288
+ return ABSENT;
289
+ db.lastResultVersion = this.versions.get(sk) ?? 0;
290
+ this.store.delete(sk);
291
+ this.versions.delete(sk);
292
+ db.lastResult = v;
293
+ return v.length;
294
+ }
295
+ uniqueLookup(ref, db, handle, keyPtr, keyLen) {
296
+ const coll = collOf(db, handle);
297
+ if (coll === null)
298
+ return INVALID_HANDLE;
299
+ const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
300
+ const v = this.store.get(sk);
301
+ if (v === undefined)
302
+ return ABSENT;
303
+ db.lastResult = v;
304
+ db.lastResultVersion = this.versions.get(sk) ?? 0;
305
+ return v.length;
306
+ }
307
+ uniqueClaim(ref, db, handle, keyPtr, keyLen, valPtr, valLen) {
308
+ const coll = collOf(db, handle);
309
+ if (coll === null)
310
+ return INVALID_HANDLE;
311
+ if (keyLen > MAX_KEY || valLen > MAX_VALUE)
312
+ throw new Error('data: key/value too large');
313
+ const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
314
+ const owner = readCopy(ref, valPtr, valLen);
315
+ const existing = this.store.get(sk);
316
+ if (existing === undefined) {
317
+ this.store.set(sk, owner);
318
+ this.stampVersion(coll, sk);
319
+ return 0;
320
+ }
321
+ if (existing.equals(owner))
322
+ return 2;
323
+ db.lastResult = existing;
324
+ return 1;
325
+ }
326
+ uniqueRelease(ref, db, handle, keyPtr, keyLen, valPtr, valLen) {
327
+ const coll = collOf(db, handle);
328
+ if (coll === null)
329
+ return INVALID_HANDLE;
330
+ const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
331
+ const existing = this.store.get(sk);
332
+ if (existing === undefined)
333
+ return 0;
334
+ if (!existing.equals(readCopy(ref, valPtr, valLen)))
335
+ return CONFLICT;
336
+ this.store.delete(sk);
337
+ this.versions.delete(sk);
338
+ return 0;
339
+ }
340
+ membershipContains(ref, db, handle, setPtr, setLen, memberPtr, memberLen) {
341
+ const coll = collOf(db, handle);
342
+ if (coll === null)
343
+ return INVALID_HANDLE;
344
+ const set = this.members.get(storeKey(coll, readKey(ref, setPtr, setLen)));
345
+ if (set === undefined)
346
+ return 0;
347
+ return set.has(readCopy(ref, memberPtr, memberLen).toString('latin1')) ? 1 : 0;
348
+ }
349
+ membershipAdd(ref, db, handle, setPtr, setLen, memberPtr, memberLen) {
350
+ const coll = collOf(db, handle);
351
+ if (coll === null)
352
+ return INVALID_HANDLE;
353
+ if (setLen > MAX_KEY || memberLen > MAX_VALUE)
354
+ throw new Error('data: set/member too large');
355
+ const sk = storeKey(coll, readKey(ref, setPtr, setLen));
356
+ const member = readCopy(ref, memberPtr, memberLen);
357
+ let set = this.members.get(sk);
358
+ if (set === undefined) {
359
+ set = new Map();
360
+ this.members.set(sk, set);
361
+ }
362
+ const ml = member.toString('latin1');
363
+ set.set(ml, member);
364
+ let mv = this.memberVersions.get(sk);
365
+ if (mv === undefined) {
366
+ mv = new Map();
367
+ this.memberVersions.set(sk, mv);
368
+ }
369
+ mv.set(ml, this.catalog.get(coll) ?? 0);
370
+ return 0;
371
+ }
372
+ membershipRemove(ref, db, handle, setPtr, setLen, memberPtr, memberLen) {
373
+ const coll = collOf(db, handle);
374
+ if (coll === null)
375
+ return INVALID_HANDLE;
376
+ const sk = storeKey(coll, readKey(ref, setPtr, setLen));
377
+ const ml = readCopy(ref, memberPtr, memberLen).toString('latin1');
378
+ this.members.get(sk)?.delete(ml);
379
+ this.memberVersions.get(sk)?.delete(ml);
380
+ return 0;
381
+ }
382
+ membershipList(ref, db, handle, setPtr, setLen, limit) {
383
+ const coll = collOf(db, handle);
384
+ if (coll === null)
385
+ return INVALID_HANDLE;
386
+ const sk = storeKey(coll, readKey(ref, setPtr, setLen));
387
+ const set = this.members.get(sk);
388
+ const mv = this.memberVersions.get(sk);
389
+ const n = Math.max(0, Math.min(limit, 0xffff));
390
+ const members = set === undefined ? [] : Array.from(set.values()).sort(Buffer.compare).slice(0, n);
391
+ const w = new DataWriter();
392
+ w.writeU32(members.length);
393
+ for (const m of members) {
394
+ w.writeU32(mv?.get(m.toString('latin1')) ?? 0).writeBytes(m);
395
+ }
396
+ db.lastResult = Buffer.from(w.toBytes());
397
+ return db.lastResult.length;
398
+ }
399
+ viewGet(ref, db, handle, keyPtr, keyLen) {
400
+ const coll = collOf(db, handle);
401
+ if (coll === null)
402
+ return INVALID_HANDLE;
403
+ const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
404
+ const v = this.views.get(sk);
405
+ if (v === undefined)
406
+ return ABSENT;
407
+ db.lastResult = v;
408
+ db.lastResultVersion = this.versions.get(sk) ?? 0;
409
+ return v.length;
410
+ }
411
+ viewPublish(ref, db, handle, keyPtr, keyLen, valPtr, valLen) {
412
+ const coll = collOf(db, handle);
413
+ if (coll === null)
414
+ return INVALID_HANDLE;
415
+ if (keyLen > MAX_KEY || valLen > MAX_VALUE)
416
+ throw new Error('data: key/view too large');
417
+ const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
418
+ this.views.set(sk, readCopy(ref, valPtr, valLen));
419
+ this.stampVersion(coll, sk);
420
+ return 0;
421
+ }
422
+ counterGet(ref, db, handle, keyPtr, keyLen) {
423
+ const coll = collOf(db, handle);
424
+ if (coll === null)
425
+ return INVALID_HANDLE;
426
+ const sum = this.counters.get(storeKey(coll, readKey(ref, keyPtr, keyLen))) ?? 0n;
427
+ const out = Buffer.alloc(8);
428
+ out.writeBigInt64LE(sum);
429
+ db.lastResult = out;
430
+ return out.length;
431
+ }
432
+ counterAdd(ref, db, handle, keyPtr, keyLen, delta) {
433
+ const coll = collOf(db, handle);
434
+ if (coll === null)
435
+ return INVALID_HANDLE;
436
+ const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
437
+ this.counters.set(sk, satI64((this.counters.get(sk) ?? 0n) + BigInt(delta)));
438
+ return 0;
439
+ }
440
+ append(ref, db, handle, keyPtr, keyLen, evPtr, evLen) {
441
+ const coll = collOf(db, handle);
442
+ if (coll === null)
443
+ return INVALID_HANDLE;
444
+ if (keyLen > MAX_KEY || evLen > MAX_VALUE)
445
+ throw new Error('data: key/event too large');
446
+ const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
447
+ const log = this.events.get(sk);
448
+ const ev = readCopy(ref, evPtr, evLen);
449
+ const sv = this.catalog.get(coll) ?? 0;
450
+ if (log === undefined) {
451
+ this.events.set(sk, [ev]);
452
+ this.eventVersions.set(sk, [sv]);
453
+ }
454
+ else {
455
+ log.push(ev);
456
+ (this.eventVersions.get(sk) ?? this.eventVersions.set(sk, []).get(sk)).push(sv);
457
+ }
458
+ return 0;
459
+ }
460
+ appendOnce(ref, db, handle, keyPtr, keyLen, evidPtr, evidLen, evPtr, evLen) {
461
+ const coll = collOf(db, handle);
462
+ if (coll === null)
463
+ return INVALID_HANDLE;
464
+ if (keyLen > MAX_KEY || evLen > MAX_VALUE)
465
+ throw new Error('data: key/event too large');
466
+ const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
467
+ const evid = readCopy(ref, evidPtr, evidLen).toString('latin1');
468
+ let seen = this.eventDedup.get(sk);
469
+ if (seen === undefined) {
470
+ seen = new Set();
471
+ this.eventDedup.set(sk, seen);
472
+ }
473
+ if (seen.has(evid))
474
+ return 0;
475
+ const ev = readCopy(ref, evPtr, evLen);
476
+ const sv = this.catalog.get(coll) ?? 0;
477
+ const log = this.events.get(sk);
478
+ if (log === undefined) {
479
+ this.events.set(sk, [ev]);
480
+ this.eventVersions.set(sk, [sv]);
481
+ }
482
+ else {
483
+ log.push(ev);
484
+ (this.eventVersions.get(sk) ?? this.eventVersions.set(sk, []).get(sk)).push(sv);
485
+ }
486
+ seen.add(evid);
487
+ return 1;
488
+ }
489
+ enqueue(ref, db, handle, keyPtr, keyLen, valPtr, valLen) {
490
+ const coll = collOf(db, handle);
491
+ if (coll === null)
492
+ return INVALID_HANDLE;
493
+ if (keyLen > MAX_KEY || valLen > MAX_VALUE)
494
+ throw new Error('data: key/value too large');
495
+ const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
496
+ if (!this.store.has(sk))
497
+ return ABSENT;
498
+ this.store.set(sk, readCopy(ref, valPtr, valLen));
499
+ this.stampVersion(coll, sk);
500
+ return 0;
501
+ }
502
+ latest(ref, db, handle, keyPtr, keyLen, limit) {
503
+ const coll = collOf(db, handle);
504
+ if (coll === null)
505
+ return INVALID_HANDLE;
506
+ const sk = storeKey(coll, readKey(ref, keyPtr, keyLen));
507
+ const log = this.events.get(sk) ?? [];
508
+ const vers = this.eventVersions.get(sk) ?? [];
509
+ const n = Math.max(0, Math.min(limit, 0xffff));
510
+ const start = Math.max(0, log.length - n);
511
+ const newest = log.slice(start).reverse();
512
+ const newestVers = vers.slice(start).reverse();
513
+ const w = new DataWriter();
514
+ w.writeU32(newest.length);
515
+ for (let i = 0; i < newest.length; i++) {
516
+ w.writeU32(newestVers[i] ?? 0).writeBytes(newest[i]);
517
+ }
518
+ db.lastResult = Buffer.from(w.toBytes());
519
+ return db.lastResult.length;
520
+ }
521
+ capacitySetTotal(ref, db, handle, keyPtr, keyLen, total) {
522
+ const coll = collOf(db, handle);
523
+ if (coll === null)
524
+ return INVALID_HANDLE;
525
+ const l = this.capLedger(storeKey(coll, readKey(ref, keyPtr, keyLen)));
526
+ const t = BigInt(total);
527
+ l.total = satI64(t < 0n ? 0n : t);
528
+ return 0;
529
+ }
530
+ capacityAvailable(ref, db, handle, keyPtr, keyLen) {
531
+ const coll = collOf(db, handle);
532
+ if (coll === null)
533
+ return INVALID_HANDLE;
534
+ const l = this.capLedger(storeKey(coll, readKey(ref, keyPtr, keyLen)));
535
+ this.capPrune(l, Date.now());
536
+ const avail = l.total - this.capReserved(l);
537
+ const out = Buffer.alloc(8);
538
+ out.writeBigInt64LE(avail < 0n ? 0n : avail);
539
+ db.lastResult = out;
540
+ return out.length;
541
+ }
542
+ capacityReserve(ref, db, handle, keyPtr, keyLen, amount, ttlMs) {
543
+ const coll = collOf(db, handle);
544
+ if (coll === null)
545
+ return INVALID_HANDLE;
546
+ const want = BigInt(amount);
547
+ if (want <= 0n)
548
+ return CODEC_ERR;
549
+ const l = this.capLedger(storeKey(coll, readKey(ref, keyPtr, keyLen)));
550
+ const now = Date.now();
551
+ this.capPrune(l, now);
552
+ if (l.total - this.capReserved(l) < want || l.reservations.size >= MAX_RESERVATIONS)
553
+ return ABSENT;
554
+ const ttl = Math.min(Math.max(0, Number(ttlMs)), MAX_RESERVATION_TTL_MS);
555
+ const id = l.nextId++;
556
+ l.reservations.set(id, { amount: want, expiresMs: now + ttl, confirmed: false });
557
+ const out = Buffer.alloc(8);
558
+ out.writeBigUInt64LE(id);
559
+ db.lastResult = out;
560
+ return out.length;
561
+ }
562
+ capacityConfirm(ref, db, handle, keyPtr, keyLen, reservationId) {
563
+ const coll = collOf(db, handle);
564
+ if (coll === null)
565
+ return INVALID_HANDLE;
566
+ const l = this.capLedger(storeKey(coll, readKey(ref, keyPtr, keyLen)));
567
+ this.capPrune(l, Date.now());
568
+ const r = l.reservations.get(BigInt(reservationId));
569
+ if (r === undefined)
570
+ return 0;
571
+ r.confirmed = true;
572
+ return 1;
573
+ }
574
+ capacityCancel(ref, db, handle, keyPtr, keyLen, reservationId) {
575
+ const coll = collOf(db, handle);
576
+ if (coll === null)
577
+ return INVALID_HANDLE;
578
+ const l = this.capLedger(storeKey(coll, readKey(ref, keyPtr, keyLen)));
579
+ this.capPrune(l, Date.now());
580
+ const id = BigInt(reservationId);
581
+ const r = l.reservations.get(id);
582
+ if (r === undefined || r.confirmed)
583
+ return 0;
584
+ l.reservations.delete(id);
585
+ return 1;
586
+ }
587
+ takeResult(ref, db, outPtr, outCap) {
588
+ const v = db.lastResult;
589
+ if (v === null)
590
+ return 0;
591
+ if (v.length > outCap)
592
+ return TOO_SMALL;
593
+ const m = mem(ref);
594
+ if (outPtr < 0 || outPtr + v.length > m.length)
595
+ throw new Error('data: take_result out of bounds');
596
+ v.copy(m, outPtr);
597
+ db.lastResult = null;
598
+ return v.length;
599
+ }
600
+ resultSchemaVersion(db) {
601
+ return BigInt(db.lastResultVersion);
602
+ }
603
+ resetForTests() {
604
+ this.clear();
605
+ this.catalog = new Map();
606
+ this.persistPath = null;
607
+ }
608
+ setCatalogForTests(entries) {
609
+ this.catalog = new Map(Object.entries(entries));
610
+ }
611
+ }
612
+ export const devDb = new DevDatabase();
613
+ export function setDbCatalog(wasm) {
614
+ devDb.setCatalog(wasm);
615
+ }
616
+ export function configureDbPersistence(filePath) {
617
+ devDb.configurePersistence(filePath);
618
+ }
619
+ export function persistDb() {
620
+ devDb.persist();
621
+ }
622
+ export function buildDatabaseImports(ref, db) {
623
+ return {
624
+ 'data.resolve_collection': (namePtr, nameLen, outHandlePtr) => devDb.resolveCollection(ref, db, namePtr, nameLen, outHandlePtr),
625
+ 'data.get': (handle, keyPtr, keyLen) => devDb.get(ref, db, handle, keyPtr, keyLen),
626
+ 'data.get_many': (handle, keysPtr, keysLen) => devDb.getMany(ref, db, handle, keysPtr, keysLen),
627
+ 'data.exists': (handle, keyPtr, keyLen) => devDb.exists(ref, db, handle, keyPtr, keyLen),
628
+ 'data.create': (handle, keyPtr, keyLen, valPtr, valLen, _idemPtr) => devDb.create(ref, db, handle, keyPtr, keyLen, valPtr, valLen),
629
+ 'data.patch': (handle, keyPtr, keyLen, patchPtr, patchLen, _idemPtr) => devDb.patch(ref, db, handle, keyPtr, keyLen, patchPtr, patchLen),
630
+ 'data.delete': (handle, keyPtr, keyLen, _idemPtr) => devDb.delete(ref, db, handle, keyPtr, keyLen),
631
+ 'data.get_delete': (handle, keyPtr, keyLen, _idemPtr) => devDb.getDelete(ref, db, handle, keyPtr, keyLen),
632
+ 'data.unique_lookup': (handle, keyPtr, keyLen) => devDb.uniqueLookup(ref, db, handle, keyPtr, keyLen),
633
+ 'data.unique_claim': (handle, keyPtr, keyLen, valPtr, valLen, _idemPtr) => devDb.uniqueClaim(ref, db, handle, keyPtr, keyLen, valPtr, valLen),
634
+ 'data.unique_release': (handle, keyPtr, keyLen, valPtr, valLen, _idemPtr) => devDb.uniqueRelease(ref, db, handle, keyPtr, keyLen, valPtr, valLen),
635
+ 'data.membership_contains': (handle, setPtr, setLen, memberPtr, memberLen) => devDb.membershipContains(ref, db, handle, setPtr, setLen, memberPtr, memberLen),
636
+ 'data.membership_add': (handle, setPtr, setLen, memberPtr, memberLen, _idemPtr) => devDb.membershipAdd(ref, db, handle, setPtr, setLen, memberPtr, memberLen),
637
+ 'data.membership_remove': (handle, setPtr, setLen, memberPtr, memberLen, _idemPtr) => devDb.membershipRemove(ref, db, handle, setPtr, setLen, memberPtr, memberLen),
638
+ 'data.membership_list': (handle, setPtr, setLen, limit) => devDb.membershipList(ref, db, handle, setPtr, setLen, limit),
639
+ 'data.view_get': (handle, keyPtr, keyLen) => devDb.viewGet(ref, db, handle, keyPtr, keyLen),
640
+ 'data.view_publish': (handle, keyPtr, keyLen, valPtr, valLen, _idemPtr) => devDb.viewPublish(ref, db, handle, keyPtr, keyLen, valPtr, valLen),
641
+ 'data.counter_get': (handle, keyPtr, keyLen) => devDb.counterGet(ref, db, handle, keyPtr, keyLen),
642
+ 'data.counter_add': (handle, keyPtr, keyLen, delta, _idemPtr) => devDb.counterAdd(ref, db, handle, keyPtr, keyLen, delta),
643
+ 'data.append': (handle, keyPtr, keyLen, evPtr, evLen, _idemPtr) => devDb.append(ref, db, handle, keyPtr, keyLen, evPtr, evLen),
644
+ 'data.append_once': (handle, keyPtr, keyLen, evidPtr, evidLen, evPtr, evLen) => devDb.appendOnce(ref, db, handle, keyPtr, keyLen, evidPtr, evidLen, evPtr, evLen),
645
+ 'data.enqueue': (handle, keyPtr, keyLen, valPtr, valLen, _idemPtr) => devDb.enqueue(ref, db, handle, keyPtr, keyLen, valPtr, valLen),
646
+ 'data.latest': (handle, keyPtr, keyLen, limit) => devDb.latest(ref, db, handle, keyPtr, keyLen, limit),
647
+ 'data.capacity_set_total': (handle, keyPtr, keyLen, total, _idemPtr) => devDb.capacitySetTotal(ref, db, handle, keyPtr, keyLen, total),
648
+ 'data.capacity_available': (handle, keyPtr, keyLen) => devDb.capacityAvailable(ref, db, handle, keyPtr, keyLen),
649
+ 'data.capacity_reserve': (handle, keyPtr, keyLen, amount, ttlMs, _idemPtr) => devDb.capacityReserve(ref, db, handle, keyPtr, keyLen, amount, ttlMs),
650
+ 'data.capacity_confirm': (handle, keyPtr, keyLen, reservationId, _idemPtr) => devDb.capacityConfirm(ref, db, handle, keyPtr, keyLen, reservationId),
651
+ 'data.capacity_cancel': (handle, keyPtr, keyLen, reservationId, _idemPtr) => devDb.capacityCancel(ref, db, handle, keyPtr, keyLen, reservationId),
652
+ 'data.take_result': (outPtr, outCap) => devDb.takeResult(ref, db, outPtr, outCap),
653
+ 'data.result_schema_version': () => devDb.resultSchemaVersion(db),
654
+ 'data.write_allowed': () => 1,
655
+ };
656
+ }
657
+ export function __resetDbForTests() {
658
+ devDb.resetForTests();
659
+ }
660
+ export function __setDbCatalogForTests(entries) {
661
+ devDb.setCatalogForTests(entries);
662
+ }
@@ -0,0 +1,3 @@
1
+ export { __resetDbForTests, __setDbCatalogForTests, buildDatabaseImports, configureDbPersistence, DevDatabase, devDb, persistDb, setDbCatalog, } from './database.js';
2
+ export { parseCatalog } from './catalog.js';
3
+ export { type DbDevState, freshDbState } from './types.js';
@@ -0,0 +1,3 @@
1
+ export { __resetDbForTests, __setDbCatalogForTests, buildDatabaseImports, configureDbPersistence, DevDatabase, devDb, persistDb, setDbCatalog, } from './database.js';
2
+ export { parseCatalog } from './catalog.js';
3
+ export { freshDbState } from './types.js';