terrascale 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +143 -0
  3. package/package.json +159 -0
  4. package/sdk-current-contract.json +27 -0
  5. package/sdk-route-manifest.json +67 -0
  6. package/src/admin.js +9 -0
  7. package/src/better-auth.js +14 -0
  8. package/src/config.js +121 -0
  9. package/src/database-codec.js +845 -0
  10. package/src/database-types.js +237 -0
  11. package/src/database-view.js +422 -0
  12. package/src/database.js +420 -0
  13. package/src/discovery.js +374 -0
  14. package/src/http.js +887 -0
  15. package/src/index.js +79 -0
  16. package/src/local/authentication.js +47 -0
  17. package/src/local/better-auth.js +517 -0
  18. package/src/local/cli.js +51 -0
  19. package/src/local/context.js +23 -0
  20. package/src/local/environment.js +109 -0
  21. package/src/local/index.js +204 -0
  22. package/src/local/router.js +999 -0
  23. package/src/local/server.js +664 -0
  24. package/src/local/store.js +530 -0
  25. package/src/local/test-environment.js +74 -0
  26. package/src/management-contracts.js +72 -0
  27. package/src/management.js +12 -0
  28. package/src/native-origin.js +75 -0
  29. package/src/postgres.js +494 -0
  30. package/src/react/core.js +743 -0
  31. package/src/react/index.js +99 -0
  32. package/src/result.js +251 -0
  33. package/src/schema.js +366 -0
  34. package/src/sql.js +996 -0
  35. package/src/svelte/index.js +129 -0
  36. package/src/tanstack/index.js +511 -0
  37. package/src/ts-auth-discovery.js +190 -0
  38. package/src/ts-auth.js +3497 -0
  39. package/types/admin.d.ts +6 -0
  40. package/types/better-auth.d.ts +8 -0
  41. package/types/config.d.ts +58 -0
  42. package/types/database-codec.d.ts +111 -0
  43. package/types/database-types.d.ts +213 -0
  44. package/types/database-view.d.ts +183 -0
  45. package/types/database.d.ts +98 -0
  46. package/types/discovery.d.ts +114 -0
  47. package/types/http.d.ts +46 -0
  48. package/types/index.d.ts +52 -0
  49. package/types/local/authentication.d.ts +11 -0
  50. package/types/local/better-auth.d.ts +33 -0
  51. package/types/local/cli.d.ts +2 -0
  52. package/types/local/context.d.ts +14 -0
  53. package/types/local/environment.d.ts +23 -0
  54. package/types/local/index.d.ts +94 -0
  55. package/types/local/router.d.ts +66 -0
  56. package/types/local/server.d.ts +54 -0
  57. package/types/local/store.d.ts +106 -0
  58. package/types/local/test-environment.d.ts +25 -0
  59. package/types/management-contracts.d.ts +44 -0
  60. package/types/management.d.ts +6 -0
  61. package/types/native-origin.d.ts +23 -0
  62. package/types/postgres.d.ts +123 -0
  63. package/types/react/core.d.ts +366 -0
  64. package/types/react/index.d.ts +54 -0
  65. package/types/result.d.ts +161 -0
  66. package/types/schema.d.ts +145 -0
  67. package/types/sql.d.ts +288 -0
  68. package/types/svelte/index.d.ts +81 -0
  69. package/types/tanstack/index.d.ts +165 -0
  70. package/types/ts-auth-discovery.d.ts +11 -0
  71. package/types/ts-auth.d.ts +1826 -0
