@bjornpagen/bumbledb 1.1.0 → 1.2.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 (148) hide show
  1. package/COOKBOOK.md +114 -11
  2. package/README.md +52 -9
  3. package/dist/capacity.d.ts +15 -55
  4. package/dist/capacity.d.ts.map +1 -1
  5. package/dist/capacity.js +63 -12
  6. package/dist/capacity.js.map +1 -1
  7. package/dist/changes.d.ts +26 -3
  8. package/dist/changes.d.ts.map +1 -1
  9. package/dist/changes.js +89 -44
  10. package/dist/changes.js.map +1 -1
  11. package/dist/closed.d.ts +23 -51
  12. package/dist/closed.d.ts.map +1 -1
  13. package/dist/closed.js +110 -168
  14. package/dist/closed.js.map +1 -1
  15. package/dist/codec.d.ts +4 -9
  16. package/dist/codec.d.ts.map +1 -1
  17. package/dist/codec.js +108 -183
  18. package/dist/codec.js.map +1 -1
  19. package/dist/compile.d.ts +16 -16
  20. package/dist/compile.d.ts.map +1 -1
  21. package/dist/compile.js +36 -15
  22. package/dist/compile.js.map +1 -1
  23. package/dist/db-native.d.ts +39 -0
  24. package/dist/db-native.d.ts.map +1 -1
  25. package/dist/db-native.js +8 -0
  26. package/dist/db-native.js.map +1 -1
  27. package/dist/db.d.ts +28 -8
  28. package/dist/db.d.ts.map +1 -1
  29. package/dist/db.js +91 -38
  30. package/dist/db.js.map +1 -1
  31. package/dist/errors.d.ts +11 -3
  32. package/dist/errors.d.ts.map +1 -1
  33. package/dist/errors.js +13 -1
  34. package/dist/errors.js.map +1 -1
  35. package/dist/face.d.ts +19 -25
  36. package/dist/face.d.ts.map +1 -1
  37. package/dist/face.js +30 -24
  38. package/dist/face.js.map +1 -1
  39. package/dist/fields.d.ts +13 -26
  40. package/dist/fields.d.ts.map +1 -1
  41. package/dist/fields.js +89 -116
  42. package/dist/fields.js.map +1 -1
  43. package/dist/immutable.d.ts +10 -0
  44. package/dist/immutable.d.ts.map +1 -0
  45. package/dist/immutable.js +75 -0
  46. package/dist/immutable.js.map +1 -0
  47. package/dist/index.d.ts +14 -10
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +5 -3
  50. package/dist/index.js.map +1 -1
  51. package/dist/internal/log.d.ts +2 -0
  52. package/dist/internal/log.d.ts.map +1 -1
  53. package/dist/internal/log.js +2 -0
  54. package/dist/internal/log.js.map +1 -1
  55. package/dist/law.d.ts +10 -10
  56. package/dist/law.d.ts.map +1 -1
  57. package/dist/law.js +1 -1
  58. package/dist/law.js.map +1 -1
  59. package/dist/lower.d.ts.map +1 -1
  60. package/dist/lower.js +11 -6
  61. package/dist/lower.js.map +1 -1
  62. package/dist/native.d.ts +1 -1
  63. package/dist/native.d.ts.map +1 -1
  64. package/dist/native.js.map +1 -1
  65. package/dist/query/atom.d.ts +1 -1
  66. package/dist/query/atom.d.ts.map +1 -1
  67. package/dist/query/atom.js.map +1 -1
  68. package/dist/query/description.d.ts +33 -0
  69. package/dist/query/description.d.ts.map +1 -0
  70. package/dist/query/description.js +302 -0
  71. package/dist/query/description.js.map +1 -0
  72. package/dist/query/lower.d.ts +60 -4
  73. package/dist/query/lower.d.ts.map +1 -1
  74. package/dist/query/lower.js +126 -161
  75. package/dist/query/lower.js.map +1 -1
  76. package/dist/query/parse-ir.d.ts +7 -2
  77. package/dist/query/parse-ir.d.ts.map +1 -1
  78. package/dist/query/parse-ir.js +240 -53
  79. package/dist/query/parse-ir.js.map +1 -1
  80. package/dist/query/run.d.ts.map +1 -1
  81. package/dist/query/run.js +8 -10
  82. package/dist/query/run.js.map +1 -1
  83. package/dist/query/scope.d.ts +3 -4
  84. package/dist/query/scope.d.ts.map +1 -1
  85. package/dist/query/scope.js +5 -10
  86. package/dist/query/scope.js.map +1 -1
  87. package/dist/relation.d.ts +22 -48
  88. package/dist/relation.d.ts.map +1 -1
  89. package/dist/relation.js +30 -82
  90. package/dist/relation.js.map +1 -1
  91. package/dist/rows.d.ts +7 -4
  92. package/dist/rows.d.ts.map +1 -1
  93. package/dist/rows.js +44 -198
  94. package/dist/rows.js.map +1 -1
  95. package/dist/runtime-errors.d.ts +26 -2
  96. package/dist/runtime-errors.d.ts.map +1 -1
  97. package/dist/runtime-errors.js +43 -3
  98. package/dist/runtime-errors.js.map +1 -1
  99. package/dist/schema.d.ts +9 -1
  100. package/dist/schema.d.ts.map +1 -1
  101. package/dist/schema.js +85 -24
  102. package/dist/schema.js.map +1 -1
  103. package/dist/selection.d.ts +27 -0
  104. package/dist/selection.d.ts.map +1 -0
  105. package/dist/selection.js +104 -0
  106. package/dist/selection.js.map +1 -0
  107. package/dist/shape.d.ts +6 -9
  108. package/dist/shape.d.ts.map +1 -1
  109. package/dist/statements.d.ts +17 -52
  110. package/dist/statements.d.ts.map +1 -1
  111. package/dist/statements.js +92 -111
  112. package/dist/statements.js.map +1 -1
  113. package/dist/values.d.ts +25 -0
  114. package/dist/values.d.ts.map +1 -0
  115. package/dist/values.js +203 -0
  116. package/dist/values.js.map +1 -0
  117. package/pack-provenance.json +3 -3
  118. package/package.json +4 -4
  119. package/src/capacity.ts +86 -76
  120. package/src/changes.ts +183 -54
  121. package/src/closed.ts +161 -314
  122. package/src/codec.ts +116 -190
  123. package/src/compile.ts +56 -32
  124. package/src/db-native.ts +42 -0
  125. package/src/db.ts +134 -52
  126. package/src/errors.ts +16 -1
  127. package/src/face.ts +44 -74
  128. package/src/fields.ts +100 -146
  129. package/src/immutable.ts +76 -0
  130. package/src/index.ts +34 -28
  131. package/src/internal/log.ts +2 -0
  132. package/src/law.ts +13 -13
  133. package/src/lower.ts +13 -10
  134. package/src/native.ts +2 -0
  135. package/src/query/atom.ts +1 -0
  136. package/src/query/description.ts +394 -0
  137. package/src/query/lower.ts +139 -173
  138. package/src/query/parse-ir.ts +247 -54
  139. package/src/query/run.ts +15 -9
  140. package/src/query/scope.ts +10 -18
  141. package/src/relation.ts +58 -142
  142. package/src/rows.ts +62 -205
  143. package/src/runtime-errors.ts +47 -3
  144. package/src/schema.ts +99 -29
  145. package/src/selection.ts +124 -0
  146. package/src/shape.ts +9 -9
  147. package/src/statements.ts +138 -189
  148. package/src/values.ts +223 -0
