@bjornpagen/bumbledb 0.1.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 (108) hide show
  1. package/LICENSE +12 -0
  2. package/README.md +84 -0
  3. package/dist/brand.d.ts +59 -0
  4. package/dist/brand.d.ts.map +1 -0
  5. package/dist/brand.js +47 -0
  6. package/dist/brand.js.map +1 -0
  7. package/dist/closed.d.ts +97 -0
  8. package/dist/closed.d.ts.map +1 -0
  9. package/dist/closed.js +107 -0
  10. package/dist/closed.js.map +1 -0
  11. package/dist/count.d.ts +55 -0
  12. package/dist/count.d.ts.map +1 -0
  13. package/dist/count.js +92 -0
  14. package/dist/count.js.map +1 -0
  15. package/dist/db.d.ts +341 -0
  16. package/dist/db.d.ts.map +1 -0
  17. package/dist/db.js +1016 -0
  18. package/dist/db.js.map +1 -0
  19. package/dist/exhume.d.ts +130 -0
  20. package/dist/exhume.d.ts.map +1 -0
  21. package/dist/exhume.js +145 -0
  22. package/dist/exhume.js.map +1 -0
  23. package/dist/face.d.ts +91 -0
  24. package/dist/face.d.ts.map +1 -0
  25. package/dist/face.js +69 -0
  26. package/dist/face.js.map +1 -0
  27. package/dist/fields.d.ts +187 -0
  28. package/dist/fields.d.ts.map +1 -0
  29. package/dist/fields.js +204 -0
  30. package/dist/fields.js.map +1 -0
  31. package/dist/index.d.ts +49 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +31 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/lower.d.ts +34 -0
  36. package/dist/lower.d.ts.map +1 -0
  37. package/dist/lower.js +105 -0
  38. package/dist/lower.js.map +1 -0
  39. package/dist/marshal.d.ts +79 -0
  40. package/dist/marshal.d.ts.map +1 -0
  41. package/dist/marshal.js +167 -0
  42. package/dist/marshal.js.map +1 -0
  43. package/dist/native.d.ts +532 -0
  44. package/dist/native.d.ts.map +1 -0
  45. package/dist/native.js +64 -0
  46. package/dist/native.js.map +1 -0
  47. package/dist/query/atom.d.ts +285 -0
  48. package/dist/query/atom.d.ts.map +1 -0
  49. package/dist/query/atom.js +281 -0
  50. package/dist/query/atom.js.map +1 -0
  51. package/dist/query/lower.d.ts +145 -0
  52. package/dist/query/lower.d.ts.map +1 -0
  53. package/dist/query/lower.js +604 -0
  54. package/dist/query/lower.js.map +1 -0
  55. package/dist/query/predicate.d.ts +101 -0
  56. package/dist/query/predicate.d.ts.map +1 -0
  57. package/dist/query/predicate.js +85 -0
  58. package/dist/query/predicate.js.map +1 -0
  59. package/dist/query/run.d.ts +30 -0
  60. package/dist/query/run.d.ts.map +1 -0
  61. package/dist/query/run.js +94 -0
  62. package/dist/query/run.js.map +1 -0
  63. package/dist/query/scope.d.ts +168 -0
  64. package/dist/query/scope.d.ts.map +1 -0
  65. package/dist/query/scope.js +134 -0
  66. package/dist/query/scope.js.map +1 -0
  67. package/dist/query/select.d.ts +106 -0
  68. package/dist/query/select.d.ts.map +1 -0
  69. package/dist/query/select.js +69 -0
  70. package/dist/query/select.js.map +1 -0
  71. package/dist/relation.d.ts +120 -0
  72. package/dist/relation.d.ts.map +1 -0
  73. package/dist/relation.js +108 -0
  74. package/dist/relation.js.map +1 -0
  75. package/dist/schema.d.ts +50 -0
  76. package/dist/schema.d.ts.map +1 -0
  77. package/dist/schema.js +235 -0
  78. package/dist/schema.js.map +1 -0
  79. package/dist/spec.d.ts +218 -0
  80. package/dist/spec.d.ts.map +1 -0
  81. package/dist/spec.js +154 -0
  82. package/dist/spec.js.map +1 -0
  83. package/dist/statements.d.ts +91 -0
  84. package/dist/statements.d.ts.map +1 -0
  85. package/dist/statements.js +101 -0
  86. package/dist/statements.js.map +1 -0
  87. package/package.json +66 -0
  88. package/src/brand.ts +82 -0
  89. package/src/closed.ts +228 -0
  90. package/src/count.ts +117 -0
  91. package/src/db.ts +1519 -0
  92. package/src/exhume.ts +243 -0
  93. package/src/face.ts +161 -0
  94. package/src/fields.ts +385 -0
  95. package/src/index.ts +185 -0
  96. package/src/lower.ts +118 -0
  97. package/src/marshal.ts +220 -0
  98. package/src/native.ts +576 -0
  99. package/src/query/atom.ts +556 -0
  100. package/src/query/lower.ts +855 -0
  101. package/src/query/predicate.ts +195 -0
  102. package/src/query/run.ts +106 -0
  103. package/src/query/scope.ts +301 -0
  104. package/src/query/select.ts +140 -0
  105. package/src/relation.ts +252 -0
  106. package/src/schema.ts +297 -0
  107. package/src/spec.ts +325 -0
  108. package/src/statements.ts +148 -0
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Dependency statements as typed values (`docs/architecture/30-dependencies.md`
3
+ * owns the semantics; `docs/architecture/70-api.md` the surface): the FD key
4
+ * form, conditional containment, the bidirectional `==` abbreviation, and
5
+ * the cardinality window. A statement value is opaque and inert — no
6
+ * methods, no fluent continuation: a fact about the theory, not a builder.
7
+ */
8
+ import { phantom } from "#brand.ts";
9
+ import type { Count } from "#count.ts";
10
+ import { type AnyFace, type FaceData, type SameArity } from "#face.ts";
11
+ import type { AnyRelation, RelationFields } from "#relation.ts";
12
+ import { type WindowSpec } from "#spec.ts";
13
+ /** One statement's runtime description, tagged by form. */
14
+ type StatementData = {
15
+ readonly kind: "key";
16
+ readonly owner: AnyRelation;
17
+ readonly projection: readonly string[];
18
+ } | {
19
+ readonly kind: "containment";
20
+ readonly source: FaceData;
21
+ readonly target: FaceData;
22
+ readonly bidirectional: boolean;
23
+ } | {
24
+ readonly kind: "window";
25
+ readonly target: FaceData;
26
+ readonly window: WindowSpec;
27
+ readonly source: FaceData;
28
+ };
29
+ /** An opaque statement value — what `schema()` assembles into a theory. */
30
+ interface Statement {
31
+ readonly data: StatementData;
32
+ }
33
+ /**
34
+ * A `key()` statement as a TYPED value: the statement plus a phantom
35
+ * carrying its owner and projection tuple — what the key-statement-selected
36
+ * `get(relation, keyStatement, key)` overload types its key object by
37
+ * (`docs/architecture/70-api.md` § the freeze, the multi-key typed get).
38
+ * Structurally still a plain {@link Statement}; the phantom is never present
39
+ * at runtime.
40
+ */
41
+ interface KeyStatement<R extends AnyRelation, Projection extends readonly string[]> extends Statement {
42
+ readonly [phantom]?: {
43
+ readonly owner: R;
44
+ readonly projection: Projection;
45
+ };
46
+ }
47
+ /**
48
+ * `R(X) -> R` — the FD key form, composite keys as tuples. No selection
49
+ * parameter exists (the FD-with-selection shape is unrepresentable, as in
50
+ * the grammar), and only ordinary relations are accepted: a closed
51
+ * relation's key `R(id) -> R` is materialized by the engine, so an
52
+ * explicit one would only ever be a duplicate. The projection tuple is
53
+ * carried in the returned value's type ({@link KeyStatement}), so keyed
54
+ * point reads through THIS statement are typed field-for-field.
55
+ */
56
+ declare function key<R extends AnyRelation, const Projection extends readonly [keyof RelationFields<R> & string, ...(keyof RelationFields<R> & string)[]]>(relation: R, fields: Projection): KeyStatement<R, Projection>;
57
+ /**
58
+ * `A(X|φ) <= B(Y|ψ)` — conditional inclusion, source left. The target
59
+ * side must resolve a declared key of B — DELIBERATELY judged by the
60
+ * engine at `Db.create`/`Db.open` (`SchemaError`), never re-checked here.
61
+ * Arity mismatch between the two faces is a type error ({@link SameArity}).
62
+ */
63
+ declare function contained<A extends AnyFace, B extends AnyFace>(source: A, target: B & SameArity<A, B>): Statement;
64
+ /**
65
+ * `A(X|φ) == B(Y|ψ)` — the bidirectional abbreviation, one utterance. It
66
+ * lowers to the two adjacent containments in the `A <= B` first order
67
+ * (macro parity) and renders as `==` once, in the written orientation.
68
+ */
69
+ declare function mirrors<A extends AnyFace, B extends AnyFace>(source: A, target: B & SameArity<A, B>): Statement;
70
+ /**
71
+ * `B(Y|ψ) <={window} A(X|φ)` — the cardinality window. READ CAREFULLY: the
72
+ * LEFT face is the window's TARGET, the per-group parent (B-family,
73
+ * target-left — macro parity), and the RIGHT face is the counted source.
74
+ * `window(on(Holder, "id"), atMost(3n), on(Account, "holder"))` says: each
75
+ * Holder id groups at most three Account rows by holder.
76
+ */
77
+ declare function window<B extends AnyFace, A extends AnyFace>(target: B, count: Count, source: A & SameArity<B, A>): Statement;
78
+ /**
79
+ * Renders one statement in the CANONICAL macro spelling
80
+ * (`docs/architecture/70-api.md`; the engine's `schema/render.rs` emits the
81
+ * same shapes for violations) — `Account(id) -> Account`,
82
+ * `Account(holder) <= Holder(id)`,
83
+ * `Account(id | kind == Savings) == SavingsTerms(account)`,
84
+ * `Holder(id) <={0..3} Account(holder)` — so TS-side errors and
85
+ * engine-side diagnostics read identically. A renderer, never a parser:
86
+ * strings are output-only.
87
+ */
88
+ declare function renderStatement(statement: Statement): string;
89
+ export type { KeyStatement, Statement, StatementData };
90
+ export { contained, key, mirrors, renderStatement, window };
91
+ //# sourceMappingURL=statements.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statements.d.ts","sourceRoot":"","sources":["../src/statements.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Dependency statements as typed values (`docs/architecture/30-dependencies.md`
3
+ * owns the semantics; `docs/architecture/70-api.md` the surface): the FD key
4
+ * form, conditional containment, the bidirectional `==` abbreviation, and
5
+ * the cardinality window. A statement value is opaque and inert — no
6
+ * methods, no fluent continuation: a fact about the theory, not a builder.
7
+ */
8
+ import * as errors from "@superbuilders/errors";
9
+ import { phantom } from "#brand.ts";
10
+ import { renderFace } from "#face.ts";
11
+ import { renderWindow } from "#spec.ts";
12
+ /**
13
+ * `R(X) -> R` — the FD key form, composite keys as tuples. No selection
14
+ * parameter exists (the FD-with-selection shape is unrepresentable, as in
15
+ * the grammar), and only ordinary relations are accepted: a closed
16
+ * relation's key `R(id) -> R` is materialized by the engine, so an
17
+ * explicit one would only ever be a duplicate. The projection tuple is
18
+ * carried in the returned value's type ({@link KeyStatement}), so keyed
19
+ * point reads through THIS statement are typed field-for-field.
20
+ */
21
+ function key(relation, fields) {
22
+ if (!("fields" in relation.data)) {
23
+ throw errors.new(`key(${relation.name}, ...): closedness already materializes ${relation.name}(id) -> ${relation.name} — an explicit key on a closed relation is rejected as a duplicate`);
24
+ }
25
+ const data = Object.freeze({
26
+ kind: "key",
27
+ owner: relation,
28
+ projection: Object.freeze([...fields])
29
+ });
30
+ return Object.freeze({ data });
31
+ }
32
+ /**
33
+ * `A(X|φ) <= B(Y|ψ)` — conditional inclusion, source left. The target
34
+ * side must resolve a declared key of B — DELIBERATELY judged by the
35
+ * engine at `Db.create`/`Db.open` (`SchemaError`), never re-checked here.
36
+ * Arity mismatch between the two faces is a type error ({@link SameArity}).
37
+ */
38
+ function contained(source, target) {
39
+ const data = Object.freeze({
40
+ kind: "containment",
41
+ source: source.data,
42
+ target: target.data,
43
+ bidirectional: false
44
+ });
45
+ return Object.freeze({ data });
46
+ }
47
+ /**
48
+ * `A(X|φ) == B(Y|ψ)` — the bidirectional abbreviation, one utterance. It
49
+ * lowers to the two adjacent containments in the `A <= B` first order
50
+ * (macro parity) and renders as `==` once, in the written orientation.
51
+ */
52
+ function mirrors(source, target) {
53
+ const data = Object.freeze({
54
+ kind: "containment",
55
+ source: source.data,
56
+ target: target.data,
57
+ bidirectional: true
58
+ });
59
+ return Object.freeze({ data });
60
+ }
61
+ /**
62
+ * `B(Y|ψ) <={window} A(X|φ)` — the cardinality window. READ CAREFULLY: the
63
+ * LEFT face is the window's TARGET, the per-group parent (B-family,
64
+ * target-left — macro parity), and the RIGHT face is the counted source.
65
+ * `window(on(Holder, "id"), atMost(3n), on(Account, "holder"))` says: each
66
+ * Holder id groups at most three Account rows by holder.
67
+ */
68
+ function window(target, count, source) {
69
+ const data = Object.freeze({
70
+ kind: "window",
71
+ target: target.data,
72
+ window: count.window,
73
+ source: source.data
74
+ });
75
+ return Object.freeze({ data });
76
+ }
77
+ /**
78
+ * Renders one statement in the CANONICAL macro spelling
79
+ * (`docs/architecture/70-api.md`; the engine's `schema/render.rs` emits the
80
+ * same shapes for violations) — `Account(id) -> Account`,
81
+ * `Account(holder) <= Holder(id)`,
82
+ * `Account(id | kind == Savings) == SavingsTerms(account)`,
83
+ * `Holder(id) <={0..3} Account(holder)` — so TS-side errors and
84
+ * engine-side diagnostics read identically. A renderer, never a parser:
85
+ * strings are output-only.
86
+ */
87
+ function renderStatement(statement) {
88
+ const data = statement.data;
89
+ switch (data.kind) {
90
+ case "key":
91
+ return `${data.owner.name}(${data.projection.join(", ")}) -> ${data.owner.name}`;
92
+ case "containment": {
93
+ const operator = data.bidirectional ? "==" : "<=";
94
+ return `${renderFace(data.source)} ${operator} ${renderFace(data.target)}`;
95
+ }
96
+ case "window":
97
+ return `${renderFace(data.target)} <=${renderWindow(data.window)} ${renderFace(data.source)}`;
98
+ }
99
+ }
100
+ export { contained, key, mirrors, renderStatement, window };
101
+ //# sourceMappingURL=statements.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statements.js","sourceRoot":"","sources":["../src/statements.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@bjornpagen/bumbledb",
3
+ "version": "0.1.0",
4
+ "description": "Type-theoretic TypeScript SDK for the bumbledb embedded relational engine",
5
+ "module": "dist/index.js",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "type": "module",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "imports": {
16
+ "#test/*.ts": "./test/*.ts",
17
+ "#*.ts": {
18
+ "types": "./src/*.ts",
19
+ "default": "./dist/*.js"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "src"
25
+ ],
26
+ "keywords": [
27
+ "database",
28
+ "relational",
29
+ "datalog",
30
+ "embedded",
31
+ "type-safe",
32
+ "typescript"
33
+ ],
34
+ "author": "Bjorn Pagen",
35
+ "license": "0BSD",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/bjornpagen/bumbledb.git",
39
+ "directory": "ts"
40
+ },
41
+ "homepage": "https://github.com/bjornpagen/bumbledb#readme",
42
+ "dependencies": {
43
+ "@superbuilders/errors": "^4.0.2"
44
+ },
45
+ "optionalDependencies": {
46
+ "@bjornpagen/bumbledb-darwin-arm64": "0.1.0"
47
+ },
48
+ "devDependencies": {
49
+ "@biomejs/biome": "^2.0.0-beta.5",
50
+ "@types/node": "^26.1.0",
51
+ "typescript": "^7.0.2"
52
+ },
53
+ "engines": {
54
+ "node": ">=24"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
59
+ "private": false,
60
+ "scripts": {
61
+ "build": "node scripts/build.ts",
62
+ "typecheck": "tsc --noEmit",
63
+ "lint": "biome check .",
64
+ "test": "pnpm run build && node --test test/**/*.test.ts"
65
+ }
66
+ }
package/src/brand.ts ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Nominal branding — the TS analog of the Rust macro's host newtypes
3
+ * (`docs/architecture/10-data-model.md`, the nominal-safety layer). A brand
4
+ * is a phantom: it exists only in the type, tsc polices the wall exactly as
5
+ * rustc polices newtype domains, and nothing is allocated or wrapped at
6
+ * runtime.
7
+ */
8
+
9
+ import * as errors from "@superbuilders/errors"
10
+
11
+ /**
12
+ * The brand-minting guard behind {@link span} — the one nominal step, as a
13
+ * type guard carrying the interval's REAL invariant (`start < end`, the
14
+ * same check Rust's `Interval::new` runs): a value that passes IS a legal
15
+ * interval of any brand, exactly as a Rust newtype wraps a checked
16
+ * `Interval<T>` at construction.
17
+ */
18
+ function isNonemptyInterval<Name extends string>(value: IntervalValue): value is Interval<Name> {
19
+ return value.start < value.end
20
+ }
21
+
22
+ /**
23
+ * The brand key. A real runtime symbol (so modules can import it without a
24
+ * `declare`-only lie), but no branded value ever carries the property — the
25
+ * brand is purely a typing device.
26
+ */
27
+ const brand: unique symbol = Symbol("bumbledb.brand")
28
+
29
+ /**
30
+ * The phantom-value key used by field values, field references, and faces
31
+ * to carry their value type without any runtime representation.
32
+ */
33
+ const phantom: unique symbol = Symbol("bumbledb.phantom")
34
+
35
+ /**
36
+ * A branded scalar: `T` walled off under the literal name `Name`. A
37
+ * `Brand<bigint, "HolderId">` is not assignable where a
38
+ * `Brand<bigint, "AccountId">` is expected — the Rust newtype wall,
39
+ * verbatim. Scalars brand as `bigint` (u64/i64), `Uint8Array` (bytes), and
40
+ * whole interval objects (`Interval<Name>`); `bool` and `str` take no
41
+ * newtype, exactly as the macro's `as` grammar refuses them.
42
+ */
43
+ type Brand<T, Name extends string> = T & { readonly [brand]: Name }
44
+
45
+ /**
46
+ * A half-open interval `[start, end)` as a plain value object. The ray is
47
+ * representable (`end` = the element domain's MAX_END); widths and
48
+ * signedness are NOT modeled here — the engine judges widths at the typed
49
+ * write boundary, the brand blocks cross-field assignment, and nothing
50
+ * else is TS's business. Interval newtypes derive no order (the Rust
51
+ * refusal, `docs/architecture/10-data-model.md`), so no comparators exist.
52
+ */
53
+ interface IntervalValue {
54
+ readonly start: bigint
55
+ readonly end: bigint
56
+ }
57
+
58
+ /**
59
+ * A branded interval: the whole `{ start, end }` object walled under
60
+ * `Name` — the `interval<i64> as ActiveDuring` analog.
61
+ */
62
+ type Interval<Name extends string> = Brand<IntervalValue, Name>
63
+
64
+ /**
65
+ * Constructs an interval literal — the `start..end` spelling. Half-open
66
+ * and nonempty by construction: `start >= end` is a typed construction
67
+ * error (parse, don't validate — the same invariant Rust's
68
+ * `Interval::new` enforces at the host boundary). The default `never`
69
+ * brand makes a fresh literal assignable to any interval field or brand,
70
+ * the wrap-at-construction idiom; pass the brand explicitly
71
+ * (`span<"ActiveDuring">(0n, 10n)`) to pin it.
72
+ */
73
+ function span<Name extends string = never>(start: bigint, end: bigint): Interval<Name> {
74
+ const value: IntervalValue = Object.freeze({ start, end })
75
+ if (!isNonemptyInterval<Name>(value)) {
76
+ throw errors.new(`interval is half-open and nonempty: start must be < end (got ${start}..${end})`)
77
+ }
78
+ return value
79
+ }
80
+
81
+ export type { Brand, Interval, IntervalValue }
82
+ export { brand, phantom, span }
package/src/closed.ts ADDED
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Closed relations (`docs/architecture/10-data-model.md` § closed
3
+ * relations): a vocabulary whose extension is declared in the schema — two
4
+ * tiers, one function. The emission per closed relation mirrors the macro's
5
+ * (host-enum analog): handle CONSTANTS on the value (`Kind.Checking`, ids =
6
+ * declaration order), the `fromId` weld, an `id` field constructor
7
+ * pre-branded with the handle newtype for other relations' field blocks
8
+ * (`kind: Kind.id`), and payload readback (`Kind.axioms`). No fact type and
9
+ * no insert surface exist — closed relations are unwritable by
10
+ * construction: the value simply lacks the writable relation shape.
11
+ */
12
+
13
+ import * as errors from "@superbuilders/errors"
14
+ import type { Brand } from "#brand.ts"
15
+ import {
16
+ type AnyField,
17
+ assertDeclarationOrderKey,
18
+ type ClosedIdField,
19
+ type ClosedRoster,
20
+ type FieldData,
21
+ type FieldValue,
22
+ fieldData,
23
+ literalOf
24
+ } from "#fields.ts"
25
+ import type { LiteralSpec } from "#spec.ts"
26
+
27
+ /**
28
+ * The value-surface property names a handle may not shadow — the macro's
29
+ * name-collision diagnostic, here over the closed value's own properties
30
+ * (`relation`/`selection` are reserved so a closed value can never be
31
+ * mistaken for a selected relation by `on()`'s discriminant).
32
+ */
33
+ const reservedHandleNames: readonly string[] = Object.freeze([
34
+ "name",
35
+ "id",
36
+ "data",
37
+ "axioms",
38
+ "fromId",
39
+ "relation",
40
+ "selection"
41
+ ])
42
+
43
+ /**
44
+ * Reads one handle's axiom row, refusing absence loudly: the payload tier's
45
+ * overload types the record exhaustively, so a missing row is ill-typed
46
+ * input, and the bare tier never reaches here (it declares no columns).
47
+ */
48
+ function axiomRow(
49
+ name: string,
50
+ axioms: Readonly<Record<string, Readonly<Record<string, unknown>>>> | undefined,
51
+ handle: string
52
+ ): Readonly<Record<string, unknown>> {
53
+ if (axioms === undefined) {
54
+ throw errors.new(`closed relation ${name}: payload columns declared without ground axioms`)
55
+ }
56
+ const row = axioms[handle]
57
+ if (row === undefined) {
58
+ throw errors.new(`closed relation ${name}: no ground axiom for handle ${handle}`)
59
+ }
60
+ return row
61
+ }
62
+
63
+ /**
64
+ * A payload column of a closed relation: any field constructor except a
65
+ * fresh-marked one (a vocabulary's rows are ground axioms, never minted).
66
+ */
67
+ type PayloadField = AnyField & { readonly data: FieldData<false> }
68
+
69
+ /** One declared payload column: name plus its field description. */
70
+ interface ClosedColumn {
71
+ readonly name: string
72
+ readonly field: FieldData
73
+ }
74
+
75
+ /**
76
+ * One ground axiom, already lowered: the handle plus one wire literal per
77
+ * declared column in column-declaration order (row id = index). Lowered
78
+ * EAGERLY at construction so axiom literals ride the same selection-literal
79
+ * machine as `where()` bindings, with the same errors (the macro's rule).
80
+ */
81
+ interface ClosedRow {
82
+ readonly handle: string
83
+ readonly values: readonly LiteralSpec[]
84
+ }
85
+
86
+ /** A closed relation's runtime description. */
87
+ interface ClosedData {
88
+ readonly name: string
89
+ readonly handles: readonly string[]
90
+ readonly columns: readonly ClosedColumn[]
91
+ readonly rows: readonly ClosedRow[]
92
+ }
93
+
94
+ /** One axiom row as the host writes and reads it: column name to host value. */
95
+ type AxiomRow<Cols> = { readonly [C in keyof Cols]: FieldValue<Cols[C]> }
96
+
97
+ /**
98
+ * The whole axiom record: every handle exactly once, every column exactly
99
+ * once per row — a missing or extra axiom, column, or handle is a TYPE
100
+ * error (mapped over the handle tuple).
101
+ */
102
+ type Axioms<Handles extends readonly string[], Cols> = {
103
+ readonly [H in Handles[number]]: AxiomRow<Cols>
104
+ }
105
+
106
+ /**
107
+ * The named surface of a closed relation value, minus the handle constants
108
+ * (which {@link Closed} intersects in).
109
+ */
110
+ interface ClosedCore<Name extends string, Handles extends readonly string[], Cols> {
111
+ readonly name: Name
112
+ /**
113
+ * The pre-branded u64 field constructor: `kind: Kind.id` in another
114
+ * relation's field block is the reference through which bare handles
115
+ * become legal in that relation's selections.
116
+ */
117
+ readonly id: ClosedIdField<Name>
118
+ readonly data: ClosedData
119
+ /** Payload readback: handle to its declared column values. */
120
+ readonly axioms: Axioms<Handles, Cols>
121
+ /** The weld: declaration-order id back to its handle, or undefined beyond the roster. */
122
+ fromId(id: Brand<bigint, Name>): Handles[number] | undefined
123
+ }
124
+
125
+ /**
126
+ * A closed relation value: the core surface plus one branded constant per
127
+ * handle (`Kind.Checking: Brand<bigint, "Kind">`, ids = declaration order).
128
+ */
129
+ type Closed<Name extends string, Handles extends readonly string[], Cols> = ClosedCore<Name, Handles, Cols> & {
130
+ readonly [H in Handles[number]]: Brand<bigint, Name>
131
+ }
132
+
133
+ /** Any closed relation value, whatever its roster and columns. */
134
+ interface AnyClosed {
135
+ readonly name: string
136
+ readonly id: ClosedIdField<string>
137
+ readonly data: ClosedData
138
+ readonly axioms: Readonly<Record<string, object>>
139
+ }
140
+
141
+ /** Bare tier: `closed("Kind", ["Checking", "Savings"])` — handles only. */
142
+ function closed<const Name extends string, const Handles extends readonly [string, ...string[]]>(
143
+ name: Name,
144
+ handles: Handles
145
+ ): Closed<Name, Handles, Record<never, never>>
146
+
147
+ /**
148
+ * Payload tier: declared columns plus ground axioms, every handle with
149
+ * every column exactly once (type-enforced by {@link Axioms}).
150
+ */
151
+ function closed<
152
+ const Name extends string,
153
+ const Handles extends readonly [string, ...string[]],
154
+ const Cols extends Record<string, PayloadField>
155
+ >(name: Name, handles: Handles, columns: Cols, axioms: Axioms<Handles, Cols>): Closed<Name, Handles, Cols>
156
+
157
+ function closed(
158
+ name: string,
159
+ handles: readonly [string, ...string[]],
160
+ columns?: Record<string, PayloadField>,
161
+ axioms?: Readonly<Record<string, Readonly<Record<string, unknown>>>>
162
+ ): unknown {
163
+ const seen = new Set<string>()
164
+ for (const handle of handles) {
165
+ if (seen.has(handle)) {
166
+ throw errors.new(`closed relation ${name}: duplicate handle ${handle}`)
167
+ }
168
+ seen.add(handle)
169
+ if (reservedHandleNames.includes(handle)) {
170
+ throw errors.new(
171
+ `closed relation ${name}: handle ${handle} collides with the closed value's own surface (${reservedHandleNames.join(", ")})`
172
+ )
173
+ }
174
+ }
175
+ const roster: ClosedRoster = Object.freeze({ name, handles: Object.freeze([...handles]) })
176
+ const cols: ClosedColumn[] = []
177
+ if (columns !== undefined) {
178
+ for (const [columnName, field] of Object.entries(columns)) {
179
+ assertDeclarationOrderKey(`closed relation ${name} column`, columnName)
180
+ cols.push(Object.freeze({ name: columnName, field: field.data }))
181
+ }
182
+ }
183
+ const rows: ClosedRow[] = handles.map(function lowerRow(handle) {
184
+ const values = cols.map(function lowerAxiomLiteral(column) {
185
+ const row = axiomRow(name, axioms, handle)
186
+ return Object.freeze(literalOf(column.field, row[column.name]))
187
+ })
188
+ return Object.freeze({ handle, values: Object.freeze(values) })
189
+ })
190
+ const data: ClosedData = Object.freeze({
191
+ name,
192
+ handles: roster.handles,
193
+ columns: Object.freeze(cols),
194
+ rows: Object.freeze(rows)
195
+ })
196
+ const id: ClosedIdField<string> = Object.freeze({
197
+ data: fieldData({ kind: "u64" }, name, false, roster)
198
+ })
199
+ /**
200
+ * Handle names are arbitrary identifiers, so rows and constants are
201
+ * minted with OWN-property definition, never assignment: a handle named
202
+ * "__proto__" would otherwise ride the Object.prototype accessor —
203
+ * silently swapping the record's prototype instead of creating the row,
204
+ * and no-oping the constant (a primitive through the setter) — minting a
205
+ * value whose type claims Brand<bigint, Name> but reads back an object.
206
+ */
207
+ const axiomsOut: Record<string, object> = {}
208
+ for (const handle of handles) {
209
+ const row = axioms === undefined ? Object.freeze({}) : Object.freeze({ ...axiomRow(name, axioms, handle) })
210
+ Object.defineProperty(axiomsOut, handle, { value: row, enumerable: true })
211
+ }
212
+ const value: Record<string, unknown> = {
213
+ name,
214
+ id,
215
+ data,
216
+ axioms: Object.freeze(axiomsOut),
217
+ fromId(idValue: bigint): string | undefined {
218
+ return roster.handles[Number(idValue)]
219
+ }
220
+ }
221
+ handles.forEach(function mintHandleConstant(handle, index) {
222
+ Object.defineProperty(value, handle, { value: BigInt(index), enumerable: true })
223
+ })
224
+ return Object.freeze(value)
225
+ }
226
+
227
+ export type { AnyClosed, AxiomRow, Axioms, Closed, ClosedColumn, ClosedCore, ClosedData, ClosedRow, PayloadField }
228
+ export { closed }