@bjornpagen/bumbledb 1.1.0 → 1.2.1

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 (152) hide show
  1. package/COOKBOOK.md +114 -11
  2. package/README.md +52 -9
  3. package/dist/capacity.d.ts +22 -59
  4. package/dist/capacity.d.ts.map +1 -1
  5. package/dist/capacity.js +66 -13
  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 +16 -12
  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 +13 -11
  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/find.d.ts +21 -6
  73. package/dist/query/find.d.ts.map +1 -1
  74. package/dist/query/find.js.map +1 -1
  75. package/dist/query/lower.d.ts +75 -10
  76. package/dist/query/lower.d.ts.map +1 -1
  77. package/dist/query/lower.js +132 -163
  78. package/dist/query/lower.js.map +1 -1
  79. package/dist/query/parse-ir.d.ts +7 -2
  80. package/dist/query/parse-ir.d.ts.map +1 -1
  81. package/dist/query/parse-ir.js +240 -53
  82. package/dist/query/parse-ir.js.map +1 -1
  83. package/dist/query/run.d.ts.map +1 -1
  84. package/dist/query/run.js +8 -10
  85. package/dist/query/run.js.map +1 -1
  86. package/dist/query/scope.d.ts +24 -16
  87. package/dist/query/scope.d.ts.map +1 -1
  88. package/dist/query/scope.js +5 -10
  89. package/dist/query/scope.js.map +1 -1
  90. package/dist/relation.d.ts +22 -48
  91. package/dist/relation.d.ts.map +1 -1
  92. package/dist/relation.js +30 -82
  93. package/dist/relation.js.map +1 -1
  94. package/dist/rows.d.ts +7 -4
  95. package/dist/rows.d.ts.map +1 -1
  96. package/dist/rows.js +44 -198
  97. package/dist/rows.js.map +1 -1
  98. package/dist/runtime-errors.d.ts +26 -2
  99. package/dist/runtime-errors.d.ts.map +1 -1
  100. package/dist/runtime-errors.js +43 -3
  101. package/dist/runtime-errors.js.map +1 -1
  102. package/dist/schema.d.ts +9 -1
  103. package/dist/schema.d.ts.map +1 -1
  104. package/dist/schema.js +85 -24
  105. package/dist/schema.js.map +1 -1
  106. package/dist/selection.d.ts +27 -0
  107. package/dist/selection.d.ts.map +1 -0
  108. package/dist/selection.js +104 -0
  109. package/dist/selection.js.map +1 -0
  110. package/dist/shape.d.ts +12 -12
  111. package/dist/shape.d.ts.map +1 -1
  112. package/dist/statements.d.ts +17 -52
  113. package/dist/statements.d.ts.map +1 -1
  114. package/dist/statements.js +92 -111
  115. package/dist/statements.js.map +1 -1
  116. package/dist/values.d.ts +25 -0
  117. package/dist/values.d.ts.map +1 -0
  118. package/dist/values.js +203 -0
  119. package/dist/values.js.map +1 -0
  120. package/pack-provenance.json +3 -3
  121. package/package.json +4 -4
  122. package/src/capacity.ts +93 -80
  123. package/src/changes.ts +183 -54
  124. package/src/closed.ts +161 -314
  125. package/src/codec.ts +116 -190
  126. package/src/compile.ts +56 -32
  127. package/src/db-native.ts +42 -0
  128. package/src/db.ts +134 -52
  129. package/src/errors.ts +16 -1
  130. package/src/face.ts +44 -74
  131. package/src/fields.ts +100 -146
  132. package/src/immutable.ts +76 -0
  133. package/src/index.ts +36 -29
  134. package/src/internal/log.ts +2 -0
  135. package/src/law.ts +21 -18
  136. package/src/lower.ts +13 -10
  137. package/src/native.ts +2 -0
  138. package/src/query/atom.ts +1 -0
  139. package/src/query/description.ts +394 -0
  140. package/src/query/find.ts +20 -7
  141. package/src/query/lower.ts +177 -187
  142. package/src/query/parse-ir.ts +247 -54
  143. package/src/query/run.ts +15 -9
  144. package/src/query/scope.ts +42 -33
  145. package/src/relation.ts +58 -142
  146. package/src/rows.ts +62 -205
  147. package/src/runtime-errors.ts +47 -3
  148. package/src/schema.ts +99 -29
  149. package/src/selection.ts +124 -0
  150. package/src/shape.ts +18 -12
  151. package/src/statements.ts +138 -189
  152. 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,15 +88,33 @@ 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"
