@bjornpagen/bumbledb 0.3.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 +246 -92
- package/README.md +31 -16
- package/dist/closed.d.ts +80 -75
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +102 -127
- package/dist/closed.js.map +1 -1
- package/dist/db.d.ts +37 -7
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +95 -55
- 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 +40 -40
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +9 -17
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +46 -15
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +58 -29
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +13 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -5
- 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 +33 -6
- package/dist/marshal.d.ts.map +1 -1
- package/dist/marshal.js +75 -26
- 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 +76 -28
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +12 -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 +308 -72
- 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 +15 -5
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +27 -8
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +37 -29
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +19 -47
- package/dist/query/scope.js.map +1 -1
- package/dist/relation.d.ts +17 -29
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +41 -38
- 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/spec.d.ts +3 -2
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +13 -4
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +75 -8
- package/dist/statements.js.map +1 -1
- package/package.json +2 -5
- package/src/closed.ts +144 -206
- package/src/db.ts +143 -68
- package/src/exhume.ts +1 -15
- package/src/face.ts +38 -48
- package/src/fields.ts +103 -49
- package/src/index.ts +11 -10
- package/src/law.ts +15 -14
- package/src/lower.ts +2 -9
- package/src/marshal.ts +82 -31
- package/src/native.ts +22 -4
- package/src/order.ts +156 -0
- package/src/query/atom.ts +70 -35
- package/src/query/lower.ts +354 -82
- package/src/query/predicate.ts +39 -4
- package/src/query/run.ts +27 -9
- package/src/query/scope.ts +52 -70
- package/src/relation.ts +54 -68
- package/src/schema.ts +7 -33
- package/src/spec.ts +3 -2
- package/src/statements.ts +82 -8
package/src/fields.ts
CHANGED
|
@@ -50,11 +50,15 @@ function span(start: bigint, end: bigint): IntervalValue {
|
|
|
50
50
|
* A closed relation's roster as seen from a referencing field: the handle
|
|
51
51
|
* namespace `where()` selections and ground axioms resolve bare handle ids
|
|
52
52
|
* through (the macro's own rule: a handle is legal exactly on a field that
|
|
53
|
-
* references a closed relation).
|
|
53
|
+
* references a closed relation). The handle union is PRECISE — `H` carries
|
|
54
|
+
* the literal handle names in declaration order (the unbound `string`
|
|
55
|
+
* default exists only as the fallback where no roster is in scope); the
|
|
56
|
+
* runtime twin is the same frozen declaration-order array that was always
|
|
57
|
+
* there.
|
|
54
58
|
*/
|
|
55
|
-
interface ClosedRoster {
|
|
59
|
+
interface ClosedRoster<H extends string = string> {
|
|
56
60
|
readonly name: string
|
|
57
|
-
readonly handles: readonly
|
|
61
|
+
readonly handles: readonly H[]
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
/** The `bool` field descriptor: value type `boolean`. No `.fresh` (macro parity). */
|
|
@@ -125,14 +129,16 @@ interface IntervalField<
|
|
|
125
129
|
|
|
126
130
|
/**
|
|
127
131
|
* A closed relation's reference field descriptor (`Kind.id`) — a u64
|
|
128
|
-
* descriptor carrying the closed linkage: the roster resolves
|
|
129
|
-
*
|
|
130
|
-
* generator class `"Kind.id"`.
|
|
131
|
-
*
|
|
132
|
+
* descriptor carrying the closed linkage: the roster resolves handle
|
|
133
|
+
* literals in selections and ground axioms, and `schema()` names the id's
|
|
134
|
+
* generator class `"Kind.id"`. The handle union `H` is the field's VALUE
|
|
135
|
+
* TYPE (see {@link Infer}); `kind: "u64"` stays load-bearing for the class
|
|
136
|
+
* map and JoinOk, which compare kind/class/width/element. Terminal: no
|
|
137
|
+
* `.fresh` — a vocabulary's rows are ground axioms, never minted.
|
|
132
138
|
*/
|
|
133
|
-
interface ClosedIdField {
|
|
139
|
+
interface ClosedIdField<H extends string = string> {
|
|
134
140
|
readonly kind: "u64"
|
|
135
|
-
readonly closed: ClosedRoster
|
|
141
|
+
readonly closed: ClosedRoster<H>
|
|
136
142
|
}
|
|
137
143
|
|
|
138
144
|
/** Any field descriptor, whatever its kind or marks. */
|
|
@@ -142,32 +148,55 @@ type AnyField = BoolField | StrField | U64Field | FreshU64Field | I64Field | Byt
|
|
|
142
148
|
* The bare structural VALUE type of a field descriptor — the one total
|
|
143
149
|
* definition every fact, result row, and query term reads: `bool` →
|
|
144
150
|
* `boolean`, `str` → `string`, `u64`/`i64` → `bigint`, `bytes<N>` →
|
|
145
|
-
* `Uint8Array`, intervals → {@link IntervalValue}
|
|
151
|
+
* `Uint8Array`, intervals → {@link IntervalValue}, and a closed reference →
|
|
152
|
+
* its PRECISE handle union (`"DirectPass" | "Failed"` — the string-literal
|
|
153
|
+
* union IS the value type at the TS surface; the engine keeps u64 row ids
|
|
154
|
+
* and the marshal owns the bijection). The closed arm precedes the `u64`
|
|
155
|
+
* arm because a closed reference is structurally a u64 descriptor plus the
|
|
156
|
+
* roster.
|
|
146
157
|
*/
|
|
147
158
|
type Infer<F extends AnyField> = F extends { readonly kind: "bool" }
|
|
148
159
|
? boolean
|
|
149
160
|
: F extends { readonly kind: "str" }
|
|
150
161
|
? string
|
|
151
|
-
: F extends { readonly
|
|
152
|
-
?
|
|
153
|
-
: F extends { readonly kind: "
|
|
162
|
+
: F extends { readonly closed: { readonly handles: readonly (infer H extends string)[] } }
|
|
163
|
+
? H
|
|
164
|
+
: F extends { readonly kind: "u64" }
|
|
154
165
|
? bigint
|
|
155
|
-
: F extends { readonly kind: "
|
|
156
|
-
?
|
|
157
|
-
: F extends { readonly kind: "
|
|
158
|
-
?
|
|
159
|
-
:
|
|
166
|
+
: F extends { readonly kind: "i64" }
|
|
167
|
+
? bigint
|
|
168
|
+
: F extends { readonly kind: "bytes" }
|
|
169
|
+
? Uint8Array
|
|
170
|
+
: F extends { readonly kind: "interval" }
|
|
171
|
+
? IntervalValue
|
|
172
|
+
: never
|
|
160
173
|
|
|
161
174
|
/**
|
|
162
|
-
* The typed shape refusal
|
|
175
|
+
* The typed shape refusal shared by every literal machine — the selection
|
|
176
|
+
* lowering here, the row marshaler (`marshal.ts`), and the query-literal
|
|
177
|
+
* tagger (`query/lower.ts`) all throw through this ONE voice; reached only
|
|
163
178
|
* through ill-typed input (the well-typed surfaces make it unrepresentable).
|
|
164
179
|
*/
|
|
165
|
-
function literalShapeError(expected: string, value: unknown): Error {
|
|
166
|
-
return errors.new(
|
|
180
|
+
function literalShapeError(context: string, expected: string, value: unknown): Error {
|
|
181
|
+
return errors.new(`${context}: expected ${expected}, got ${typeof value}`)
|
|
167
182
|
}
|
|
168
183
|
|
|
169
|
-
/**
|
|
170
|
-
|
|
184
|
+
/**
|
|
185
|
+
* The roster a field descriptor carries — THE one reader: present exactly
|
|
186
|
+
* on a closed-reference descriptor (the structural `closed` property of
|
|
187
|
+
* {@link ClosedIdField}), absent on every other field kind. Tolerates
|
|
188
|
+
* `undefined` so name-lookup misses flow through without a re-spelled
|
|
189
|
+
* probe at every call site.
|
|
190
|
+
*/
|
|
191
|
+
function rosterOf(field: AnyField | undefined): ClosedRoster | undefined {
|
|
192
|
+
if (field !== undefined && "closed" in field) {
|
|
193
|
+
return field.closed
|
|
194
|
+
}
|
|
195
|
+
return undefined
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Narrows an interval-shaped value: a plain object with bigint start/end — THE one interval predicate. */
|
|
199
|
+
function isIntervalValue(value: unknown): value is IntervalValue {
|
|
171
200
|
return (
|
|
172
201
|
typeof value === "object" &&
|
|
173
202
|
value !== null &&
|
|
@@ -179,28 +208,28 @@ function isIntervalLiteral(value: unknown): value is IntervalValue {
|
|
|
179
208
|
}
|
|
180
209
|
|
|
181
210
|
/**
|
|
182
|
-
* Resolves one closed-handle literal: the handle
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
211
|
+
* Resolves one closed-handle literal: the handle NAME, verified against the
|
|
212
|
+
* roster — an unknown name is a construction error, the belt the wide
|
|
213
|
+
* fallback type deliberately does not provide (structural values make any
|
|
214
|
+
* string spellable here; the roster judges). The name IS the value at the
|
|
215
|
+
* TS surface (the drizzle law); the wire literal already crossed as
|
|
216
|
+
* `{ kind: "handle", handle }`, so the output — and every fingerprint
|
|
217
|
+
* derived from it — is untouched.
|
|
186
218
|
*/
|
|
187
219
|
function handleLiteral(closed: ClosedRoster, value: unknown): LiteralSpec {
|
|
188
|
-
if (typeof value !== "
|
|
189
|
-
throw literalShapeError(`a ${closed.name} handle
|
|
220
|
+
if (typeof value !== "string") {
|
|
221
|
+
throw literalShapeError("selection literal", `a ${closed.name} handle name (string)`, value)
|
|
190
222
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
throw errors.new(
|
|
194
|
-
`closed relation ${closed.name} has no handle with id ${value} (roster holds ${closed.handles.length})`
|
|
195
|
-
)
|
|
223
|
+
if (!closed.handles.includes(value)) {
|
|
224
|
+
throw errors.new(`"${value}" is not a handle of ${closed.name} — the roster is ${closed.handles.join(", ")}`)
|
|
196
225
|
}
|
|
197
|
-
return { kind: "handle", handle }
|
|
226
|
+
return { kind: "handle", handle: value }
|
|
198
227
|
}
|
|
199
228
|
|
|
200
229
|
/** Lowers one interval literal at its element type. */
|
|
201
230
|
function intervalLiteral(element: "u64" | "i64", value: unknown): LiteralSpec {
|
|
202
|
-
if (!
|
|
203
|
-
throw literalShapeError("interval ({ start, end } bigints)", value)
|
|
231
|
+
if (!isIntervalValue(value)) {
|
|
232
|
+
throw literalShapeError("selection literal", "interval ({ start, end } bigints)", value)
|
|
204
233
|
}
|
|
205
234
|
if (element === "u64") {
|
|
206
235
|
return { kind: "value", value: { kind: "intervalU64", start: value.start, end: value.end } }
|
|
@@ -288,43 +317,55 @@ function interval(element: U64Field | I64Field, width?: bigint): IntervalField<"
|
|
|
288
317
|
* Lowers one host literal at its field position to the wire
|
|
289
318
|
* {@link LiteralSpec} — the selection-literal machine ground axioms and
|
|
290
319
|
* `where()` bindings both ride (one machine, same errors — the macro's own
|
|
291
|
-
* rule). A value on a closed-reference field
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
* the field's structural kind.
|
|
320
|
+
* rule). A value on a closed-reference field IS its handle NAME (verified
|
|
321
|
+
* against the roster: an unknown name is a construction error); everything
|
|
322
|
+
* else lowers to a plain value tagged by the field's structural kind.
|
|
295
323
|
*/
|
|
296
324
|
function literalOf(field: AnyField, value: unknown): LiteralSpec {
|
|
297
|
-
|
|
298
|
-
|
|
325
|
+
const roster = rosterOf(field)
|
|
326
|
+
if (roster !== undefined) {
|
|
327
|
+
return handleLiteral(roster, value)
|
|
299
328
|
}
|
|
300
329
|
switch (field.kind) {
|
|
301
330
|
case "bool": {
|
|
302
331
|
if (typeof value !== "boolean") {
|
|
303
|
-
throw literalShapeError("boolean", value)
|
|
332
|
+
throw literalShapeError("selection literal", "boolean", value)
|
|
304
333
|
}
|
|
305
334
|
return { kind: "value", value: { kind: "bool", value } }
|
|
306
335
|
}
|
|
307
336
|
case "u64": {
|
|
308
337
|
if (typeof value !== "bigint") {
|
|
309
|
-
throw literalShapeError("bigint", value)
|
|
338
|
+
throw literalShapeError("selection literal", "bigint", value)
|
|
310
339
|
}
|
|
311
340
|
return { kind: "value", value: { kind: "u64", value } }
|
|
312
341
|
}
|
|
313
342
|
case "i64": {
|
|
314
343
|
if (typeof value !== "bigint") {
|
|
315
|
-
throw literalShapeError("bigint", value)
|
|
344
|
+
throw literalShapeError("selection literal", "bigint", value)
|
|
316
345
|
}
|
|
317
346
|
return { kind: "value", value: { kind: "i64", value } }
|
|
318
347
|
}
|
|
319
348
|
case "str": {
|
|
320
349
|
if (typeof value !== "string") {
|
|
321
|
-
throw literalShapeError("string", value)
|
|
350
|
+
throw literalShapeError("selection literal", "string", value)
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* The marshal's bijection law at the schema-literal seam
|
|
354
|
+
* (`marshal.ts` cellOf): a lone surrogate would cross dbCreate
|
|
355
|
+
* lossily (stored as U+FFFD engine-side), collapsing two
|
|
356
|
+
* distinct TS schema values into one stored theory/fingerprint
|
|
357
|
+
* and splitting the canonical statement rendering from the
|
|
358
|
+
* SDK's. All three string-admission seams — fact row, query
|
|
359
|
+
* literal/param, schema literal — enforce the one law.
|
|
360
|
+
*/
|
|
361
|
+
if (!value.isWellFormed()) {
|
|
362
|
+
throw literalShapeError("selection literal", "well-formed string", value)
|
|
322
363
|
}
|
|
323
364
|
return { kind: "value", value: { kind: "string", value } }
|
|
324
365
|
}
|
|
325
366
|
case "bytes": {
|
|
326
367
|
if (!(value instanceof Uint8Array)) {
|
|
327
|
-
throw literalShapeError("Uint8Array", value)
|
|
368
|
+
throw literalShapeError("selection literal", "Uint8Array", value)
|
|
328
369
|
}
|
|
329
370
|
return { kind: "value", value: { kind: "fixedBytes", value } }
|
|
330
371
|
}
|
|
@@ -347,4 +388,17 @@ export type {
|
|
|
347
388
|
StrField,
|
|
348
389
|
U64Field
|
|
349
390
|
}
|
|
350
|
-
export {
|
|
391
|
+
export {
|
|
392
|
+
assertDeclarationOrderKey,
|
|
393
|
+
bool,
|
|
394
|
+
bytes,
|
|
395
|
+
i64,
|
|
396
|
+
interval,
|
|
397
|
+
isIntervalValue,
|
|
398
|
+
literalOf,
|
|
399
|
+
literalShapeError,
|
|
400
|
+
rosterOf,
|
|
401
|
+
span,
|
|
402
|
+
str,
|
|
403
|
+
u64
|
|
404
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -14,10 +14,13 @@
|
|
|
14
14
|
* `db.prepare` as a plain value; the comparison/connective builders are
|
|
15
15
|
* also free exports, and the free names `eq`/`not`/`and`/`or` collide with
|
|
16
16
|
* common host identifiers — import aliasing is the answer; the SDK does
|
|
17
|
-
* not rename for collision-avoidance),
|
|
17
|
+
* not rename for collision-avoidance), the exhume surface
|
|
18
18
|
* (`Db.exhume` — the one schema-independent read path: the store's
|
|
19
19
|
* self-described shapes and raw facts by name, typed at bare structural
|
|
20
|
-
* values, deliberately schema-free)
|
|
20
|
+
* values, deliberately schema-free), and the answer-ordering helpers
|
|
21
|
+
* (`by`/`desc` — sort keys as data for the language's own `.sort`; answers
|
|
22
|
+
* are sets, the engine never orders, and limit is the language's own
|
|
23
|
+
* `.slice`). The raw native bridge is not exported.
|
|
21
24
|
*/
|
|
22
25
|
|
|
23
26
|
export type {
|
|
@@ -50,7 +53,7 @@ export type {
|
|
|
50
53
|
WitnessedWriteResult,
|
|
51
54
|
WriteResult
|
|
52
55
|
} from "#db.ts"
|
|
53
|
-
export { abandon, Db, ErrNewtypeMismatch } from "#db.ts"
|
|
56
|
+
export { abandon, Db, ErrNewtypeMismatch, ErrWitnessedLivelock, WITNESSED_ATTEMPT_CAP } from "#db.ts"
|
|
54
57
|
export type {
|
|
55
58
|
Exhumed,
|
|
56
59
|
ExhumedAxiom,
|
|
@@ -75,12 +78,11 @@ export type {
|
|
|
75
78
|
FaceShapeMismatch,
|
|
76
79
|
FaceShapes,
|
|
77
80
|
FaceSource,
|
|
78
|
-
OneOf,
|
|
79
81
|
OwnerOf,
|
|
80
82
|
SameArity,
|
|
81
83
|
SameShapes
|
|
82
84
|
} from "#face.ts"
|
|
83
|
-
export { on
|
|
85
|
+
export { on } from "#face.ts"
|
|
84
86
|
export type {
|
|
85
87
|
AnyField,
|
|
86
88
|
BoolField,
|
|
@@ -106,6 +108,8 @@ export type {
|
|
|
106
108
|
Staleness,
|
|
107
109
|
StatementKindTag
|
|
108
110
|
} from "#native.ts"
|
|
111
|
+
export type { Desc, SortKey } from "#order.ts"
|
|
112
|
+
export { by, desc } from "#order.ts"
|
|
109
113
|
|
|
110
114
|
export type {
|
|
111
115
|
AnyCond,
|
|
@@ -120,7 +124,7 @@ export type {
|
|
|
120
124
|
SelectColumn,
|
|
121
125
|
Tree
|
|
122
126
|
} from "#query/atom.ts"
|
|
123
|
-
export { ALLEN, allen, and,
|
|
127
|
+
export { ALLEN, allen, and, eq, ge, gt, le, lt, ne, not, or, pointIn } from "#query/atom.ts"
|
|
124
128
|
export type {
|
|
125
129
|
AnyQuery,
|
|
126
130
|
AnyRuleValue,
|
|
@@ -151,16 +155,13 @@ export type {
|
|
|
151
155
|
ParamEntry,
|
|
152
156
|
ParamsRecord,
|
|
153
157
|
SetParam,
|
|
154
|
-
Var
|
|
155
|
-
VarsRecord
|
|
158
|
+
Var
|
|
156
159
|
} from "#query/scope.ts"
|
|
157
160
|
export type { Agg, SelectEntry } from "#query/select.ts"
|
|
158
161
|
export type {
|
|
159
162
|
AnyRelation,
|
|
160
163
|
AnySelected,
|
|
161
164
|
Fact,
|
|
162
|
-
FieldRef,
|
|
163
|
-
FieldRefs,
|
|
164
165
|
FieldsShape,
|
|
165
166
|
FreshKeys,
|
|
166
167
|
InsertFact,
|
package/src/law.ts
CHANGED
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
|
|
53
53
|
import * as errors from "@superbuilders/errors"
|
|
54
54
|
import type { AnyClosed } from "#closed.ts"
|
|
55
|
+
import { isClosedMember, sealedFieldsOf } from "#closed.ts"
|
|
55
56
|
import type { FaceData } from "#face.ts"
|
|
56
57
|
import type { AnyRelation, RelationFields } from "#relation.ts"
|
|
57
58
|
import type { SchemaRelation, SchemaRelations } from "#schema.ts"
|
|
@@ -328,22 +329,21 @@ interface MemberCoords {
|
|
|
328
329
|
readonly fields: ReadonlyArray<{ readonly name: string; readonly generator: boolean }>
|
|
329
330
|
}
|
|
330
331
|
|
|
331
|
-
/**
|
|
332
|
+
/**
|
|
333
|
+
* Reads every member's coordinates off the relation record, declaration
|
|
334
|
+
* order throughout — the sealed shape through THE one reader
|
|
335
|
+
* (`sealedFieldsOf`): a closed member's generator is its synthetic `id`
|
|
336
|
+
* (ordinal 0), an ordinary member's generators are its fresh-marked fields.
|
|
337
|
+
*/
|
|
332
338
|
function memberCoords(relations: SchemaRelations): MemberCoords[] {
|
|
333
339
|
const out: MemberCoords[] = []
|
|
334
340
|
for (const [relationName, member] of Object.entries(relations)) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
]
|
|
342
|
-
out.push({ relation: relationName, fields })
|
|
343
|
-
continue
|
|
344
|
-
}
|
|
345
|
-
const fields = member.data.fields.map(function fieldCoord(declared) {
|
|
346
|
-
return { name: declared.name, generator: "fresh" in declared.field && declared.field.fresh === true }
|
|
341
|
+
const closed = isClosedMember(member)
|
|
342
|
+
const fields = sealedFieldsOf(member).map(function fieldCoord(declared) {
|
|
343
|
+
return {
|
|
344
|
+
name: declared.name,
|
|
345
|
+
generator: closed ? declared.name === "id" : "fresh" in declared.field && declared.field.fresh === true
|
|
346
|
+
}
|
|
347
347
|
})
|
|
348
348
|
out.push({ relation: relationName, fields })
|
|
349
349
|
}
|
|
@@ -490,7 +490,8 @@ function computeClasses(name: string, relations: SchemaRelations, statements: re
|
|
|
490
490
|
}
|
|
491
491
|
|
|
492
492
|
/**
|
|
493
|
-
* The trusted seam of the class-map mint (the
|
|
493
|
+
* The trusted admission seam of the class-map mint (the pattern's home is
|
|
494
|
+
* `isTypedScope` in query/lower.ts): the
|
|
494
495
|
* checkable facts — one own record per declared relation, one own entry
|
|
495
496
|
* per declared field (the closed sealed shape's `id` included), everything
|
|
496
497
|
* frozen — are verified before the runtime map is admitted at the computed
|
package/src/lower.ts
CHANGED
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { AnyClosed } from "#closed.ts"
|
|
12
|
+
import { isClosedMember } from "#closed.ts"
|
|
12
13
|
import type { FaceData } from "#face.ts"
|
|
13
14
|
import type { AnyField } from "#fields.ts"
|
|
14
15
|
import type { RelationClasses } from "#law.ts"
|
|
15
16
|
import type { AnyRelation } from "#relation.ts"
|
|
16
|
-
import type { AnySchema
|
|
17
|
+
import type { AnySchema } from "#schema.ts"
|
|
17
18
|
import type {
|
|
18
19
|
FieldSpec,
|
|
19
20
|
LiteralSetSpec,
|
|
@@ -25,14 +26,6 @@ import type {
|
|
|
25
26
|
} from "#spec.ts"
|
|
26
27
|
import type { Statement } from "#statements.ts"
|
|
27
28
|
|
|
28
|
-
/**
|
|
29
|
-
* The relation-kind discriminant: a closed relation's runtime description
|
|
30
|
-
* carries its handle roster, an ordinary relation's never does.
|
|
31
|
-
*/
|
|
32
|
-
function isClosedMember(member: SchemaRelation): member is AnyClosed {
|
|
33
|
-
return "handles" in member.data
|
|
34
|
-
}
|
|
35
|
-
|
|
36
29
|
/**
|
|
37
30
|
* Lowers one field descriptor's structural type to the wire
|
|
38
31
|
* {@link ValueTypeSpec}: the S1 kind tags map 1:1 onto the `ValueType`
|
package/src/marshal.ts
CHANGED
|
@@ -13,10 +13,23 @@
|
|
|
13
13
|
* the engine admitted IS a legal fact of its relation (the same trust
|
|
14
14
|
* direction as Rust's typed readback). Shape mismatches here are genuine
|
|
15
15
|
* failures and THROW typed; they are never domain data.
|
|
16
|
+
*
|
|
17
|
+
* THE CLOSED BIJECTION (0.4.0): a closed-referencing cell crosses this
|
|
18
|
+
* boundary as its handle NAME — the write side lowers name → u64 row id
|
|
19
|
+
* (declaration order = row ids, the sealed roster's own law, ≤ 256 rows),
|
|
20
|
+
* the read side lifts id → name, and both directions are total and static
|
|
21
|
+
* over the roster. An unknown name is a pointed THROW at the write seam —
|
|
22
|
+
* a deliberate UPGRADE over 0.3.0, where any bigint sailed through the
|
|
23
|
+
* marshal to a commit-time containment violation; the wrong spelling now
|
|
24
|
+
* dies here, before the engine ever sees the row. An out-of-roster id on
|
|
25
|
+
* the read side (reachable only in a store whose closed-typed column was
|
|
26
|
+
* never pinned by its containment law) is equally pointed — never a
|
|
27
|
+
* silent fallback, never `undefined`.
|
|
16
28
|
*/
|
|
17
29
|
|
|
18
30
|
import * as errors from "@superbuilders/errors"
|
|
19
|
-
import type { AnyField } from "#fields.ts"
|
|
31
|
+
import type { AnyField, ClosedRoster } from "#fields.ts"
|
|
32
|
+
import { isIntervalValue, literalShapeError, rosterOf } from "#fields.ts"
|
|
20
33
|
import type { FactValue } from "#native.ts"
|
|
21
34
|
import type { AnyRelation, Fact, FreshKeys, RelationData } from "#relation.ts"
|
|
22
35
|
|
|
@@ -54,23 +67,6 @@ type KeyFact<R extends AnyRelation> = [FreshKeys<R>] extends [never]
|
|
|
54
67
|
? Partial<Fact<R>>
|
|
55
68
|
: { [K in FreshKeys<R>]: Fact<R>[K] }
|
|
56
69
|
|
|
57
|
-
/** The typed shape refusal of the row marshaler — a genuine failure, never data. */
|
|
58
|
-
function cellShapeError(context: string, expected: string, value: unknown): Error {
|
|
59
|
-
return errors.new(`${context}: expected ${expected}, got ${typeof value}`)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** Narrows an interval cell: a plain object with bigint start/end. */
|
|
63
|
-
function isIntervalCell(value: unknown): value is { readonly start: bigint; readonly end: bigint } {
|
|
64
|
-
return (
|
|
65
|
-
typeof value === "object" &&
|
|
66
|
-
value !== null &&
|
|
67
|
-
"start" in value &&
|
|
68
|
-
"end" in value &&
|
|
69
|
-
typeof value.start === "bigint" &&
|
|
70
|
-
typeof value.end === "bigint"
|
|
71
|
-
)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
70
|
/**
|
|
75
71
|
* Reprojects any host object to a string-indexed record — the boundary
|
|
76
72
|
* through which generic fact objects (whose type parameters carry no index
|
|
@@ -80,31 +76,82 @@ function recordOf(fact: object): Record<string, unknown> {
|
|
|
80
76
|
return Object.fromEntries(Object.entries(fact))
|
|
81
77
|
}
|
|
82
78
|
|
|
79
|
+
/**
|
|
80
|
+
* The write half of the closed bijection: one handle NAME to its u64 row
|
|
81
|
+
* id (declaration order = row ids — the engine's own minting of the
|
|
82
|
+
* sealed extension). An unknown name is a pointed refusal naming the
|
|
83
|
+
* vocabulary and its roster — the 0.4.0 upgrade over any-bigint-compiles:
|
|
84
|
+
* the wrong spelling dies at the marshal, never as a commit-time
|
|
85
|
+
* violation. `indexOf` is the whole machine (the roster is ≤ 256 rows,
|
|
86
|
+
* engine law — no map is warranted).
|
|
87
|
+
*/
|
|
88
|
+
function closedCellOf(context: string, closed: ClosedRoster, name: string): FactValue {
|
|
89
|
+
const id = closed.handles.indexOf(name)
|
|
90
|
+
if (id === -1) {
|
|
91
|
+
throw errors.new(
|
|
92
|
+
`${context}: "${name}" is not a handle of ${closed.name} — the roster is ${closed.handles.join(", ")}`
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
return BigInt(id)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The read half of the closed bijection: one u64 row id back to its handle
|
|
100
|
+
* NAME (`Number(cell)` is safe — the sealed extension holds at most 256
|
|
101
|
+
* rows, engine law). An id outside the roster THROWS pointed, never a
|
|
102
|
+
* silent fallback and never `undefined`: the state is reachable only in a
|
|
103
|
+
* store whose closed-typed column was never pinned by its containment law,
|
|
104
|
+
* and the error names that missing piece.
|
|
105
|
+
*/
|
|
106
|
+
function handleOf(context: string, closed: ClosedRoster, cell: FactValue): string {
|
|
107
|
+
if (typeof cell !== "bigint") {
|
|
108
|
+
throw literalShapeError(context, `a ${closed.name} handle id (bigint)`, cell)
|
|
109
|
+
}
|
|
110
|
+
const handle = closed.handles[Number(cell)]
|
|
111
|
+
if (handle === undefined) {
|
|
112
|
+
throw errors.new(
|
|
113
|
+
`${context}: id ${cell} is outside the ${closed.name} roster (${closed.handles.join(", ")}) — the column types ${closed.name} but no law pins it — a containment statement is the missing piece`
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
return handle
|
|
117
|
+
}
|
|
118
|
+
|
|
83
119
|
/**
|
|
84
120
|
* Marshals one host cell at its field position to the natural wire value,
|
|
85
121
|
* schema-directed by the field descriptor's structural kind (never
|
|
86
|
-
* guessed).
|
|
87
|
-
*
|
|
88
|
-
*
|
|
122
|
+
* guessed). A closed-referencing cell arrives as its handle NAME and
|
|
123
|
+
* lowers through {@link closedCellOf} — the arm precedes the switch
|
|
124
|
+
* because a closed reference is structurally a u64 descriptor plus the
|
|
125
|
+
* roster (the same precedence `Infer` pins at the type level). Everything
|
|
126
|
+
* else is bare, so the runtime values ARE the wire's natural JS values;
|
|
127
|
+
* widths and domain labels are the engine's own judgment at the write
|
|
128
|
+
* boundary.
|
|
89
129
|
*/
|
|
90
130
|
function cellOf(context: string, field: AnyField, value: unknown): FactValue {
|
|
131
|
+
const roster = rosterOf(field)
|
|
132
|
+
if (roster !== undefined) {
|
|
133
|
+
if (typeof value !== "string") {
|
|
134
|
+
throw literalShapeError(context, `a ${roster.name} handle name (string)`, value)
|
|
135
|
+
}
|
|
136
|
+
return closedCellOf(context, roster, value)
|
|
137
|
+
}
|
|
91
138
|
switch (field.kind) {
|
|
92
139
|
case "bool": {
|
|
93
140
|
if (typeof value !== "boolean") {
|
|
94
|
-
throw
|
|
141
|
+
throw literalShapeError(context, "boolean", value)
|
|
95
142
|
}
|
|
96
143
|
return value
|
|
97
144
|
}
|
|
98
145
|
case "u64":
|
|
99
146
|
case "i64": {
|
|
100
147
|
if (typeof value !== "bigint") {
|
|
101
|
-
throw
|
|
148
|
+
throw literalShapeError(context, "bigint", value)
|
|
102
149
|
}
|
|
103
150
|
return value
|
|
104
151
|
}
|
|
105
152
|
case "str": {
|
|
106
153
|
if (typeof value !== "string") {
|
|
107
|
-
throw
|
|
154
|
+
throw literalShapeError(context, "string", value)
|
|
108
155
|
}
|
|
109
156
|
/**
|
|
110
157
|
* A lone surrogate would be lossily replaced with U+FFFD at the
|
|
@@ -114,19 +161,19 @@ function cellOf(context: string, field: AnyField, value: unknown): FactValue {
|
|
|
114
161
|
* lookup lowers through.
|
|
115
162
|
*/
|
|
116
163
|
if (!value.isWellFormed()) {
|
|
117
|
-
throw
|
|
164
|
+
throw literalShapeError(context, "well-formed string", value)
|
|
118
165
|
}
|
|
119
166
|
return value
|
|
120
167
|
}
|
|
121
168
|
case "bytes": {
|
|
122
169
|
if (!(value instanceof Uint8Array)) {
|
|
123
|
-
throw
|
|
170
|
+
throw literalShapeError(context, "Uint8Array", value)
|
|
124
171
|
}
|
|
125
172
|
return value
|
|
126
173
|
}
|
|
127
174
|
case "interval": {
|
|
128
|
-
if (!
|
|
129
|
-
throw
|
|
175
|
+
if (!isIntervalValue(value)) {
|
|
176
|
+
throw literalShapeError(context, "interval ({ start, end } bigints)", value)
|
|
130
177
|
}
|
|
131
178
|
return { start: value.start, end: value.end }
|
|
132
179
|
}
|
|
@@ -210,7 +257,9 @@ function isMintedFresh<R extends AnyRelation>(
|
|
|
210
257
|
/**
|
|
211
258
|
* Unmarshals one positional row to the relation's named, frozen fact object
|
|
212
259
|
* of bare structural values — the inverse of {@link rowOf},
|
|
213
|
-
* ordinal-directed by the same declaration order.
|
|
260
|
+
* ordinal-directed by the same declaration order. Closed-referencing cells
|
|
261
|
+
* lift id → handle NAME through {@link handleOf} (the read half of the
|
|
262
|
+
* bijection), so every fact a user sees speaks the roster's vocabulary.
|
|
214
263
|
*/
|
|
215
264
|
function factOf<R extends AnyRelation>(relation: R, row: readonly FactValue[]): Fact<R> {
|
|
216
265
|
const data = relation.data
|
|
@@ -225,7 +274,9 @@ function factOf<R extends AnyRelation>(relation: R, row: readonly FactValue[]):
|
|
|
225
274
|
if (cell === undefined) {
|
|
226
275
|
throw errors.new(`relation ${data.name}: row cell ${ordinal} (${declared.name}) is absent`)
|
|
227
276
|
}
|
|
228
|
-
|
|
277
|
+
const roster = rosterOf(declared.field)
|
|
278
|
+
decoded[declared.name] =
|
|
279
|
+
roster !== undefined ? handleOf(`relation ${data.name} field ${declared.name}`, roster, cell) : cell
|
|
229
280
|
})
|
|
230
281
|
Object.freeze(decoded)
|
|
231
282
|
if (!isCompleteFact(relation, decoded)) {
|
|
@@ -235,4 +286,4 @@ function factOf<R extends AnyRelation>(relation: R, row: readonly FactValue[]):
|
|
|
235
286
|
}
|
|
236
287
|
|
|
237
288
|
export type { KeyFact, Minted }
|
|
238
|
-
export { cellOf, factOf, isFreshField, isMintedFresh, keyRowOf, recordOf, rowOf }
|
|
289
|
+
export { cellOf, factOf, handleOf, isFreshField, isMintedFresh, keyRowOf, recordOf, rowOf }
|
package/src/native.ts
CHANGED
|
@@ -241,7 +241,7 @@ interface Violation {
|
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
/**
|
|
244
|
-
* `dbCreate`/`dbOpen`'s domain outcome. `schemaError`
|
|
244
|
+
* `dbCreate`/`dbOpen`'s domain outcome. `schemaError` spans both spec
|
|
245
245
|
* resolution (unresolvable names, banned spellings — every issue in one
|
|
246
246
|
* message) and schema validation at the declaration boundary;
|
|
247
247
|
* `newtypeMismatch` is the coherence wall's own kind — a spec whose
|
|
@@ -471,8 +471,12 @@ interface Native {
|
|
|
471
471
|
* The sole platform this release ships (PRD-03 ruling 1: prebuilt-only,
|
|
472
472
|
* darwin-arm64). The per-platform-package structure below makes adding
|
|
473
473
|
* `darwin-x64`/`linux-*`/`win32-*` pure addition — one more `os`/`cpu`-gated
|
|
474
|
-
* package plus a CI matrix — never a redesign
|
|
475
|
-
*
|
|
474
|
+
* package plus a CI matrix — never a redesign. This constant names the
|
|
475
|
+
* shipped set for the unsupported-platform message; the build's
|
|
476
|
+
* `PUBLISH_PLATFORM` (`scripts/platform.ts` — src cannot import scripts,
|
|
477
|
+
* the packaging boundary) and the `ts/.gitignore` carve-out spell the same
|
|
478
|
+
* target, and the single-source pin in `test/build-platform.test.ts` holds
|
|
479
|
+
* all three in lockstep.
|
|
476
480
|
*/
|
|
477
481
|
const SHIPPED_PLATFORMS = "darwin-arm64"
|
|
478
482
|
|
|
@@ -536,6 +540,20 @@ function loadNativeBinding(platform: string, arch: string): Native {
|
|
|
536
540
|
*/
|
|
537
541
|
const native: Native = loadNativeBinding(process.platform, process.arch)
|
|
538
542
|
|
|
543
|
+
/**
|
|
544
|
+
* The bridge guard — THE one wrapper every native call crosses (db.ts and
|
|
545
|
+
* exhume.ts both import it): runs one native call and wraps anything it
|
|
546
|
+
* throws, so marshal-shape refusals and handle-lifecycle refusals cross as
|
|
547
|
+
* genuine typed failures, never bare foreign errors.
|
|
548
|
+
*/
|
|
549
|
+
function bridged<T>(context: string, run: () => T): T {
|
|
550
|
+
const result = errors.trySync(run)
|
|
551
|
+
if (result.error) {
|
|
552
|
+
throw errors.wrap(result.error, context)
|
|
553
|
+
}
|
|
554
|
+
return result.data
|
|
555
|
+
}
|
|
556
|
+
|
|
539
557
|
export type {
|
|
540
558
|
AggOpIr,
|
|
541
559
|
AtomIr,
|
|
@@ -577,4 +595,4 @@ export type {
|
|
|
577
595
|
ViolationFact,
|
|
578
596
|
WriteFromResult
|
|
579
597
|
}
|
|
580
|
-
export { loadNativeBinding, native }
|
|
598
|
+
export { bridged, loadNativeBinding, native, SHIPPED_PLATFORMS }
|