@@ -0,0 +1,845 @@
1
+ /**
2
+ * @import {
3
+ * DatabaseAuthority, DatabaseCommit, DatabaseCommitReceipt, DatabaseCondition, DatabaseEvent,
4
+ * DatabaseField, DatabaseObserved, DatabasePoint, DatabaseQuery, DatabaseQueryPage,
5
+ * DatabaseQueryPredicate, DatabaseScope, DatabaseSibling, DatabaseSubscriptionRequest,
6
+ * DatabaseSubscriptionResponse, DatabaseTransactionId, DatabaseValue, DatabaseWrite,
7
+ * DatabaseTransactionIdentity, DatabaseTransactionStatus,
8
+ * } from "./database-types.js"
9
+ */
10
+
11
+ /** @typedef {null | boolean | number | string | JsonArray | JsonObject} Json */
12
+ /** @typedef {readonly Json[]} JsonArray */
13
+ /** @typedef {{ readonly [key: string]: Json }} JsonObject */
14
+ /** @typedef {Record<string, unknown>} RecordValue */
15
+ const encoder = new TextEncoder();
16
+ const maxU64 = (1n << 64n) - 1n;
17
+ const minI64 = -(1n << 63n);
18
+ const maxI64 = (1n << 63n) - 1n;
19
+ const maximumBodyBytes = 4 * 1024 * 1024;
20
+ const maximumValueBytes = 1024 * 1024;
21
+ const scalarTypes = new Set(["bool", "i64", "utf8", "bytes"]);
22
+
23
+ /**
24
+ * @param {unknown} value
25
+ * @param {string} [name]
26
+ * @returns {string}
27
+ */
28
+ export function requireDatabaseUuid(value, name = "UUID") {
29
+ if (typeof value !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u.test(value)) {
30
+ throw new TypeError(`${name} must be a canonical lowercase UUID.`);
31
+ }
32
+ return value;
33
+ }
34
+
35
+ /**
36
+ * @param {unknown} value
37
+ * @param {string} [name]
38
+ * @param {boolean} [positive]
39
+ * @returns {bigint}
40
+ */
41
+ export function requireDatabaseU64(value, name = "u64", positive = false) {
42
+ if (typeof value !== "bigint" || value < (positive ? 1n : 0n) || value > maxU64) {
43
+ throw new TypeError(`${name} must be a ${positive ? "positive " : ""}u64 bigint.`);
44
+ }
45
+ return value;
46
+ }
47
+
48
+ /**
49
+ * @param {unknown} value
50
+ * @param {string} name
51
+ * @param {boolean} [positive]
52
+ * @returns {bigint}
53
+ */
54
+ function decimal(value, name, positive = false) {
55
+ if (typeof value !== "string" || !/^(0|[1-9][0-9]*)$/u.test(value)) throw new TypeError(`${name} must be canonical decimal.`);
56
+ return requireDatabaseU64(BigInt(value), name, positive);
57
+ }
58
+
59
+ /**
60
+ * @param {unknown} value
61
+ * @param {number} bytes
62
+ * @param {string} name
63
+ * @returns {string}
64
+ */
65
+ function hex(value, bytes, name) {
66
+ if (typeof value !== "string" || value.length !== bytes * 2 || !/^[0-9a-f]+$/u.test(value)) {
67
+ throw new TypeError(`${name} must be canonical lowercase hexadecimal.`);
68
+ }
69
+ return value;
70
+ }
71
+
72
+ /**
73
+ * @param {unknown} value
74
+ * @param {readonly string[]} required
75
+ * @param {readonly string[]} [optional]
76
+ * @returns {RecordValue}
77
+ */
78
+ function object(value, required, optional = []) {
79
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("Expected a protocol object.");
80
+ const entry = /** @type {RecordValue} */ (value);
81
+ if (required.some(key => !Object.hasOwn(entry, key)) || Object.keys(entry).some(key => !required.includes(key) && !optional.includes(key))) {
82
+ throw new TypeError("Protocol object has missing or unknown fields.");
83
+ }
84
+ return entry;
85
+ }
86
+
87
+ /**
88
+ * @param {unknown} value
89
+ * @param {number} [maximum]
90
+ * @param {number} [minimum]
91
+ * @returns {readonly unknown[]}
92
+ */
93
+ function array(value, maximum = 1024, minimum = 0) {
94
+ if (!Array.isArray(value) || value.length < minimum || value.length > maximum) throw new TypeError("Protocol array exceeds its bounds.");
95
+ return value;
96
+ }
97
+
98
+ /**
99
+ * @param {unknown} value
100
+ * @returns {number}
101
+ */
102
+ function fieldId(value) {
103
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > 0xffff_ffff) {
104
+ throw new TypeError("Field ID must be a positive u32.");
105
+ }
106
+ return value;
107
+ }
108
+
109
+ /**
110
+ * @param {unknown} value
111
+ * @returns {string}
112
+ */
113
+ function text(value) {
114
+ if (typeof value !== "string") throw new TypeError("UTF-8 value must be text.");
115
+ for (let index = 0; index < value.length; index++) {
116
+ const code = value.charCodeAt(index);
117
+ if (code >= 0xd800 && code <= 0xdbff) {
118
+ const next = value.charCodeAt(++index);
119
+ if (!(next >= 0xdc00 && next <= 0xdfff)) throw new TypeError("Text contains an unpaired surrogate.");
120
+ } else if (code >= 0xdc00 && code <= 0xdfff) throw new TypeError("Text contains an unpaired surrogate.");
121
+ }
122
+ if (encoder.encode(value).length > maximumValueBytes) throw new TypeError("Value exceeds its byte limit.");
123
+ return value;
124
+ }
125
+
126
+ /**
127
+ * @param {unknown} value
128
+ * @returns {bigint}
129
+ */
130
+ function i64(value) {
131
+ if (typeof value !== "bigint" || value < minI64 || value > maxI64) throw new TypeError("i64 value must be an in-range bigint.");
132
+ return value;
133
+ }
134
+
135
+ /**
136
+ * @param {unknown} value
137
+ * @returns {bigint}
138
+ */
139
+ function parseI64(value) {
140
+ if (typeof value !== "string" || !/^(0|-?[1-9][0-9]*)$/u.test(value)) throw new TypeError("i64 must be canonical decimal.");
141
+ return i64(BigInt(value));
142
+ }
143
+
144
+ /**
145
+ * @param {Uint8Array} bytes
146
+ * @returns {string}
147
+ */
148
+ function base64url(bytes) {
149
+ let binary = "";
150
+ for (const byte of bytes) binary += String.fromCharCode(byte);
151
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
152
+ }
153
+
154
+ /**
155
+ * @param {unknown} value
156
+ * @returns {Uint8Array}
157
+ */
158
+ function parseBytes(value) {
159
+ if (typeof value !== "string" || !/^[A-Za-z0-9_-]*$/u.test(value) || value.length > 1_398_102) throw new TypeError("Bytes must be bounded canonical base64url.");
160
+ /** @type {string} */
161
+ let binary;
162
+ try { binary = atob(value.replaceAll("-", "+").replaceAll("_", "/")); }
163
+ catch { throw new TypeError("Bytes must be canonical base64url."); }
164
+ const bytes = Uint8Array.from(binary, character => character.charCodeAt(0));
165
+ if (bytes.length > maximumValueBytes || base64url(bytes) !== value) throw new TypeError("Bytes must be bounded canonical base64url.");
166
+ return bytes;
167
+ }
168
+
169
+ /** @typedef {{ nodes: number }} Budget */
170
+
171
+ /**
172
+ * @param {number} depth
173
+ * @param {Budget} budget
174
+ * @returns {void}
175
+ */
176
+ function takeNode(depth, budget) {
177
+ if (depth > 16 || ++budget.nodes > 4096) throw new TypeError("Document exceeds its depth or node limit.");
178
+ }
179
+
180
+ /**
181
+ * @param {readonly DatabaseField[]} fields
182
+ * @param {number} depth
183
+ * @param {Budget} budget
184
+ * @returns {readonly Json[]}
185
+ */
186
+ function fieldsToWire(fields, depth, budget) {
187
+ const entries = array(fields);
188
+ let previous = 0;
189
+ return entries.map(raw => {
190
+ const field = object(raw, ["id", "value"]);
191
+ const id = fieldId(field.id);
192
+ if (id <= previous) throw new TypeError("Field IDs must be strictly increasing.");
193
+ previous = id;
194
+ return { id, value: valueToWire(/** @type {DatabaseValue} */ (field.value), depth, budget) };
195
+ });
196
+ }
197
+
198
+ /**
199
+ * @param {DatabaseValue} value
200
+ * @param {number} depth
201
+ * @param {Budget} budget
202
+ * @returns {JsonObject}
203
+ */
204
+ function valueToWire(value, depth, budget) {
205
+ takeNode(depth, budget);
206
+ const initial = object(value, ["type"], ["value", "fields", "items"]);
207
+ switch (initial.type) {
208
+ case "null": object(value, ["type"]); return { type: "null" };
209
+ case "bool": {
210
+ const entry = object(value, ["type", "value"]);
211
+ if (typeof entry.value !== "boolean") throw new TypeError("Boolean value must be boolean.");
212
+ return { type: "bool", value: entry.value };
213
+ }
214
+ case "i64": return { type: "i64", value: i64(object(value, ["type", "value"]).value).toString() };
215
+ case "utf8": return { type: "utf8", value: text(object(value, ["type", "value"]).value) };
216
+ case "bytes": {
217
+ const bytes = object(value, ["type", "value"]).value;
218
+ if (!(bytes instanceof Uint8Array) || bytes.length > maximumValueBytes) throw new TypeError("Bytes value must be a bounded Uint8Array.");
219
+ return { type: "bytes", value: base64url(bytes) };
220
+ }
221
+ case "map": return { type: "map", fields: fieldsToWire(/** @type {readonly DatabaseField[]} */ (object(value, ["type", "fields"]).fields), depth + 1, budget) };
222
+ case "array": return { type: "array", items: array(object(value, ["type", "items"]).items).map(item => valueToWire(/** @type {DatabaseValue} */ (item), depth + 1, budget)) };
223
+ default: throw new TypeError("Unsupported database value type.");
224
+ }
225
+ }
226
+
227
+ /**
228
+ * @param {unknown} raw
229
+ * @param {number} depth
230
+ * @param {Budget} budget
231
+ * @returns {readonly DatabaseField[]}
232
+ */
233
+ function fieldsFromWire(raw, depth, budget) {
234
+ let previous = 0;
235
+ return array(raw).map(value => {
236
+ const entry = object(value, ["id", "value"]);
237
+ const id = fieldId(entry.id);
238
+ if (id <= previous) throw new TypeError("Field IDs must be strictly increasing.");
239
+ previous = id;
240
+ return { id, value: valueFromWire(entry.value, depth, budget) };
241
+ });
242
+ }
243
+
244
+ /**
245
+ * @param {unknown} raw
246
+ * @param {number} depth
247
+ * @param {Budget} budget
248
+ * @returns {DatabaseValue}
249
+ */
250
+ function valueFromWire(raw, depth, budget) {
251
+ takeNode(depth, budget);
252
+ const initial = object(raw, ["type"], ["value", "fields", "items"]);
253
+ switch (initial.type) {
254
+ case "null": object(raw, ["type"]); return { type: "null" };
255
+ case "bool": {
256
+ const entry = object(raw, ["type", "value"]);
257
+ if (typeof entry.value !== "boolean") throw new TypeError("Boolean value must be boolean.");
258
+ return { type: "bool", value: entry.value };
259
+ }
260
+ case "i64": return { type: "i64", value: parseI64(object(raw, ["type", "value"]).value) };
261
+ case "utf8": return { type: "utf8", value: text(object(raw, ["type", "value"]).value) };
262
+ case "bytes": return { type: "bytes", value: parseBytes(object(raw, ["type", "value"]).value) };
263
+ case "map": return { type: "map", fields: fieldsFromWire(object(raw, ["type", "fields"]).fields, depth + 1, budget) };
264
+ case "array": return { type: "array", items: array(object(raw, ["type", "items"]).items).map(value => valueFromWire(value, depth + 1, budget)) };
265
+ default: throw new TypeError("Unsupported database value type.");
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Typed value encoding preserves numeric field IDs, arrays, bytes, and explicit null.
271
+ * @param {DatabaseValue} value
272
+ * @returns {JsonObject}
273
+ */
274
+ export function encodeDatabaseValue(value) {
275
+ const wire = valueToWire(value, 1, { nodes: 0 });
276
+ nativeValue(wire);
277
+ return wire;
278
+ }
279
+
280
+ /**
281
+ * @param {unknown} value
282
+ * @returns {DatabaseValue}
283
+ */
284
+ export function decodeDatabaseValue(value) {
285
+ const decoded = valueFromWire(value, 1, { nodes: 0 });
286
+ nativeValue(valueToWire(decoded, 1, { nodes: 0 }));
287
+ return decoded;
288
+ }
289
+
290
+ /**
291
+ * @param {readonly DatabaseObserved[]} entries
292
+ * @returns {readonly Json[]}
293
+ */
294
+ function observedToWire(entries) {
295
+ let previous = "";
296
+ return array(entries).map(raw => {
297
+ const entry = object(raw, ["replica", "incarnation", "sequence"]);
298
+ const replica = requireDatabaseUuid(entry.replica, "replica");
299
+ const incarnation = hex(entry.incarnation, 16, "incarnation");
300
+ const order = replica + incarnation;
301
+ if (order <= previous) throw new TypeError("Observed origins must be strictly increasing.");
302
+ previous = order;
303
+ return { replica, incarnation, sequence: requireDatabaseU64(entry.sequence, "sequence", true).toString() };
304
+ });
305
+ }
306
+
307
+ /**
308
+ * @param {unknown} raw
309
+ * @returns {readonly DatabaseObserved[]}
310
+ */
311
+ function observedFromWire(raw) {
312
+ const entries = array(raw).map(value => {
313
+ const entry = object(value, ["replica", "incarnation", "sequence"]);
314
+ return { replica: requireDatabaseUuid(entry.replica, "replica"), incarnation: hex(entry.incarnation, 16, "incarnation"), sequence: decimal(entry.sequence, "sequence", true) };
315
+ });
316
+ observedToWire(entries);
317
+ return entries;
318
+ }
319
+
320
+ /**
321
+ * @param {unknown} raw
322
+ * @returns {DatabaseEvent}
323
+ */
324
+ function eventFromWire(raw) {
325
+ const entry = object(raw, ["database", "shard", "replica", "incarnation", "sequence"]);
326
+ return {
327
+ database: requireDatabaseUuid(entry.database, "database"), shard: decimal(entry.shard, "shard"),
328
+ replica: requireDatabaseUuid(entry.replica, "replica"), incarnation: hex(entry.incarnation, 16, "incarnation"),
329
+ sequence: decimal(entry.sequence, "sequence", true),
330
+ };
331
+ }
332
+
333
+ /**
334
+ * @param {unknown} raw
335
+ * @returns {DatabasePoint}
336
+ */
337
+ export function decodeDatabasePoint(raw) {
338
+ const entry = object(raw, ["version", "state", "state_version", "context", "siblings"]);
339
+ if (entry.version !== 1 || !["absent", "deleted", "value", "conflict"].includes(String(entry.state))) throw new TypeError("Unsupported point state or protocol.");
340
+ const context = observedFromWire(entry.context);
341
+ /** @type {DatabaseSibling[]} */
342
+ const siblings = array(entry.siblings).map(rawSibling => {
343
+ const sibling = object(rawSibling, ["event", "schema_generation", "schema_digest", "kind"], ["fields"]);
344
+ const base = { event: eventFromWire(sibling.event), schema_generation: decimal(sibling.schema_generation, "schema_generation", true), schema_digest: hex(sibling.schema_digest, 32, "schema_digest") };
345
+ if (sibling.kind === "tombstone") {
346
+ object(rawSibling, ["event", "schema_generation", "schema_digest", "kind"]);
347
+ return { ...base, kind: "tombstone" };
348
+ }
349
+ if (sibling.kind !== "live") throw new TypeError("Unsupported sibling kind.");
350
+ object(rawSibling, ["event", "schema_generation", "schema_digest", "kind", "fields"]);
351
+ const fields = fieldsFromWire(sibling.fields, 1, { nodes: 0 });
352
+ validateNativeDocument(fieldsToWire(fields, 1, { nodes: 0 }));
353
+ return { ...base, kind: "live", fields };
354
+ });
355
+ const state = /** @type {DatabasePoint["state"]} */ (entry.state);
356
+ const state_version = entry.state_version === null ? null : hex(entry.state_version, 32, "state_version");
357
+ if ((state === "absent" && (context.length !== 0 || siblings.length !== 0 || state_version !== null)) ||
358
+ (state === "deleted" && (siblings.length !== 1 || siblings[0]?.kind !== "tombstone" || state_version === null)) ||
359
+ (state === "value" && (siblings.length !== 1 || siblings[0]?.kind !== "live" || state_version === null)) ||
360
+ (state === "conflict" && (siblings.length < 2 || state_version !== null))) {
361
+ throw new TypeError("Point state contradicts its siblings or condition token.");
362
+ }
363
+ /** @type {Set<string>} */
364
+ const events = new Set();
365
+ for (const sibling of siblings) {
366
+ const event = sibling.event;
367
+ const key = event.replica + event.incarnation + ":" + event.sequence;
368
+ if (events.has(key) || !context.some(origin => origin.replica === event.replica && origin.incarnation === event.incarnation && origin.sequence >= event.sequence)) {
369
+ throw new TypeError("Point causal context is incomplete or contains duplicate siblings.");
370
+ }
371
+ events.add(key);
372
+ }
373
+ return { version: 1, state, state_version, context, siblings };
374
+ }
375
+
376
+ /**
377
+ * @param {unknown} value
378
+ * @returns {string}
379
+ */
380
+ function cursor(value) {
381
+ const message = "Cursor must be bounded opaque canonical base64url.";
382
+ if (typeof value !== "string" || value.length > 1024 || !/^[A-Za-z0-9_-]+$/u.test(value)) throw new TypeError(message);
383
+ // Validate only the encoding; the producer-owned cursor contents remain opaque.
384
+ try { parseBytes(value); }
385
+ catch { throw new TypeError(message); }
386
+ return value;
387
+ }
388
+
389
+ /**
390
+ * @param {DatabaseQueryPredicate} predicate
391
+ * @returns {JsonObject}
392
+ */
393
+ function predicateToWire(predicate) {
394
+ const entry = object(predicate, ["op", "path"], ["value"]);
395
+ const path = array(entry.path, 16, 1).map(fieldId);
396
+ if (entry.op === "is_null" || entry.op === "is_missing") {
397
+ object(predicate, ["op", "path"]);
398
+ return { op: entry.op, path };
399
+ }
400
+ if (!["equal", "less_than", "less_than_or_equal", "greater_than", "greater_than_or_equal"].includes(String(entry.op))) throw new TypeError("Unsupported query operator.");
401
+ object(predicate, ["op", "path", "value"]);
402
+ const value = encodeDatabaseValue(/** @type {DatabaseValue} */ (entry.value));
403
+ if (!scalarTypes.has(String(value.type))) throw new TypeError("Query values must be supported scalars.");
404
+ return { op: String(entry.op), path, value };
405
+ }
406
+
407
+ /**
408
+ * @param {unknown} value
409
+ * @returns {number}
410
+ */
411
+ function queryLimit(value) {
412
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > 128) throw new TypeError("Query limit must be between 1 and 128.");
413
+ return value;
414
+ }
415
+
416
+ /**
417
+ * @param {DatabaseQuery} query
418
+ * @returns {string}
419
+ */
420
+ export function encodeDatabaseQuery(query) {
421
+ const entry = object(query, ["predicate", "limit"], ["cursor"]);
422
+ return boundedJson({ version: 1, predicate: predicateToWire(/** @type {DatabaseQueryPredicate} */ (entry.predicate)), limit: queryLimit(entry.limit), ...(entry.cursor === undefined ? {} : { cursor: cursor(entry.cursor) }) });
423
+ }
424
+
425
+ /**
426
+ * @param {unknown} raw
427
+ * @returns {DatabaseQueryPage["rows"]}
428
+ */
429
+ function rowsFromWire(raw) {
430
+ let previous = "";
431
+ return array(raw, 128).map(rawRow => {
432
+ const entry = object(rawRow, ["document", "point"]);
433
+ const document = requireDatabaseUuid(entry.document, "document");
434
+ if (document <= previous) throw new TypeError("Query documents must be strictly increasing.");
435
+ previous = document;
436
+ return { document, point: decodeDatabasePoint(entry.point) };
437
+ });
438
+ }
439
+
440
+ /**
441
+ * @param {unknown} raw
442
+ * @returns {DatabaseQueryPage}
443
+ */
444
+ export function decodeDatabaseQueryPage(raw) {
445
+ const entry = object(raw, ["version", "rows"], ["next_cursor"]);
446
+ if (entry.version !== 1) throw new TypeError("Unsupported query protocol.");
447
+ return { version: 1, rows: rowsFromWire(entry.rows), ...(entry.next_cursor === undefined ? {} : { next_cursor: cursor(entry.next_cursor) }) };
448
+ }
449
+
450
+ /**
451
+ * @param {DatabaseSubscriptionRequest} request
452
+ * @returns {string}
453
+ */
454
+ export function encodeDatabaseSubscription(request) {
455
+ if (request.phase === "snapshot") {
456
+ const entry = object(request, ["phase", "predicate", "limit"], ["snapshot_cursor"]);
457
+ return boundedJson({ version: 1, phase: "snapshot", predicate: predicateToWire(request.predicate), limit: queryLimit(entry.limit), ...(entry.snapshot_cursor === undefined ? {} : { snapshot_cursor: cursor(entry.snapshot_cursor) }) });
458
+ }
459
+ object(request, ["phase", "predicate", "poll_cursor"]);
460
+ if (request.phase !== "poll") throw new TypeError("Unsupported subscription phase.");
461
+ return boundedJson({ version: 1, phase: "poll", predicate: predicateToWire(request.predicate), poll_cursor: cursor(request.poll_cursor) });
462
+ }
463
+
464
+ /**
465
+ * @param {unknown} raw
466
+ * @returns {DatabaseSubscriptionResponse}
467
+ */
468
+ export function decodeDatabaseSubscription(raw) {
469
+ const initial = object(raw, ["version", "phase"], ["rows", "next_snapshot_cursor", "poll_cursor", "groups"]);
470
+ if (initial.version !== 1) throw new TypeError("Unsupported subscription protocol.");
471
+ if (initial.phase === "snapshot") {
472
+ const continuation = Object.hasOwn(initial, "next_snapshot_cursor") ? "next_snapshot_cursor" : "poll_cursor";
473
+ const entry = object(raw, ["version", "phase", "rows", continuation]);
474
+ const rows = rowsFromWire(entry.rows);
475
+ return continuation === "next_snapshot_cursor"
476
+ ? { version: 1, phase: "snapshot", rows, next_snapshot_cursor: cursor(entry.next_snapshot_cursor) }
477
+ : { version: 1, phase: "snapshot", rows, poll_cursor: cursor(entry.poll_cursor) };
478
+ }
479
+ const entry = object(raw, ["version", "phase", "groups", "poll_cursor"]);
480
+ if (entry.phase !== "poll") throw new TypeError("Unsupported subscription phase.");
481
+ let previous = 0n;
482
+ const groups = array(entry.groups, 8).map(rawGroup => {
483
+ const group = object(rawGroup, ["sequence", "effects"]);
484
+ const sequence = decimal(group.sequence, "sequence", true);
485
+ if (sequence <= previous) throw new TypeError("Subscription sequences must increase.");
486
+ previous = sequence;
487
+ return { sequence, effects: array(group.effects, 128).map(rawEffect => {
488
+ const effect = object(rawEffect, ["document", "point"]);
489
+ return { document: requireDatabaseUuid(effect.document, "document"), point: effect.point === null ? null : decodeDatabasePoint(effect.point) };
490
+ }) };
491
+ });
492
+ return { version: 1, phase: "poll", groups, poll_cursor: cursor(entry.poll_cursor) };
493
+ }
494
+
495
+ /**
496
+ * @param {DatabaseAuthority} authority
497
+ * @returns {JsonObject}
498
+ */
499
+ export function encodeDatabaseAuthority(authority) {
500
+ if (authority.mode === "leaderless") { object(authority, ["mode"]); return { mode: "leaderless" }; }
501
+ object(authority, ["mode", "generation", "writer"]);
502
+ if (authority.mode !== "single_writer") throw new TypeError("Unsupported authority mode.");
503
+ const writer = object(authority.writer, ["replica", "incarnation"]);
504
+ return { mode: "single_writer", generation: requireDatabaseU64(authority.generation, "authority generation", true).toString(), writer: { replica: requireDatabaseUuid(writer.replica, "writer"), incarnation: hex(writer.incarnation, 16, "incarnation") } };
505
+ }
506
+
507
+ /**
508
+ * @param {unknown} raw
509
+ * @returns {DatabaseAuthority}
510
+ */
511
+ export function decodeDatabaseAuthority(raw) {
512
+ const initial = object(raw, ["mode"], ["generation", "writer"]);
513
+ if (initial.mode === "leaderless") { object(raw, ["mode"]); return { mode: "leaderless" }; }
514
+ const entry = object(raw, ["mode", "generation", "writer"]);
515
+ if (entry.mode !== "single_writer") throw new TypeError("Unsupported authority mode.");
516
+ const writer = object(entry.writer, ["replica", "incarnation"]);
517
+ return { mode: "single_writer", generation: decimal(entry.generation, "authority generation", true), writer: { replica: requireDatabaseUuid(writer.replica, "writer"), incarnation: hex(writer.incarnation, 16, "incarnation") } };
518
+ }
519
+
520
+ /**
521
+ * @param {DatabaseCondition} condition
522
+ * @returns {JsonObject}
523
+ */
524
+ function conditionToWire(condition) {
525
+ if (condition.kind === "none" || condition.kind === "absent") { object(condition, ["kind"]); return { kind: condition.kind }; }
526
+ object(condition, ["kind", "version"]);
527
+ if (condition.kind !== "version") throw new TypeError("Unsupported condition.");
528
+ return { kind: "version", version: hex(condition.version, 32, "condition version") };
529
+ }
530
+
531
+ /**
532
+ * @param {DatabaseTransactionId} transaction
533
+ * @returns {JsonObject}
534
+ */
535
+ function transactionToWire(transaction) {
536
+ object(transaction, ["expires_at_unix_ms", "nonce"]);
537
+ return { expires_at_unix_ms: requireDatabaseU64(transaction.expires_at_unix_ms, "expiry").toString(), nonce: hex(transaction.nonce, 16, "nonce") };
538
+ }
539
+
540
+ /**
541
+ * @param {unknown} raw
542
+ * @returns {DatabaseTransactionId}
543
+ */
544
+ function transactionFromWire(raw) {
545
+ const entry = object(raw, ["expires_at_unix_ms", "nonce"]);
546
+ return { expires_at_unix_ms: decimal(entry.expires_at_unix_ms, "expiry"), nonce: hex(entry.nonce, 16, "nonce") };
547
+ }
548
+
549
+ /**
550
+ * @param {unknown} raw
551
+ * @returns {DatabaseCommitReceipt}
552
+ */
553
+ export function decodeDatabaseCommitReceipt(raw) {
554
+ const entry = object(raw, ["version", "durability", "transaction_id", "event", "commit_sequence", "effect_digest", "authority"]);
555
+ if (entry.version !== 1 || (entry.durability !== "local_durable" && entry.durability !== "object_durable")) throw new TypeError("Unsupported durability or protocol.");
556
+ return { version: 1, durability: entry.durability, transaction_id: transactionFromWire(entry.transaction_id), event: eventFromWire(entry.event), commit_sequence: decimal(entry.commit_sequence, "commit_sequence", true), effect_digest: hex(entry.effect_digest, 32, "effect digest"), authority: decodeDatabaseAuthority(entry.authority) };
557
+ }
558
+
559
+ /**
560
+ * @param {DatabaseWrite} write
561
+ * @param {DatabaseAuthority} authority
562
+ * @returns {JsonObject}
563
+ */
564
+ function writeToWire(write, authority) {
565
+ object(write, ["collection", "document", "schema_generation", "schema_digest", "condition", "observed", "value"]);
566
+ if (authority.mode === "single_writer" && write.condition.kind === "none") throw new TypeError("Single-writer mutations require an atomic condition.");
567
+ const value = write.value.kind === "tombstone"
568
+ ? (object(write.value, ["kind"]), { kind: "tombstone" })
569
+ : (object(write.value, ["kind", "fields"]), { kind: "live", fields: fieldsToWire(write.value.fields, 1, { nodes: 0 }) });
570
+ if (write.value.kind !== "live" && write.value.kind !== "tombstone") throw new TypeError("Unsupported write kind.");
571
+ if (value.kind === "live") validateNativeDocument(/** @type {readonly Json[]} */ (value.fields));
572
+ return { collection: requireDatabaseUuid(write.collection, "collection"), document: requireDatabaseUuid(write.document, "document"), schema_generation: requireDatabaseU64(write.schema_generation, "schema generation", true).toString(), schema_digest: hex(write.schema_digest, 32, "schema digest"), condition: conditionToWire(write.condition), observed: observedToWire(write.observed), value };
573
+ }
574
+
575
+ class Bytes {
576
+ /**
577
+ * @readonly
578
+ * @type {Uint8Array[]}
579
+ */
580
+ chunks = [];
581
+ length = 0;
582
+ /**
583
+ * @param {Uint8Array} bytes
584
+ * @returns {void}
585
+ */
586
+ put(bytes) { this.length += bytes.length; if (this.length > maximumBodyBytes * 2) throw new TypeError("Canonical effect exceeds its byte limit."); this.chunks.push(bytes); }
587
+ /**
588
+ * @param {bigint} value
589
+ * @param {number} size
590
+ * @returns {void}
591
+ */
592
+ integer(value, size) {
593
+ const bytes = new Uint8Array(size);
594
+ for (let index = 0; index < size; index++) { bytes[index] = Number(value & 255n); value >>= 8n; }
595
+ this.put(bytes);
596
+ }
597
+ /**
598
+ * @param {string} value
599
+ * @returns {void}
600
+ */
601
+ hex(value) { this.put(Uint8Array.from(value.match(/../gu) ?? [], byte => Number.parseInt(byte, 16))); }
602
+ /**
603
+ * @param {string} value
604
+ * @returns {void}
605
+ */
606
+ uuid(value) { this.hex(value.replaceAll("-", "")); }
607
+ /** @returns {Uint8Array} */
608
+ finish() { const bytes = new Uint8Array(this.length); let offset = 0; for (const chunk of this.chunks) { bytes.set(chunk, offset); offset += chunk.length; } return bytes; }
609
+ }
610
+
611
+ /**
612
+ * Exact tb-format nested containers: u16 count, optional u32 field ID, u8 type, u32 byte length, bytes.
613
+ * @param {JsonObject} value
614
+ * @param {boolean} [insideArray]
615
+ * @returns {{ type: number; bytes: Uint8Array }}
616
+ */
617
+ function nativeValue(value, insideArray = false) {
618
+ const bytes = new Bytes();
619
+ /** @type {number} */
620
+ let type;
621
+ switch (value.type) {
622
+ case "null": type = 1; break;
623
+ case "bool": type = 2; bytes.integer(value.value === true ? 1n : 0n, 1); break;
624
+ case "i64": type = 3; bytes.integer(BigInt(/** @type {string} */ (value.value)), 8); break;
625
+ case "utf8": type = 4; bytes.put(encoder.encode(/** @type {string} */ (value.value))); break;
626
+ case "bytes": type = 5; bytes.put(parseBytes(value.value)); break;
627
+ case "map":
628
+ case "array": {
629
+ const map = value.type === "map";
630
+ if (!map && insideArray) throw new TypeError("Arrays cannot contain descendant arrays, including through maps.");
631
+ type = map ? 6 : 7;
632
+ const entries = array(map ? value.fields : value.items);
633
+ bytes.integer(BigInt(entries.length), 2);
634
+ for (const entry of entries) {
635
+ const field = map ? object(entry, ["id", "value"]) : undefined;
636
+ if (field !== undefined) bytes.integer(BigInt(fieldId(field.id)), 4);
637
+ const native = nativeValue(/** @type {JsonObject} */ (field?.value ?? entry), insideArray || !map);
638
+ bytes.integer(BigInt(native.type), 1); bytes.integer(BigInt(native.bytes.length), 4); bytes.put(native.bytes);
639
+ }
640
+ break;
641
+ }
642
+ default: throw new TypeError("Unsupported database value type.");
643
+ }
644
+ if (bytes.length > maximumValueBytes) throw new TypeError("Native value exceeds its byte limit.");
645
+ return { type, bytes: bytes.finish() };
646
+ }
647
+
648
+ /**
649
+ * @param {readonly Json[]} fields
650
+ * @returns {void}
651
+ */
652
+ function validateNativeDocument(fields) {
653
+ // tb-format document header + checksum, directory entries, and native payloads.
654
+ let size = 98 + 13 * fields.length;
655
+ for (const raw of fields) {
656
+ size += nativeValue(/** @type {JsonObject} */ (object(raw, ["id", "value"]).value)).bytes.length;
657
+ if (size > maximumValueBytes) throw new TypeError("Encoded document exceeds its 1 MiB limit.");
658
+ }
659
+ }
660
+
661
+ /**
662
+ * @param {DatabaseScope} scope
663
+ * @param {JsonObject} authority
664
+ * @param {readonly JsonObject[]} writes
665
+ * @returns {Uint8Array}
666
+ */
667
+ function effectBytes(scope, authority, writes) {
668
+ const bytes = new Bytes();
669
+ bytes.put(encoder.encode("TB-DOC-EFFECT-v1\0"));
670
+ bytes.uuid(requireDatabaseUuid(scope.tenant, "tenant")); bytes.uuid(requireDatabaseUuid(scope.database, "database")); bytes.integer(requireDatabaseU64(scope.shard, "shard"), 8);
671
+ const singleWriter = authority.mode === "single_writer";
672
+ bytes.integer(singleWriter ? 1n : 0n, 1);
673
+ if (singleWriter) {
674
+ const writer = object(authority.writer, ["replica", "incarnation"]);
675
+ bytes.integer(BigInt(/** @type {string} */ (authority.generation)), 8); bytes.uuid(/** @type {string} */ (writer.replica)); bytes.hex(/** @type {string} */ (writer.incarnation));
676
+ }
677
+ bytes.integer(BigInt(writes.length), 8);
678
+ for (const write of writes) {
679
+ bytes.uuid(/** @type {string} */ (write.collection)); bytes.uuid(/** @type {string} */ (write.document)); bytes.integer(BigInt(/** @type {string} */ (write.schema_generation)), 8); bytes.hex(/** @type {string} */ (write.schema_digest));
680
+ const condition = object(write.condition, ["kind"], ["version"]);
681
+ bytes.integer(condition.kind === "none" ? 0n : condition.kind === "absent" ? 1n : 2n, 1);
682
+ if (condition.kind === "version") bytes.hex(/** @type {string} */ (condition.version));
683
+ const observed = array(write.observed);
684
+ bytes.integer(BigInt(observed.length), 8);
685
+ for (const raw of observed) { const entry = object(raw, ["replica", "incarnation", "sequence"]); bytes.uuid(/** @type {string} */ (entry.replica)); bytes.hex(/** @type {string} */ (entry.incarnation)); bytes.integer(BigInt(/** @type {string} */ (entry.sequence)), 8); }
686
+ const value = object(write.value, ["kind"], ["fields"]);
687
+ bytes.integer(value.kind === "tombstone" ? 0n : 1n, 1);
688
+ if (value.kind === "live") {
689
+ const fields = array(value.fields); bytes.integer(BigInt(fields.length), 8);
690
+ for (const raw of fields) { const field = object(raw, ["id", "value"]); const native = nativeValue(/** @type {JsonObject} */ (field.value)); bytes.integer(BigInt(/** @type {number} */ (field.id)), 4); bytes.integer(BigInt(native.type), 1); bytes.integer(BigInt(native.bytes.length), 8); bytes.put(native.bytes); }
691
+ }
692
+ }
693
+ return bytes.finish();
694
+ }
695
+
696
+ /**
697
+ * @param {Json} value
698
+ * @returns {string}
699
+ */
700
+ function boundedJson(value) {
701
+ const encoded = JSON.stringify(value);
702
+ if (encoder.encode(encoded).length > maximumBodyBytes) throw new TypeError("Request exceeds its 4 MiB limit.");
703
+ return encoded;
704
+ }
705
+
706
+ /**
707
+ * @param {DatabaseTransactionIdentity} identity
708
+ * @returns {string}
709
+ */
710
+ export function encodeDatabaseStatus(identity) {
711
+ const entry = object(identity, ["transaction_id", "authority", "effect_digest"]);
712
+ return boundedJson({ version: 1, transaction_id: transactionToWire(/** @type {DatabaseTransactionId} */ (entry.transaction_id)), authority: encodeDatabaseAuthority(/** @type {DatabaseAuthority} */ (entry.authority)), effect_digest: hex(entry.effect_digest, 32, "effect digest") });
713
+ }
714
+
715
+ /**
716
+ * @param {unknown} raw
717
+ * @returns {DatabaseTransactionStatus}
718
+ */
719
+ export function decodeDatabaseStatus(raw) {
720
+ const entry = object(raw, ["version", "state", "transaction_id", "authority", "effect_digest", "outcome"]);
721
+ if (entry.version !== 1) throw new TypeError("Unsupported transaction status protocol.");
722
+ const base = { version: /** @type {const} */ (1), transaction_id: transactionFromWire(entry.transaction_id), authority: decodeDatabaseAuthority(entry.authority), effect_digest: hex(entry.effect_digest, 32, "effect digest") };
723
+ if (entry.state === "pending" || entry.state === "unknown" || entry.state === "expired") {
724
+ if (entry.outcome !== null) throw new TypeError("Unresolved transaction status must have no terminal outcome.");
725
+ return { ...base, state: entry.state, outcome: null };
726
+ }
727
+ if (entry.state === "committed") {
728
+ const outcome = object(entry.outcome, ["receipt"]);
729
+ const receipt = decodeDatabaseCommitReceipt(outcome.receipt);
730
+ if (receipt.transaction_id.nonce !== base.transaction_id.nonce || receipt.transaction_id.expires_at_unix_ms !== base.transaction_id.expires_at_unix_ms || receipt.effect_digest !== base.effect_digest || JSON.stringify(encodeDatabaseAuthority(receipt.authority)) !== JSON.stringify(encodeDatabaseAuthority(base.authority))) {
731
+ throw new TypeError("Status receipt contradicts its operation identity.");
732
+ }
733
+ return { ...base, state: "committed", outcome: { receipt } };
734
+ }
735
+ if (entry.state === "rejected") {
736
+ const outcome = object(entry.outcome, ["error"]);
737
+ const error = object(outcome.error, ["code"]);
738
+ if (error.code !== "precondition_failed") throw new TypeError("Unsupported rejection outcome.");
739
+ return { ...base, state: "rejected", outcome: { error: { code: "precondition_failed" } } };
740
+ }
741
+ throw new TypeError("Unsupported transaction status.");
742
+ }
743
+
744
+ /**
745
+ * Reject duplicate JSON members and unbounded syntax before typed response decoding.
746
+ * @param {Uint8Array} bytes
747
+ * @returns {unknown}
748
+ */
749
+ export function parseDatabaseJson(bytes) {
750
+ if (bytes.length < 1 || bytes.length > maximumBodyBytes) throw new TypeError("JSON response exceeds its byte limit.");
751
+ const input = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
752
+ let offset = 0;
753
+ /** @type {() => void} */
754
+ const whitespace = () => { while (/[ \t\r\n]/u.test(input[offset] ?? "x")) offset++; };
755
+ /** @type {() => never} */
756
+ const fail = () => { throw new TypeError("Response contains malformed or duplicate JSON members."); };
757
+ /** @type {() => string} */
758
+ const string = () => {
759
+ const start = offset++;
760
+ while (offset < input.length) {
761
+ const code = input.charCodeAt(offset);
762
+ if (input[offset] === '"') {
763
+ offset++;
764
+ /** @type {unknown} */
765
+ const parsed = JSON.parse(input.slice(start, offset));
766
+ if (typeof parsed !== "string") return fail();
767
+ return parsed;
768
+ }
769
+ if (code < 32) return fail();
770
+ if (input[offset++] === "\\") {
771
+ const escaped = input[offset++];
772
+ if (escaped === "u") {
773
+ if (!/^[0-9a-fA-F]{4}$/u.test(input.slice(offset, offset + 4))) return fail();
774
+ offset += 4;
775
+ } else if (escaped === undefined || !'"\\/bfnrt'.includes(escaped)) return fail();
776
+ }
777
+ }
778
+ return fail();
779
+ };
780
+ /** @type {(depth: number) => void} */
781
+ const value = (depth) => {
782
+ if (depth > 64) fail();
783
+ whitespace();
784
+ if (input[offset] === '"') { string(); return; }
785
+ if (input[offset] === "{" || input[offset] === "[") {
786
+ const map = input[offset++] === "{";
787
+ const end = map ? "}" : "]";
788
+ /** @type {Set<string>} */
789
+ const keys = new Set();
790
+ whitespace();
791
+ if (input[offset] === end) { offset++; return; }
792
+ while (offset < input.length) {
793
+ if (map) {
794
+ whitespace();
795
+ if (input[offset] !== '"') fail();
796
+ const key = string();
797
+ if (keys.has(key)) fail();
798
+ keys.add(key);
799
+ whitespace();
800
+ if (input[offset++] !== ":") fail();
801
+ }
802
+ value(depth + 1);
803
+ whitespace();
804
+ if (input[offset] === end) { offset++; return; }
805
+ if (input[offset++] !== ",") fail();
806
+ }
807
+ fail();
808
+ }
809
+ for (const literal of ["true", "false", "null"]) {
810
+ if (input.slice(offset, offset + literal.length) === literal) { offset += literal.length; return; }
811
+ }
812
+ const number = /-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/uy;
813
+ number.lastIndex = offset;
814
+ if (number.exec(input) === null) fail();
815
+ offset = number.lastIndex;
816
+ };
817
+ value(0); whitespace();
818
+ if (offset !== input.length) fail();
819
+ return /** @type {unknown} */ (JSON.parse(input));
820
+ }
821
+
822
+ /**
823
+ * Build and snapshot the exact logical effect before awaiting WebCrypto.
824
+ * @param {DatabaseScope} scope
825
+ * @param {DatabaseCommit} commit
826
+ * @returns {Promise<{ readonly body: string; readonly effect_digest: string }>}
827
+ */
828
+ export async function encodeDatabaseCommit(scope, commit) {
829
+ object(scope, ["tenant", "database", "shard"]);
830
+ object(commit, ["transaction_id", "authority", "writes"]);
831
+ const authority = encodeDatabaseAuthority(commit.authority);
832
+ const transaction = transactionToWire(commit.transaction_id);
833
+ let previous = "";
834
+ const writes = array(commit.writes, 1024, 1).map(raw => {
835
+ const write = writeToWire(/** @type {DatabaseWrite} */ (raw), commit.authority);
836
+ const order = String(write.collection) + String(write.document);
837
+ if (order <= previous) throw new TypeError("Writes must be sorted by collection and document.");
838
+ previous = order;
839
+ return write;
840
+ });
841
+ const canonical = effectBytes(scope, authority, writes);
842
+ const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new Uint8Array(canonical).buffer));
843
+ const effect_digest = Array.from(digest, byte => byte.toString(16).padStart(2, "0")).join("");
844
+ return { body: boundedJson({ version: 1, transaction_id: transaction, authority, effect_digest, writes }), effect_digest };
845
+ }