package/src/fields.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  import { regex } from "arkregex"
2
- import { Result } from "effect"
3
2
  import { AuthoringError } from "#errors.ts"
4
- import { DbError } from "#runtime-errors.ts"
5
3
  import type { LiteralSpec } from "#spec.ts"
6
- import { Uuid } from "#uuid.ts"
4
+ import type { Uuid } from "#uuid.ts"
5
+ import { arrayValue, fieldValue, recordValue, taggedValueOf, U64_MAX } from "#values.ts"
7
6
 
8
7
  const INTEGER_INDEX_NAME = regex("^(?:0|[1-9][0-9]*)$")
9
8
 
@@ -23,54 +22,15 @@ interface IntervalValue {
23
22
  /**
24
23
  * A half-open dense float interval `[start, end)` as a plain value object:
25
24
  * two canonical binary64 bounds on the dense numeric line.
26
- * NaN is never an endpoint, signed zero is normalized at the checked
27
- * constructor and again by the native boundary, and strict `start < end`
28
- * makes empty spans unrepresentable through {@link span}. Infinite bounds
29
- * denote a missing bound, not a member point.
25
+ * Field-directed validation rejects NaN endpoints and empty intervals and
26
+ * normalizes signed zero. Infinite bounds denote a missing bound, not a
27
+ * member point; a plain object alone is not evidence of validation.
30
28
  */
31
29
  interface FloatIntervalValue {
32
30
  readonly start: number
33
31
  readonly end: number
34
32
  }
35
33
 
36
- /**
37
- * Constructs a checked interval literal — the `start..end` spelling.
38
- * Half-open and nonempty by construction. Fallible pure parsing
39
- * returns `Result` (use `Effect.fromResult(span(...))` inside a generator),
40
- * never hidden I/O and never a thrown domain outcome.
41
- *
42
- * Two element domains, selected by argument type: `span(0n, 60n)` is a
43
- * discrete integer interval; `span(0.5, 1.5)` is a dense float interval
44
- * with canonical endpoints (NaN refused, `-0` normalized to `+0`,
45
- * `-Infinity` legal only as the lower bound and `+Infinity` only as the
46
- * upper bound — both enforced by strict numeric `start < end`).
47
- */
48
- function span(start: bigint, end: bigint): Result.Result<IntervalValue, DbError>
49
- function span(start: number, end: number): Result.Result<FloatIntervalValue, DbError>
50
- function span(
51
- start: bigint | number,
52
- end: bigint | number
53
- ): Result.Result<IntervalValue, DbError> | Result.Result<FloatIntervalValue, DbError> {
54
- if (typeof start === "bigint" && typeof end === "bigint") {
55
- if (start >= end) {
56
- return Result.fail(new DbError({ operation: "span", reason: { _tag: "InvalidArgument" } }))
57
- }
58
- return Result.succeed(Object.freeze({ start, end }))
59
- }
60
- if (typeof start === "number" && typeof end === "number") {
61
- if (Number.isNaN(start) || Number.isNaN(end)) {
62
- return Result.fail(new DbError({ operation: "span", reason: { _tag: "InvalidArgument" } }))
63
- }
64
- const lo = Object.is(start, -0) ? 0 : start
65
- const hi = Object.is(end, -0) ? 0 : end
66
- if (!(lo < hi)) {
67
- return Result.fail(new DbError({ operation: "span", reason: { _tag: "InvalidArgument" } }))
68
- }
69
- return Result.succeed(Object.freeze({ start: lo, end: hi }))
70
- }
71
- return Result.fail(new DbError({ operation: "span", reason: { _tag: "InvalidArgument" } }))
72
- }
73
-
74
34
  /**
75
35
  * Nonempty declaration-order handle vector — the ONE roster carrier. The
76
36
  * handle union is `Handles[number]`, the ordinal of a handle is its tuple
@@ -187,11 +147,11 @@ type Infer<F extends AnyField> = F extends { readonly kind: "bool" }
187
147
  ? Uuid
188
148
  : F extends { readonly kind: "bytes" }
189
149
  ? Uint8Array
190
- : F extends { readonly kind: "interval"; readonly element: "f64" }
191
- ? FloatIntervalValue
192
- : F extends { readonly kind: "interval" }
193
- ? IntervalValue
194
- : never
150
+ : F extends { readonly kind: "interval"; readonly element: infer Element }
151
+ ? Element extends "f64"
152
+ ? FloatIntervalValue
153
+ : IntervalValue
154
+ : never
195
155
 
196
156
  /**
197
157
  * The typed shape refusal shared by every literal machine — the selection
@@ -263,47 +223,10 @@ function isFloatIntervalValue(value: unknown): value is FloatIntervalValue {
263
223
  )
264
224
  }
265
225
 
266
- function handleLiteral(closed: AnyClosedRoster, value: unknown): LiteralSpec {
267
- if (typeof value !== "string") {
268
- throw literalShapeError("selection literal", `a ${closed.name} handle name (string)`, value)
269
- }
270
- if (!closed.handles.includes(value)) {
271
- throw new AuthoringError({
272
- message: `"${value}" is not a handle of ${closed.name} — the roster is ${closed.handles.join(", ")}`
273
- })
274
- }
275
- return { kind: "handle", handle: value }
276
- }
277
-
278
- function intervalLiteral(element: IntervalElementKind, value: unknown): LiteralSpec {
279
- if (element === "f64") {
280
- if (!isFloatIntervalValue(value)) {
281
- throw literalShapeError("selection literal", "float interval ({ start, end } numbers)", value)
282
- }
283
- if (Number.isNaN(value.start) || Number.isNaN(value.end) || !(value.start < value.end)) {
284
- throw new AuthoringError({
285
- message: "selection literal: a float interval is half-open and nonempty with non-NaN canonical endpoints"
286
- })
287
- }
288
- return {
289
- kind: "value",
290
- value: {
291
- kind: "intervalF64",
292
- start: Object.is(value.start, -0) ? 0 : value.start,
293
- end: Object.is(value.end, -0) ? 0 : value.end
294
- }
295
- }
296
- }
297
- if (!isIntervalValue(value)) {
298
- throw literalShapeError("selection literal", "interval ({ start, end } bigints)", value)
299
- }
300
- if (element === "u64") {
301
- return { kind: "value", value: { kind: "intervalU64", start: value.start, end: value.end } }
302
- }
303
- return { kind: "value", value: { kind: "intervalI64", start: value.start, end: value.end } }
304
- }
305
-
306
226
  function assertDeclarationOrderKey(where: string, name: string): void {
227
+ if (typeof name !== "string" || !name.isWellFormed()) {
228
+ throw new AuthoringError({ message: `${where}: expected a well-formed Unicode name` })
229
+ }
307
230
  if (INTEGER_INDEX_NAME.test(name)) {
308
231
  throw new AuthoringError({
309
232
  message: `${where}: name ${name} is an integer index — JavaScript object keys re-order integer indices, breaking the declaration-order law; use a non-numeric name`
@@ -317,12 +240,92 @@ function assertDeclarationOrderKey(where: string, name: string): void {
317
240
  }
318
241
 
319
242
  function assertDeclarationRecord(where: string, record: object): void {
243
+ if (typeof record !== "object" || record === null) {
244
+ throw new AuthoringError({ message: `${where}: expected a plain declaration record` })
245
+ }
320
246
  const proto = Object.getPrototypeOf(record)
321
247
  if (proto !== Object.prototype && proto !== null) {
322
248
  throw new AuthoringError({
323
249
  message: `${where}: the declaration record's prototype was replaced — a plain \`__proto__: {...}\` entry is the prototype setter, so its key silently vanishes from the declaration; spell it computed (["__proto__"]: {...}) to declare it as data`
324
250
  })
325
251
  }
252
+ for (const key of Reflect.ownKeys(record)) {
253
+ const property = Object.getOwnPropertyDescriptor(record, key)
254
+ if (typeof key !== "string" || property?.enumerable !== true || !("value" in property)) {
255
+ throw new AuthoringError({ message: `${where}: declarations require enumerable own data fields` })
256
+ }
257
+ }
258
+ }
259
+
260
+ /** Own a checked roster; never freeze the caller's array. */
261
+ function ownHandles<const H extends ClosedHandleTuple>(context: string, handles: H): H
262
+ function ownHandles(context: string, handles: unknown): ClosedHandleTuple
263
+ function ownHandles(context: string, handles: unknown): ClosedHandleTuple {
264
+ if (!Array.isArray(handles) || handles.length === 0) {
265
+ throw new AuthoringError({ message: `${context}: expected a nonempty handle tuple` })
266
+ }
267
+ const seen = new Set<string>()
268
+ const values = arrayValue(context, handles, (_, handle) => {
269
+ if (typeof handle !== "string") throw new AuthoringError({ message: `${context}: expected string handles` })
270
+ if (!handle.isWellFormed()) throw new AuthoringError({ message: `${context}: expected well-formed string handles` })
271
+ if (seen.has(handle)) throw new AuthoringError({ message: `${context}: duplicate handle ${handle}` })
272
+ seen.add(handle)
273
+ return handle
274
+ })
275
+ const first = values[0]
276
+ if (first === undefined) throw new AuthoringError({ message: `${context}: expected a nonempty handle tuple` })
277
+ return Object.freeze([first, ...values.slice(1)])
278
+ }
279
+
280
+ /** Checked structural field descriptors, shared by declaration boundaries. */
281
+ function fieldDescriptor<F extends AnyField>(context: string, input: F): F
282
+ function fieldDescriptor(context: string, input: unknown): AnyField
283
+ function fieldDescriptor(context: string, input: unknown): AnyField {
284
+ if (typeof input !== "object" || input === null || !("kind" in input)) {
285
+ throw new AuthoringError({ message: `${context}: expected a field descriptor` })
286
+ }
287
+ assertDeclarationRecord(context, input)
288
+ switch (input.kind) {
289
+ case "bool":
290
+ case "str":
291
+ case "i64":
292
+ case "f64":
293
+ case "uuid":
294
+ recordValue(context, input, ["kind"])
295
+ return Object.freeze({ kind: input.kind })
296
+ case "u64": {
297
+ if (!("closed" in input)) {
298
+ recordValue(context, input, ["kind"])
299
+ return u64
300
+ }
301
+ recordValue(context, input, ["kind", "closed"])
302
+ const roster = recordValue(context, input.closed, ["name", "handles"])
303
+ if (typeof roster.name !== "string") throw new AuthoringError({ message: `${context}: expected a roster name` })
304
+ assertDeclarationOrderKey(context, roster.name)
305
+ return Object.freeze({
306
+ kind: "u64",
307
+ closed: Object.freeze({ name: roster.name, handles: ownHandles(context, roster.handles) })
308
+ })
309
+ }
310
+ case "bytes": {
311
+ const value = recordValue(context, input, ["kind", "width"])
312
+ if (typeof value.width !== "number") throw new AuthoringError({ message: `${context}: expected a bytes width` })
313
+ return bytes(value.width)
314
+ }
315
+ case "interval": {
316
+ const value = recordValue(context, input, ["kind", "element", "width"])
317
+ if (value.element !== "u64" && value.element !== "i64" && value.element !== "f64") {
318
+ throw new AuthoringError({ message: `${context}: expected an interval element kind` })
319
+ }
320
+ if (value.width === undefined) return interval({ kind: value.element })
321
+ if (value.element === "f64" || typeof value.width !== "bigint") {
322
+ throw new AuthoringError({ message: `${context}: only discrete intervals accept a bigint width` })
323
+ }
324
+ return interval({ kind: value.element }, value.width)
325
+ }
326
+ default:
327
+ throw new AuthoringError({ message: `${context}: unknown field kind` })
328
+ }
326
329
  }
327
330
 
328
331
  const u64: U64Field = Object.freeze({ kind: "u64" })
@@ -371,69 +374,19 @@ function interval(
371
374
  "interval(f64) takes no width — a fixed-width float interval is unrepresentable (rounded start + width is not an exact fixed length on the dense line); applications supply two checked bounds"
372
375
  })
