@bjornpagen/bumbledb 0.4.0 → 0.5.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.
- package/COOKBOOK.md +181 -47
- package/README.md +4 -2
- package/dist/closed.d.ts +30 -2
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +66 -20
- package/dist/closed.js.map +1 -1
- package/dist/db.d.ts +33 -6
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +87 -70
- package/dist/db.js.map +1 -1
- package/dist/exhume.d.ts.map +1 -1
- package/dist/exhume.js +1 -14
- package/dist/exhume.js.map +1 -1
- package/dist/face.d.ts +1 -1
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +2 -1
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +18 -1
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +44 -16
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -4
- package/dist/index.js.map +1 -1
- package/dist/law.d.ts +2 -1
- package/dist/law.d.ts.map +1 -1
- package/dist/law.js +15 -14
- package/dist/law.js.map +1 -1
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +1 -7
- package/dist/lower.js.map +1 -1
- package/dist/marshal.d.ts.map +1 -1
- package/dist/marshal.js +15 -27
- package/dist/marshal.js.map +1 -1
- package/dist/native.d.ts +21 -2
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +20 -3
- package/dist/native.js.map +1 -1
- package/dist/order.d.ts +36 -0
- package/dist/order.d.ts.map +1 -0
- package/dist/order.js +135 -0
- package/dist/order.js.map +1 -0
- package/dist/query/atom.d.ts +18 -15
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +9 -16
- package/dist/query/atom.js.map +1 -1
- package/dist/query/lower.d.ts +5 -8
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +87 -76
- package/dist/query/lower.js.map +1 -1
- package/dist/query/predicate.d.ts.map +1 -1
- package/dist/query/predicate.js +34 -2
- package/dist/query/predicate.js.map +1 -1
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +1 -2
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +2 -16
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +7 -47
- package/dist/query/scope.js.map +1 -1
- package/dist/relation.d.ts +9 -22
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +9 -28
- package/dist/relation.js.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +7 -31
- package/dist/schema.js.map +1 -1
- package/dist/statements.d.ts +5 -2
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +30 -35
- package/dist/statements.js.map +1 -1
- package/package.json +2 -5
- package/src/closed.ts +73 -28
- package/src/db.ts +122 -84
- package/src/exhume.ts +1 -15
- package/src/face.ts +4 -3
- package/src/fields.ts +58 -16
- package/src/index.ts +10 -8
- package/src/law.ts +15 -14
- package/src/lower.ts +2 -9
- package/src/marshal.ts +15 -31
- package/src/native.ts +22 -4
- package/src/order.ts +156 -0
- package/src/query/atom.ts +13 -21
- package/src/query/lower.ts +90 -91
- package/src/query/predicate.ts +39 -4
- package/src/query/run.ts +1 -3
- package/src/query/scope.ts +7 -59
- package/src/relation.ts +9 -51
- package/src/schema.ts +7 -33
- package/src/statements.ts +33 -38
package/dist/statements.js
CHANGED
|
@@ -12,8 +12,11 @@
|
|
|
12
12
|
* schema type (the minimal kernel — descriptors are pure structure) and
|
|
13
13
|
* constrained positionwise equal, so a u64 face against a str face, a
|
|
14
14
|
* bytes width mismatch, an interval element mismatch, or a bare column
|
|
15
|
-
* against a closed reference is a compile error.
|
|
16
|
-
* construction-time runtime
|
|
15
|
+
* against a closed reference is a compile error. TWO of those walls carry
|
|
16
|
+
* construction-time runtime twins here for untyped callers: ARITY
|
|
17
|
+
* ({@link assertArityAgreement} — cleanup-0.5.0 ruling 9: an
|
|
18
|
+
* arity-mismatched pairing fails at the statement, never by silent
|
|
19
|
+
* truncation) and the ROSTER slot ({@link assertRosterAgreement} —
|
|
17
20
|
* roster IDENTITY, positionwise: a closed vocabulary's referencing column
|
|
18
21
|
* is spelled with the vocabulary's own id descriptor, the ONE spelling, so
|
|
19
22
|
* a plain u64 column can never alias a vocabulary through a declared law
|
|
@@ -31,42 +34,28 @@
|
|
|
31
34
|
* authority).
|
|
32
35
|
*/
|
|
33
36
|
import * as errors from "@superbuilders/errors";
|
|
37
|
+
import { isClosedMember, sealedFieldOf } from "#closed.ts";
|
|
34
38
|
import { renderFace } from "#face.ts";
|
|
39
|
+
import { rosterOf } from "#fields.ts";
|
|
35
40
|
import { renderWindow } from "#spec.ts";
|
|
36
|
-
/**
|
|
37
|
-
* The field descriptor one face position projects: an ordinary relation's
|
|
38
|
-
* declared field; a closed relation's SEALED shape — the synthetic `id`
|
|
39
|
-
* (the value's own roster-carrying descriptor, by identity) or a declared
|
|
40
|
-
* payload column. `undefined` when the name is foreign (the type tier makes
|
|
41
|
-
* that unwritable; the engine re-judges projections at `Db.create`).
|
|
42
|
-
*/
|
|
43
|
-
function projectedFieldOf(face, fieldName) {
|
|
44
|
-
const owner = face.owner;
|
|
45
|
-
if ("axioms" in owner) {
|
|
46
|
-
if (fieldName === "id") {
|
|
47
|
-
return owner.id;
|
|
48
|
-
}
|
|
49
|
-
const column = owner.data.columns.find(function byName(candidate) {
|
|
50
|
-
return candidate.name === fieldName;
|
|
51
|
-
});
|
|
52
|
-
return column?.field;
|
|
53
|
-
}
|
|
54
|
-
const declared = owner.data.fields.find(function byName(candidate) {
|
|
55
|
-
return candidate.name === fieldName;
|
|
56
|
-
});
|
|
57
|
-
return declared?.field;
|
|
58
|
-
}
|
|
59
|
-
/** The roster a descriptor carries: present exactly on a closed reference, absent on every other kind. */
|
|
60
|
-
function rosterOfField(field) {
|
|
61
|
-
if (field !== undefined && "closed" in field) {
|
|
62
|
-
return field.closed;
|
|
63
|
-
}
|
|
64
|
-
return undefined;
|
|
65
|
-
}
|
|
66
41
|
/** Renders one face position's closedness for the roster-agreement diagnostics. */
|
|
67
42
|
function renderRosterSide(roster) {
|
|
68
43
|
return roster === undefined ? "a bare column" : `a ${roster.name} reference`;
|
|
69
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* The runtime twin of {@link SameArity} (cleanup-0.5.0 ruling 9): the two
|
|
47
|
+
* faces must project equally many fields, judged at CONSTRUCTION for
|
|
48
|
+
* untyped callers too — without it an arity-mismatched containment
|
|
49
|
+
* silently truncates to the shorter projection (this module's positionwise
|
|
50
|
+
* walk and `law.ts`'s `unionSlot` both skip unpaired positions) until
|
|
51
|
+
* `Db.create`'s colder engine refusal. The error carries the two faces'
|
|
52
|
+
* own facts: names, arities, and the rendered statement.
|
|
53
|
+
*/
|
|
54
|
+
function assertArityAgreement(source, target, statement) {
|
|
55
|
+
if (source.projection.length !== target.projection.length) {
|
|
56
|
+
throw errors.new(`${source.owner.name}(${source.projection.join(", ")}) and ${target.owner.name}(${target.projection.join(", ")}) project ${source.projection.length} vs ${target.projection.length} fields — positional pairing requires both faces to project equally many — ${renderStatement(statement)}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
70
59
|
/**
|
|
71
60
|
* The runtime twin of {@link SameShapes}'s roster slot: the two faces'
|
|
72
61
|
* projected descriptors must agree POSITIONWISE on closedness — the same
|
|
@@ -79,6 +68,9 @@ function renderRosterSide(roster) {
|
|
|
79
68
|
* vocabulary's own descriptor (`Kind.id`) is the ONE spelling of a closed
|
|
80
69
|
* reference at this surface (the canonical-utterance law); the engine
|
|
81
70
|
* cannot backstop this one — the wire carries plain u64s, no rosters.
|
|
71
|
+
* Arity agreement ({@link assertArityAgreement}) runs first, so the
|
|
72
|
+
* positionwise walk here never sees an unpaired position from a well-typed
|
|
73
|
+
* OR an untyped caller.
|
|
82
74
|
*/
|
|
83
75
|
function assertRosterAgreement(source, target, statement) {
|
|
84
76
|
source.projection.forEach(function agreeAt(fieldName, position) {
|
|
@@ -86,8 +78,8 @@ function assertRosterAgreement(source, target, statement) {
|
|
|
86
78
|
if (targetField === undefined) {
|
|
87
79
|
return;
|
|
88
80
|
}
|
|
89
|
-
const sourceRoster =
|
|
90
|
-
const targetRoster =
|
|
81
|
+
const sourceRoster = rosterOf(sealedFieldOf(source.owner, fieldName));
|
|
82
|
+
const targetRoster = rosterOf(sealedFieldOf(target.owner, targetField));
|
|
91
83
|
if (sourceRoster !== targetRoster) {
|
|
92
84
|
throw errors.new(`${source.owner.name}.${fieldName} is ${renderRosterSide(sourceRoster)} but ${target.owner.name}.${targetField} is ${renderRosterSide(targetRoster)} — closedness rides the descriptor: a closed reference is spelled with the vocabulary's own id descriptor (one meaning, one spelling), so faces pair closed-with-closed through one roster or bare-with-bare, never across — ${renderStatement(statement)}`);
|
|
93
85
|
}
|
|
@@ -105,7 +97,7 @@ function assertRosterAgreement(source, target, statement) {
|
|
|
105
97
|
* through the owner's schema type.
|
|
106
98
|
*/
|
|
107
99
|
function key(relation, fields) {
|
|
108
|
-
if (
|
|
100
|
+
if (isClosedMember(relation)) {
|
|
109
101
|
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`);
|
|
110
102
|
}
|
|
111
103
|
const data = Object.freeze({
|
|
@@ -132,6 +124,7 @@ function contained(source, target) {
|
|
|
132
124
|
bidirectional: false
|
|
133
125
|
});
|
|
134
126
|
const statement = Object.freeze({ data });
|
|
127
|
+
assertArityAgreement(data.source, data.target, statement);
|
|
135
128
|
assertRosterAgreement(data.source, data.target, statement);
|
|
136
129
|
return statement;
|
|
137
130
|
}
|
|
@@ -152,6 +145,7 @@ function mirrors(source, target) {
|
|
|
152
145
|
bidirectional: true
|
|
153
146
|
});
|
|
154
147
|
const statement = Object.freeze({ data });
|
|
148
|
+
assertArityAgreement(data.source, data.target, statement);
|
|
155
149
|
assertRosterAgreement(data.source, data.target, statement);
|
|
156
150
|
return statement;
|
|
157
151
|
}
|
|
@@ -173,6 +167,7 @@ function window(target, count, source) {
|
|
|
173
167
|
source: source.data
|
|
174
168
|
});
|
|
175
169
|
const statement = Object.freeze({ data });
|
|
170
|
+
assertArityAgreement(data.source, data.target, statement);
|
|
176
171
|
assertRosterAgreement(data.source, data.target, statement);
|
|
177
172
|
return statement;
|
|
178
173
|
}
|
package/dist/statements.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statements.js","sourceRoot":"","sources":["../src/statements.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"statements.js","sourceRoot":"","sources":["../src/statements.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bjornpagen/bumbledb",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
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
5
|
"type": "module",
|
|
9
6
|
"exports": {
|
|
10
7
|
".": {
|
|
@@ -44,7 +41,7 @@
|
|
|
44
41
|
"@superbuilders/errors": "^4.0.2"
|
|
45
42
|
},
|
|
46
43
|
"optionalDependencies": {
|
|
47
|
-
"@bjornpagen/bumbledb-darwin-arm64": "0.
|
|
44
|
+
"@bjornpagen/bumbledb-darwin-arm64": "0.5.0"
|
|
48
45
|
},
|
|
49
46
|
"devDependencies": {
|
|
50
47
|
"@biomejs/biome": "^2.0.0-beta.5",
|
package/src/closed.ts
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
type Infer,
|
|
40
40
|
literalOf
|
|
41
41
|
} from "#fields.ts"
|
|
42
|
+
import type { AnyRelation, RelationField } from "#relation.ts"
|
|
42
43
|
import { resolveSelection, type SelectionBinding, type SelectionInput } from "#relation.ts"
|
|
43
44
|
import type { LiteralSpec } from "#spec.ts"
|
|
44
45
|
|
|
@@ -53,7 +54,7 @@ type PayloadField = Exclude<AnyField, { readonly fresh: true }>
|
|
|
53
54
|
* unspellable — the sealed shape mints the synthetic `id` itself (ordinal
|
|
54
55
|
* 0 of the matchable fields), so a declared column named `id` would be
|
|
55
56
|
* shadowed by the synthetic slot everywhere the shape resolves by name
|
|
56
|
-
* (`
|
|
57
|
+
* (`sealedFieldsOf`, the projected face, `spec.rs`'s resolver). The wall is
|
|
57
58
|
* typed here and judged again at construction in {@link mintClosed} — the
|
|
58
59
|
* runtime twin for untyped callers, warmer and earlier than the engine's
|
|
59
60
|
* `DuplicateFieldName` at `Db.create`.
|
|
@@ -193,6 +194,48 @@ interface AnyClosed {
|
|
|
193
194
|
readonly columns: Readonly<Record<string, PayloadField>>
|
|
194
195
|
}
|
|
195
196
|
|
|
197
|
+
/**
|
|
198
|
+
* THE relation-kind discriminant — the ONE spelling of "is this schema
|
|
199
|
+
* member closed?" (the type tier's twin is the `AnyClosed` conditional
|
|
200
|
+
* arms). A closed relation's runtime description carries its handle
|
|
201
|
+
* roster; an ordinary relation's never does. Every runtime closed/ordinary
|
|
202
|
+
* fork in the SDK judges through this predicate — never a re-spelled
|
|
203
|
+
* structural probe.
|
|
204
|
+
*/
|
|
205
|
+
function isClosedMember(member: AnyRelation | AnyClosed): member is AnyClosed {
|
|
206
|
+
return "handles" in member.data
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* The SEALED field list of a schema member — THE one reader of "what
|
|
211
|
+
* fields does this owner expose": an ordinary relation's declared fields;
|
|
212
|
+
* a closed relation's sealed shape — the synthetic `id` (the value's own
|
|
213
|
+
* roster-carrying descriptor, by identity) at ordinal 0, then the declared
|
|
214
|
+
* payload columns at declared index + 1 (the sealed shift, mirroring the
|
|
215
|
+
* engine's `SchemaDescriptor::sealed_fields`). A `ClosedColumn` is
|
|
216
|
+
* structurally a `RelationField`, so both kinds read uniformly.
|
|
217
|
+
*/
|
|
218
|
+
function sealedFieldsOf(member: AnyRelation | AnyClosed): readonly RelationField[] {
|
|
219
|
+
if (isClosedMember(member)) {
|
|
220
|
+
return Object.freeze([Object.freeze({ name: "id", field: member.id }), ...member.data.columns])
|
|
221
|
+
}
|
|
222
|
+
return member.data.fields
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* One sealed field by name — derived from {@link sealedFieldsOf}, so the
|
|
227
|
+
* closed synthetic `id` resolves everywhere a name is looked up (no reader
|
|
228
|
+
* can silently lack the `id` arm). `undefined` when the name is foreign
|
|
229
|
+
* (the type tiers make that unwritable; the engine re-judges at
|
|
230
|
+
* `Db.create`).
|
|
231
|
+
*/
|
|
232
|
+
function sealedFieldOf(member: AnyRelation | AnyClosed, fieldName: string): AnyField | undefined {
|
|
233
|
+
const declared = sealedFieldsOf(member).find(function byName(candidate) {
|
|
234
|
+
return candidate.name === fieldName
|
|
235
|
+
})
|
|
236
|
+
return declared?.field
|
|
237
|
+
}
|
|
238
|
+
|
|
196
239
|
/** Narrows the two-tier second argument: a handle tuple (bare tier) or a column block (payload tier). */
|
|
197
240
|
function isHandleTuple(
|
|
198
241
|
shape: readonly [string, ...string[]] | Record<string, PayloadField>
|
|
@@ -219,7 +262,8 @@ function handleKeysOwn<Handles extends string>(
|
|
|
219
262
|
* The trusted seam of the axiom-readback mint: every handle carries an own
|
|
220
263
|
* frozen row and every row carries every declared column as an own
|
|
221
264
|
* property — verified before the record is admitted as the typed
|
|
222
|
-
* {@link Axioms} (the
|
|
265
|
+
* {@link Axioms} (the trusted-admission-seam pattern — its home is
|
|
266
|
+
* `isTypedScope` in query/lower.ts).
|
|
223
267
|
*/
|
|
224
268
|
function axiomsMinted<Handles extends string, Cols extends Record<string, PayloadField>>(
|
|
225
269
|
record: Readonly<Record<string, object>>,
|
|
@@ -237,38 +281,22 @@ function axiomsMinted<Handles extends string, Cols extends Record<string, Payloa
|
|
|
237
281
|
})
|
|
238
282
|
}
|
|
239
283
|
|
|
240
|
-
/**
|
|
241
|
-
* Reads one handle's ground axiom row for lowering. The typed payload
|
|
242
|
-
* surface makes absence unrepresentable ({@link Axioms} carries every
|
|
243
|
-
* handle's row); the refusal below guards the one ill-typed path — payload
|
|
244
|
-
* columns with the bare tier's absent axioms — which no public spelling
|
|
245
|
-
* reaches.
|
|
246
|
-
*/
|
|
247
|
-
function groundRow<Handles extends string, Cols extends Record<string, PayloadField>>(
|
|
248
|
-
name: string,
|
|
249
|
-
axioms: Axioms<Handles, Cols> | undefined,
|
|
250
|
-
handle: Handles
|
|
251
|
-
): Readonly<Record<string, unknown>> {
|
|
252
|
-
if (axioms === undefined) {
|
|
253
|
-
throw errors.new(`closed relation ${name}: payload columns declared without ground axioms`)
|
|
254
|
-
}
|
|
255
|
-
return axioms[handle]
|
|
256
|
-
}
|
|
257
|
-
|
|
258
284
|
/**
|
|
259
285
|
* Mints the axiom-readback record: one own frozen row per handle (the bare
|
|
260
286
|
* tier's rows are empty — it declares no columns), each row a fresh copy of
|
|
261
|
-
* its ground axiom.
|
|
287
|
+
* its ground axiom. Both tiers supply a REAL axioms record ({@link
|
|
288
|
+
* closedBare} mints its empty rows), so the columns-without-axioms state is
|
|
289
|
+
* unrepresentable here — no undefined arm exists to guard.
|
|
262
290
|
*/
|
|
263
291
|
function mintAxioms<Handles extends string, Cols extends Record<string, PayloadField>>(
|
|
264
292
|
name: string,
|
|
265
293
|
handles: readonly Handles[],
|
|
266
294
|
cols: readonly ClosedColumn[],
|
|
267
|
-
axioms: Axioms<Handles, Cols>
|
|
295
|
+
axioms: Axioms<Handles, Cols>
|
|
268
296
|
): Axioms<Handles, Cols> {
|
|
269
297
|
const out: Record<string, object> = {}
|
|
270
298
|
for (const handle of handles) {
|
|
271
|
-
const row =
|
|
299
|
+
const row = Object.freeze({ ...axioms[handle] })
|
|
272
300
|
Object.defineProperty(out, handle, { value: row, enumerable: true })
|
|
273
301
|
}
|
|
274
302
|
Object.freeze(out)
|
|
@@ -321,12 +349,29 @@ function closed<const Name extends string, const Cols extends PayloadColumns, Ha
|
|
|
321
349
|
return closedPayload(name, shape, axioms)
|
|
322
350
|
}
|
|
323
351
|
|
|
324
|
-
/**
|
|
352
|
+
/**
|
|
353
|
+
* The bare tier's precisely-typed builder: no columns, and the axioms
|
|
354
|
+
* record is the EMPTY-ROW record over the handle roster (one own frozen
|
|
355
|
+
* `{}` per handle, `__proto__`-safe own-property definition) — the same
|
|
356
|
+
* representation the payload tier carries, so `mintClosed` never sees a
|
|
357
|
+
* tier fork and the columns-without-axioms state stops being spellable.
|
|
358
|
+
*/
|
|
325
359
|
function closedBare<Name extends string, Handles extends string>(
|
|
326
360
|
name: Name,
|
|
327
361
|
handles: readonly [Handles, ...Handles[]]
|
|
328
362
|
): Closed<Name, Handles, Record<never, never>> {
|
|
329
|
-
|
|
363
|
+
const empty: Record<string, object> = {}
|
|
364
|
+
for (const handle of handles) {
|
|
365
|
+
/** A duplicated name mints one row; the roster's own duplicate refusal in {@link mintClosed} stays the judge. */
|
|
366
|
+
if (!Object.hasOwn(empty, handle)) {
|
|
367
|
+
Object.defineProperty(empty, handle, { value: Object.freeze({}), enumerable: true })
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
Object.freeze(empty)
|
|
371
|
+
if (!axiomsMinted<Handles, Record<never, never>>(empty, handles, [])) {
|
|
372
|
+
throw errors.new(`closed relation ${name}: bare-tier axiom-row minting incomplete`)
|
|
373
|
+
}
|
|
374
|
+
return mintClosed<Name, Handles, Record<never, never>>(name, handles, {}, empty)
|
|
330
375
|
}
|
|
331
376
|
|
|
332
377
|
/**
|
|
@@ -379,7 +424,7 @@ function mintClosed<Name extends string, Handles extends string, Cols extends Re
|
|
|
379
424
|
name: Name,
|
|
380
425
|
handles: readonly Handles[],
|
|
381
426
|
columns: Cols,
|
|
382
|
-
axioms: Axioms<Handles, Cols>
|
|
427
|
+
axioms: Axioms<Handles, Cols>
|
|
383
428
|
): Closed<Name, Handles, Cols> {
|
|
384
429
|
if (handles.length === 0) {
|
|
385
430
|
throw errors.new(`closed relation ${name}: at least one handle is required (an empty vocabulary declares nothing)`)
|
|
@@ -405,8 +450,8 @@ function mintClosed<Name extends string, Handles extends string, Cols extends Re
|
|
|
405
450
|
}
|
|
406
451
|
Object.freeze(cols)
|
|
407
452
|
const rows: ClosedRow[] = handleList.map(function lowerRow(handle) {
|
|
453
|
+
const row: Readonly<Record<string, unknown>> = axioms[handle]
|
|
408
454
|
const values = cols.map(function lowerAxiomLiteral(column) {
|
|
409
|
-
const row = groundRow(name, axioms, handle)
|
|
410
455
|
return Object.freeze(literalOf(column.field, row[column.name]))
|
|
411
456
|
})
|
|
412
457
|
return Object.freeze({ handle, values: Object.freeze(values) })
|
|
@@ -471,4 +516,4 @@ export type {
|
|
|
471
516
|
PayloadField,
|
|
472
517
|
SelectedClosed
|
|
473
518
|
}
|
|
474
|
-
export { closed }
|
|
519
|
+
export { closed, isClosedMember, sealedFieldOf, sealedFieldsOf }
|