105
- export type { Agg, RowOfFind } from "#query/find.ts"
115
+ export type { DescriptionParameter, DescriptionRow, DescriptionTable, QueryDescription } from "#query/description.ts"
116
+ export { describeQuery, queryFromDescription } from "#query/description.ts"
117
+ export type { Agg, HeadRecordOf, RowOfFind } from "#query/find.ts"
106
118
  export type {
107
119
  AnyQuery,
108
120
  AnyRuleValue,
@@ -124,6 +136,7 @@ export { lowerQuery, query } from "#query/lower.ts"
124
136
  export type {
125
137
  ClassedField,
126
138
  Flatten,
139
+ ImportedFieldVar,
127
140
  MatchFields,
128
141
  MatchOwner,
129
142
  Param,
@@ -136,16 +149,11 @@ export type {
136
149
  export { v } from "#query/scope.ts"
137
150
  export type {
138
151
  AnyRelation,
139
- AnySelected,
140
152
  Fact,
141
153
  FieldsShape,
142
154
  Relation,
143
- RelationData,
144
155
  RelationField,
145
- RelationFields,
146
- Selected,
147
- SelectionBinding,
148
- SelectionInput
156
+ RelationFields
149
157
  } from "#relation.ts"
150
158
  export { relation } from "#relation.ts"
151
159
  export type { CompleteResult } from "#result.ts"
@@ -169,6 +177,8 @@ export type {
169
177
  export { Scalar } from "#scalar.ts"
170
178
  export type { AnySchema, Schema as SchemaDeclaration, SchemaRelation, SchemaRelations } from "#schema.ts"
171
179
  export { schema } from "#schema.ts"
180
+ export type { AnySelected, FieldsOf, Selected, SelectionBinding, SelectionInput } from "#selection.ts"
181
+ export { select } from "#selection.ts"
172
182
  export type { Key, QueryTemplate, Rel } from "#shape.ts"
173
183
  export type {
174
184
  CapacityBoundSpec,
@@ -185,14 +195,11 @@ export type {
185
195
  ValueTypeSpec
186
196
  } from "#spec.ts"
187
197
  export type {
188
- CapacityData,
189
198
  CapacityStatement,
190
- ContainedStatement,
191
- ContainmentData,
192
- KeyData,
199
+ ContainmentStatement,
193
200
  KeyStatement,
194
- Statement,
195
- StatementData
201
+ MirrorsStatement,
202
+ Statement
196
203
  } from "#statements.ts"
197
204
  export { capacity, contained, key, mirrors, renderStatement } from "#statements.ts"
198
205
  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
@@ -377,11 +377,14 @@ type ComputedClasses<Rels extends SchemaRelations, Comps extends readonly string
377
377
  }
378
378
  }
379
379
 
380
- type ClassesOf<Rels extends SchemaRelations, Stmts extends readonly Statement[]> = ComputedClasses<
381
- Rels,
382
- BuildComps<PairsOf<Stmts>>,
383
- GeneratorsOf<Rels>
384
- >
380
+ // A generated/erased statement array is not an empty theory. Its runtime
381
+ // containments may connect any compatible fields, so retain unknown classes
382
+ // until the native authoring boundary rather than inventing bare carriers.
383
+ type ClassesOf<Rels extends SchemaRelations, Stmts extends readonly Statement[]> = [DecidableRoster<Stmts>] extends [
384
+ true
385
+ ]
386
+ ? ComputedClasses<Rels, BuildComps<PairsOf<Stmts>>, GeneratorsOf<Rels>>
387
+ : SchemaClasses
385
388
 
386
389
  interface MemberCoords {
387
390
  readonly relation: string
@@ -452,8 +455,8 @@ function makeUnionFind(): UnionFind {
452
455
  }
453
456
  }
454
457
 
455
- function statementFaces(statement: Statement): readonly [FaceData, FaceData] | undefined {
456
- const data = statement.data
458
+ function statementFaces(statement: Statement): readonly [AnyFace, AnyFace] | undefined {
459
+ const data = statement
457
460
  if (data.kind === "key") {
458
461
  return undefined
459
462
  }