373
376
  }
374
- if (width !== undefined && width < 1n) {
377
+ if (width !== undefined && (typeof width !== "bigint" || width < 1n || width > U64_MAX)) {
375
378
  throw new AuthoringError({
376
- message: `interval width must be >= 1 (got ${width}) — w >= 1 is pinned at declaration`
379
+ message: "interval width must be a bigint in 1..=u64::MAX"
377
380
  })
378
381
  }
379
382
  return Object.freeze({ kind: "interval", element: elementKind, width })
380
383
  }
381
384
 
382
385
  function literalOf(field: AnyField, value: unknown): LiteralSpec {
383
- const roster = rosterOf(field)
384
- if (roster !== undefined) {
385
- return handleLiteral(roster, value)
386
- }
387
- switch (field.kind) {
388
- case "bool": {
389
- if (typeof value !== "boolean") {
390
- throw literalShapeError("selection literal", "boolean", value)
391
- }
392
- return { kind: "value", value: { kind: "bool", value } }
393
- }
394
- case "u64": {
395
- if (typeof value !== "bigint") {
396
- throw literalShapeError("selection literal", "bigint", value)
397
- }
398
- return { kind: "value", value: { kind: "u64", value } }
399
- }
400
- case "i64": {
401
- if (typeof value !== "bigint") {
402
- throw literalShapeError("selection literal", "bigint", value)
403
- }
404
- return { kind: "value", value: { kind: "i64", value } }
405
- }
406
- case "str": {
407
- if (typeof value !== "string") {
408
- throw literalShapeError("selection literal", "string", value)
409
- }
410
-
411
- if (!value.isWellFormed()) {
412
- throw literalShapeError("selection literal", "well-formed string", value)
413
- }
414
- return { kind: "value", value: { kind: "string", value } }
415
- }
416
- case "f64": {
417
- if (typeof value !== "number") {
418
- throw literalShapeError("selection literal", "number", value)
419
- }
420
- return { kind: "value", value: { kind: "f64", value } }
421
- }
422
- case "uuid": {
423
- if (!Uuid.isUuid(value)) {
424
- throw literalShapeError("selection literal", "a UUID (canonical UUID text)", value)
425
- }
426
- return { kind: "value", value: { kind: "uuid", value } }
427
- }
428
- case "bytes": {
429
- if (!(value instanceof Uint8Array)) {
430
- throw literalShapeError("selection literal", "Uint8Array", value)
431
- }
432
- return { kind: "value", value: { kind: "fixedBytes", value } }
433
- }
434
- case "interval":
435
- return intervalLiteral(field.element, value)
386
+ if ("closed" in field) {
387
+ return { kind: "handle", handle: fieldValue("selection literal", field, value) }
436
388
  }
389
+ return { kind: "value", value: taggedValueOf("selection literal", field, value) }
437
390
  }
438
391
 
439
392
  export type {
@@ -463,16 +416,17 @@ export {
463
416
  bool,
464
417
  bytes,
465
418
  f64,
419
+ fieldDescriptor,
466
420
  i64,
467
421
  interval,
468
422
  isFloatIntervalValue,
469
423
  isIntervalValue,
470
424
  literalOf,
471
425
  literalShapeError,
426
+ ownHandles,
472
427
  rosterOf,
473
428
  rostersAgree,
474
429
  signaturesAgree,
475
- span,
476
430
  str,
477
431
  u64,
478
432
  uuid
@@ -0,0 +1,76 @@
1
+ /** Only frozen plain data graphs are stable cache keys. Typed-array contents
2
+ * remain mutable even behind a frozen parent, so they never qualify. */
3
+ const immutable = new WeakSet<object>()
4
+
5
+ function isImmutable(input: unknown, visiting = new WeakSet<object>()): boolean {
6
+ if (typeof input !== "object" || input === null) return typeof input !== "function"
7
+ if (immutable.has(input)) return true
8
+ const prototype = Object.getPrototypeOf(input)
9
+ if (
10
+ (prototype !== Object.prototype && prototype !== null && prototype !== Array.prototype) ||
11
+ !Object.isFrozen(input) ||
12
+ visiting.has(input)
13
+ )
14
+ return false
15
+ visiting.add(input)
16
+ for (const name of Reflect.ownKeys(input)) {
17
+ const property = Object.getOwnPropertyDescriptor(input, name)
18
+ if (property === undefined || !("value" in property) || !isImmutable(property.value, visiting)) {
19
+ visiting.delete(input)
20
+ return false
21
+ }
22
+ }
23
+ visiting.delete(input)
24
+ immutable.add(input)
25
+ return true
26
+ }
27
+
28
+ /** Validation is never skipped for a mutable structural input. Validated
29
+ * immutable outputs can be reused without requiring constructor identity. */
30
+ function descriptorCache<A extends object>(parse: (input: unknown) => A): (input: unknown) => A {
31
+ const cache = new WeakMap<object, A>()
32
+ return (input) => {
33
+ if (typeof input === "object" && input !== null) {
34
+ const found = cache.get(input)
35
+ if (found !== undefined) return found
36
+ }
37
+ const result = parse(input)
38
+ if (isImmutable(result)) {
39
+ cache.set(result, result)
40
+ if (typeof input === "object" && input !== null && isImmutable(input)) cache.set(input, result)
41
+ }
42
+ return result
43
+ }
44
+ }
45
+
46
+ /** Detached inspection data: share proven immutable branches, copy byte
47
+ * buffers and their parents, and preserve references within one snapshot.
48
+ * This is for already checked internal data, not an input validator. */
49
+ function snapshotData<A>(input: A, onCopy?: (source: object, snapshot: object) => void): A
50
+ function snapshotData(input: unknown, onCopy?: (source: object, snapshot: object) => void): unknown {
51
+ const copied = new WeakMap<object, object>()
52
+ function visit(value: unknown): unknown {
53
+ if (typeof value !== "object" || value === null || isImmutable(value)) return value
54
+ const existing = copied.get(value)
55
+ if (existing !== undefined) return existing
56
+ if (value instanceof Uint8Array) {
57
+ const bytes = Uint8Array.from(value)
58
+ copied.set(value, bytes)
59
+ return bytes
60
+ }
61
+ const result = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value))
62
+ copied.set(value, result)
63
+ for (const name of Reflect.ownKeys(value)) {
64
+ if (Array.isArray(value) && name === "length") continue
65
+ const property = Object.getOwnPropertyDescriptor(value, name)
66
+ if (property === undefined || !("value" in property)) throw new TypeError("snapshotData requires checked data")
67
+ Object.defineProperty(result, name, { value: visit(property.value), enumerable: property.enumerable === true })
68
+ }
69
+ Object.freeze(result)
70
+ onCopy?.(value, result)
71
+ return result
72
+ }
73
+ return visit(input)
74
+ }
75
+
76
+ export { descriptorCache, isImmutable, snapshotData }
package/src/index.ts CHANGED
@@ -16,40 +16,35 @@ export type {
16
16
  WeightOnSource
17
17
  } from "#capacity.ts"
18
18
  export { duration, ref, weigh, within } from "#capacity.ts"
19
- export type { ChangeDraft } from "#changes.ts"
19
+ export type { ChangeCounts, ChangeDraft, ChangeRecord } from "#changes.ts"
20
20
  export { ChangeSet } from "#changes.ts"
21
21
  export type {
22
22
  AnyClosed,
23
- AnySelectedClosed,
24
23
  AxiomRow,
25
24
  Axioms,
26
25
  Closed,
27
- ClosedColumn,
28
- ClosedCore,
29
- ClosedData,
30
- ClosedRow,
31
- ClosedSelectionInput,
32
- PayloadField,
33
- SelectedClosed
26
+ PayloadField
34
27
  } from "#closed.ts"
35
- export { closed } from "#closed.ts"
28
+ export { closed, closedId } from "#closed.ts"
36
29
  export type { RowShape } from "#codec.ts"
37
30
  export { decodeBoundaryRows, decodeRows, encodeBoundaryRows, encodeRows, rowSchema, rowShape } from "#codec.ts"
38
31
  export type { CompiledSchema, SchemaId } from "#compile.ts"
39
32
  export { Schema } from "#compile.ts"
40
33
  export type {
41
- ApplyExpected,
42
- ApplyOptions,
43
34
  ApplyOutcome,
44
35
  CoreWitness,
45
36
  DbInspection,
37
+ JudgeOutcome,
46
38
  PreparedQuery,
47
39
  QueryReader,
48
40
  Snapshot,
49
- StorageInspection
41
+ StorageInspection,
42
+ WriteExpected,
43
+ WriteOptions
50
44
  } from "#db.ts"
51
45
  export { Db } from "#db.ts"
52
46
  export {
47
+ AuthoringDiagnostic,
53
48
  AuthoringError,
54
49
  NativeLoadError,
55
50
  NativeOperationError,
@@ -61,7 +56,6 @@ export type {
61
56
  Arity,
62
57
  Face,
63
58
  FaceArityMismatch,
64
- FaceData,
65
59
  FaceFields,
66
60
  FaceOwner,
67
61
  FaceShapeMismatch,
@@ -94,14 +88,32 @@ export type {
94
88
  U64Field,
95
89
  UuidField
96
90
  } from "#fields.ts"
97
- export { bool, bytes, f64, i64, interval, span, str, u64, uuid } from "#fields.ts"
91
+ export { bool, bytes, f64, i64, interval, str, u64, uuid } from "#fields.ts"
98
92
  export type { Same, SameLen } from "#judgment.ts"
99
93
  export type { ClassesOf, ClassWall, LawfulStatements, RelationClasses, SchemaClasses } from "#law.ts"
100
- export type { Violation, ViolationFact } from "#native.ts"
94
+ export type {
95
+ AtomIr,
96
+ AtomSourceIr,
97
+ CmpOpIr,
98
+ ComparisonIr,
99
+ ConditionTreeIr,
100
+ FindTermIr,
101
+ HeadTermIr,
102
+ InteriorIr,
103
+ QueryIr,
104
+ RecIr,
105
+ RuleIr,
106
+ ScalarExprIr,
107
+ TermIr,
108
+ Violation,
109
+ ViolationFact
110
+ } from "#native.ts"
101
111
  export type { FindColumn } from "#query/atom.ts"
102
112
  export { ALLEN } from "#query/atom.ts"
103
113
  export type { AnyComputeExpr, ComputeExpr, ComputeValue, QueryNode } from "#query/compute.ts"
104
114
  export { Compute } from "#query/compute.ts"
115
+ export type { DescriptionParameter, DescriptionRow, DescriptionTable, QueryDescription } from "#query/description.ts"
116
+ export { describeQuery, queryFromDescription } from "#query/description.ts"
105
117
  export type { Agg, RowOfFind } from "#query/find.ts"
106
118
  export type {
107
119
  AnyQuery,
@@ -136,16 +148,11 @@ export type {
136
148
  export { v } from "#query/scope.ts"
137
149
  export type {
138
150
  AnyRelation,
139
- AnySelected,
140
151
  Fact,
141
152
  FieldsShape,
142
153
  Relation,
143
- RelationData,
144
154
  RelationField,
145
- RelationFields,
146
- Selected,
147
- SelectionBinding,
148
- SelectionInput
155
+ RelationFields
149
156
  } from "#relation.ts"
150
157
  export { relation } from "#relation.ts"
151
158
  export type { CompleteResult } from "#result.ts"
@@ -169,6 +176,8 @@ export type {
169
176
  export { Scalar } from "#scalar.ts"
170
177
  export type { AnySchema, Schema as SchemaDeclaration, SchemaRelation, SchemaRelations } from "#schema.ts"
171
178
  export { schema } from "#schema.ts"
179
+ export type { AnySelected, FieldsOf, Selected, SelectionBinding, SelectionInput } from "#selection.ts"
180
+ export { select } from "#selection.ts"
172
181
  export type { Key, QueryTemplate, Rel } from "#shape.ts"
173
182
  export type {
174
183
  CapacityBoundSpec,
@@ -185,14 +194,11 @@ export type {
185
194
  ValueTypeSpec
186
195
  } from "#spec.ts"
187
196
  export type {
188
- CapacityData,
189
197
  CapacityStatement,
190
- ContainedStatement,
191
- ContainmentData,
192
- KeyData,
198
+ ContainmentStatement,
193
199
  KeyStatement,
194
- Statement,
195
- StatementData
200
+ MirrorsStatement,
201
+ Statement
196
202
  } from "#statements.ts"
197
203
  export { capacity, contained, key, mirrors, renderStatement } from "#statements.ts"
198
204
  export { Uuid } from "#uuid.ts"
@@ -21,6 +21,7 @@ export {
21
21
  nativeBindingIsLoaded
22
22
  } from "#native.ts"
23
23
  export type { CompleteResult } from "#result.ts"
24
+ export { factCellsOf } from "#rows.ts"
24
25
  export type { NativeRuntimeOptions, RepositoryLock } from "#runtime.ts"
25
26
  export {
26
27
  finalizeClose,
@@ -44,3 +45,4 @@ export type {
44
45
  RuntimeHandle
45
46
  } from "#runtime-native.ts"
46
47
  export { runtimeNative } from "#runtime-native.ts"
48
+ export { schemasAgree } from "#schema.ts"
package/src/law.ts CHANGED
@@ -58,7 +58,7 @@ import { AuthoringError } from "#errors.ts"
58
58
 
59
59
  import type { AnyClosed } from "#closed.ts"
60
60
  import { isClosedMember, sealedFieldsOf } from "#closed.ts"
61
- import type { FaceData } from "#face.ts"
61
+ import type { AnyFace } from "#face.ts"
62
62
  import type { Same } from "#judgment.ts"
63
63
  import type { AnyRelation, RelationFields } from "#relation.ts"
64
64
  import type { SchemaRelation, SchemaRelations } from "#schema.ts"
@@ -104,9 +104,9 @@ type ZipCoords<
104
104
  : Acc
105
105
  : Acc
106
106
 
107
- type StatementPairs<St extends Statement> = St["data"] extends {
108
- readonly source: infer S extends FaceData
109
- readonly target: infer T extends FaceData
107
+ type StatementPairs<St extends Statement> = St extends {
108
+ readonly source: infer S extends AnyFace
109
+ readonly target: infer T extends AnyFace
110
110
  }
111
111
  ? string extends S["owner"]["name"]
112
112
  ? []
@@ -208,7 +208,7 @@ type DeclaredKeysOf<Stmts extends readonly Statement[], Acc extends readonly Key
208
208
  infer H extends Statement,
209
209
  ...infer T extends readonly Statement[]
210
210
  ]
211
- ? H["data"] extends {
211
+ ? H extends {
212
212
  readonly kind: "key"
213
213
  readonly owner: infer O extends AnyRelation
214
214
  readonly projection: infer P extends readonly string[]
@@ -295,8 +295,8 @@ type DecidableRoster<Stmts extends readonly Statement[]> = [true] extends [IsMul
295
295
  : [Stmts] extends [readonly [infer H extends Statement, ...infer T extends readonly Statement[]]]
296
296
  ? [true] extends [IsMulti<H>]
297
297
  ? false
298
- : "key" extends H["data"]["kind"]
299
- ? [DecidableKeyData<H["data"]>] extends [true]
298
+ : "key" extends H["kind"]
299
+ ? [DecidableKeyData<H>] extends [true]
300
300
  ? DecidableRoster<T>
301
301
  : false
302
302
  : DecidableRoster<T>
@@ -322,7 +322,7 @@ type DeclaredKeyMatch<N extends string, PU extends string, Keys extends readonly
322
322
  : DeclaredKeyMatch<N, PU, T>
323
323
  : TargetKeyWall<N, PU>
324
324
 
325
- type JudgeTargetFace<F extends FaceData, Keys extends readonly KeyEntry[]> = string extends F["owner"]["name"]
325
+ type JudgeTargetFace<F extends AnyFace, Keys extends readonly KeyEntry[]> = string extends F["owner"]["name"]
326
326
  ? true
327
327
  : string extends F["projection"][number]
328
328
  ? true
@@ -334,9 +334,9 @@ type JudgeTargetFace<F extends FaceData, Keys extends readonly KeyEntry[]> = str
334
334
  ? DeclaredKeyMatch<F["owner"]["name"], F["projection"][number], Keys>
335
335
  : true
336
336
 
337
- type JudgeStatement<St extends Statement, Keys extends readonly KeyEntry[]> = St["data"] extends {
338
- readonly source: FaceData
339
- readonly target: infer Tg extends FaceData
337
+ type JudgeStatement<St extends Statement, Keys extends readonly KeyEntry[]> = St extends {
338
+ readonly source: AnyFace
339
+ readonly target: infer Tg extends AnyFace
340
340
  }
341
341
  ? JudgeTargetFace<Tg, Keys>
342
342
  : true
@@ -452,8 +452,8 @@ function makeUnionFind(): UnionFind {
452
452
  }
453
453
  }
454
454
 
455
- function statementFaces(statement: Statement): readonly [FaceData, FaceData] | undefined {
456
- const data = statement.data
455
+ function statementFaces(statement: Statement): readonly [AnyFace, AnyFace] | undefined {
456
+ const data = statement
457
457
  if (data.kind === "key") {
458
458
  return undefined
459
459
  }
package/src/lower.ts CHANGED
@@ -15,10 +15,10 @@ import { AuthoringError } from "#errors.ts"
15
15
 
16
16
  import type { AnyClosed } from "#closed.ts"
17
17
  import { isClosedMember } from "#closed.ts"
18
- import type { FaceData } from "#face.ts"
19
- import type { AnyField } from "#fields.ts"
18
+ import type { AnyFace } from "#face.ts"
19
+ import { type AnyField, literalOf } from "#fields.ts"
20
20
  import type { RelationClasses } from "#law.ts"
21
- import type { AnyRelation } from "#relation.ts"
21
+ import { type AnyRelation, relationFields } from "#relation.ts"
22
22
  import type { AnySchema } from "#schema.ts"
23
23
  import type {
24
24
  FieldSpec,
@@ -60,7 +60,7 @@ function lowerField(name: string, field: AnyField, newtype: string | undefined):
60
60
  }
61
61
  }
62
62
 
63
- function lowerFace(face: FaceData): SideSpec {
63
+ function lowerFace(face: AnyFace): SideSpec {
64
64
  return {
65
65
  relation: face.owner.name,
66
66
  projection: [...face.projection],
@@ -71,7 +71,7 @@ function lowerFace(face: FaceData): SideSpec {
71
71
  }
72
72
 
73
73
  function lowerStatement(statement: Statement): StatementSpec {
74
- const data = statement.data
74
+ const data = statement
75
75
  switch (data.kind) {
76
76
  case "key":
77
77
  return { kind: "fd", relation: data.owner.name, projection: [...data.projection] }
@@ -101,18 +101,21 @@ function lowerStatement(statement: Statement): StatementSpec {
101
101
  }
102
102
 
103
103
  function lowerRelation(relation: AnyRelation, classes: RelationClasses): RelationSpec {
104
- const fields: FieldSpec[] = relation.data.fields.map(function lowerDeclared(declared) {
104
+ const fields: FieldSpec[] = relationFields(relation).map(function lowerDeclared(declared) {
105
105
  return lowerField(declared.name, declared.field, classes[declared.name])
106
106
  })
107
107
  return { name: relation.name, fields, closed: undefined }
108
108
  }
109
109
 
110
110
  function lowerClosed(member: AnyClosed, classes: RelationClasses): RelationSpec {
111
- const fields: FieldSpec[] = member.data.columns.map(function lowerColumn(column) {
112
- return lowerField(column.name, column.field, classes[column.name])
111
+ const fields: FieldSpec[] = Object.entries(member.columns).map(function lowerColumn([name, field]) {
112
+ return lowerField(name, field, classes[name])
113
113
  })
114
- const rows = member.data.rows.map(function lowerRow(row) {
115
- return { handle: row.handle, values: row.values }
114
+ const rows = member.handles.map(function lowerRow(handle) {
115
+ return {
116
+ handle,
117
+ values: Object.entries(member.columns).map(([name, field]) => literalOf(field, member.axioms[handle]?.[name]))
118
+ }
116
119
  })
117
120
  const newtype = classes.id
118
121
  if (newtype === undefined) {