@bjornpagen/bumbledb 0.4.0 → 0.6.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 +191 -92
- package/README.md +9 -7
- 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 +89 -72
- 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 +18 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -6
- 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 +140 -206
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +33 -52
- package/dist/query/atom.js.map +1 -1
- package/dist/query/find.d.ts +116 -0
- package/dist/query/find.d.ts.map +1 -0
- package/dist/query/{select.js → find.js} +22 -22
- package/dist/query/find.js.map +1 -0
- package/dist/query/lower.d.ts +124 -162
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +462 -507
- package/dist/query/lower.js.map +1 -1
- package/dist/query/predicate.d.ts +22 -14
- package/dist/query/predicate.d.ts.map +1 -1
- package/dist/query/predicate.js +45 -20
- package/dist/query/predicate.js.map +1 -1
- package/dist/query/run.d.ts +3 -3
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +10 -11
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +124 -83
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +77 -70
- 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 +126 -88
- package/src/exhume.ts +1 -15
- package/src/face.ts +4 -3
- package/src/fields.ts +58 -16
- package/src/index.ts +19 -13
- 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 +186 -276
- package/src/query/find.ts +212 -0
- package/src/query/lower.ts +614 -749
- package/src/query/predicate.ts +47 -20
- package/src/query/run.ts +11 -13
- package/src/query/scope.ts +165 -132
- package/src/relation.ts +9 -51
- package/src/schema.ts +7 -33
- package/src/statements.ts +33 -38
- package/dist/query/select.d.ts +0 -128
- package/dist/query/select.d.ts.map +0 -1
- package/dist/query/select.js.map +0 -1
- package/src/query/select.ts +0 -215
package/src/fields.ts
CHANGED
|
@@ -172,15 +172,31 @@ type Infer<F extends AnyField> = F extends { readonly kind: "bool" }
|
|
|
172
172
|
: never
|
|
173
173
|
|
|
174
174
|
/**
|
|
175
|
-
* 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
|
|
176
178
|
* through ill-typed input (the well-typed surfaces make it unrepresentable).
|
|
177
179
|
*/
|
|
178
|
-
function literalShapeError(expected: string, value: unknown): Error {
|
|
179
|
-
return errors.new(
|
|
180
|
+
function literalShapeError(context: string, expected: string, value: unknown): Error {
|
|
181
|
+
return errors.new(`${context}: expected ${expected}, got ${typeof value}`)
|
|
180
182
|
}
|
|
181
183
|
|
|
182
|
-
/**
|
|
183
|
-
|
|
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 {
|
|
184
200
|
return (
|
|
185
201
|
typeof value === "object" &&
|
|
186
202
|
value !== null &&
|
|
@@ -202,7 +218,7 @@ function isIntervalLiteral(value: unknown): value is IntervalValue {
|
|
|
202
218
|
*/
|
|
203
219
|
function handleLiteral(closed: ClosedRoster, value: unknown): LiteralSpec {
|
|
204
220
|
if (typeof value !== "string") {
|
|
205
|
-
throw literalShapeError(`a ${closed.name} handle name (string)`, value)
|
|
221
|
+
throw literalShapeError("selection literal", `a ${closed.name} handle name (string)`, value)
|
|
206
222
|
}
|
|
207
223
|
if (!closed.handles.includes(value)) {
|
|
208
224
|
throw errors.new(`"${value}" is not a handle of ${closed.name} — the roster is ${closed.handles.join(", ")}`)
|
|
@@ -212,8 +228,8 @@ function handleLiteral(closed: ClosedRoster, value: unknown): LiteralSpec {
|
|
|
212
228
|
|
|
213
229
|
/** Lowers one interval literal at its element type. */
|
|
214
230
|
function intervalLiteral(element: "u64" | "i64", value: unknown): LiteralSpec {
|
|
215
|
-
if (!
|
|
216
|
-
throw literalShapeError("interval ({ start, end } bigints)", value)
|
|
231
|
+
if (!isIntervalValue(value)) {
|
|
232
|
+
throw literalShapeError("selection literal", "interval ({ start, end } bigints)", value)
|
|
217
233
|
}
|
|
218
234
|
if (element === "u64") {
|
|
219
235
|
return { kind: "value", value: { kind: "intervalU64", start: value.start, end: value.end } }
|
|
@@ -306,37 +322,50 @@ function interval(element: U64Field | I64Field, width?: bigint): IntervalField<"
|
|
|
306
322
|
* else lowers to a plain value tagged by the field's structural kind.
|
|
307
323
|
*/
|
|
308
324
|
function literalOf(field: AnyField, value: unknown): LiteralSpec {
|
|
309
|
-
|
|
310
|
-
|
|
325
|
+
const roster = rosterOf(field)
|
|
326
|
+
if (roster !== undefined) {
|
|
327
|
+
return handleLiteral(roster, value)
|
|
311
328
|
}
|
|
312
329
|
switch (field.kind) {
|
|
313
330
|
case "bool": {
|
|
314
331
|
if (typeof value !== "boolean") {
|
|
315
|
-
throw literalShapeError("boolean", value)
|
|
332
|
+
throw literalShapeError("selection literal", "boolean", value)
|
|
316
333
|
}
|
|
317
334
|
return { kind: "value", value: { kind: "bool", value } }
|
|
318
335
|
}
|
|
319
336
|
case "u64": {
|
|
320
337
|
if (typeof value !== "bigint") {
|
|
321
|
-
throw literalShapeError("bigint", value)
|
|
338
|
+
throw literalShapeError("selection literal", "bigint", value)
|
|
322
339
|
}
|
|
323
340
|
return { kind: "value", value: { kind: "u64", value } }
|
|
324
341
|
}
|
|
325
342
|
case "i64": {
|
|
326
343
|
if (typeof value !== "bigint") {
|
|
327
|
-
throw literalShapeError("bigint", value)
|
|
344
|
+
throw literalShapeError("selection literal", "bigint", value)
|
|
328
345
|
}
|
|
329
346
|
return { kind: "value", value: { kind: "i64", value } }
|
|
330
347
|
}
|
|
331
348
|
case "str": {
|
|
332
349
|
if (typeof value !== "string") {
|
|
333
|
-
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)
|
|
334
363
|
}
|
|
335
364
|
return { kind: "value", value: { kind: "string", value } }
|
|
336
365
|
}
|
|
337
366
|
case "bytes": {
|
|
338
367
|
if (!(value instanceof Uint8Array)) {
|
|
339
|
-
throw literalShapeError("Uint8Array", value)
|
|
368
|
+
throw literalShapeError("selection literal", "Uint8Array", value)
|
|
340
369
|
}
|
|
341
370
|
return { kind: "value", value: { kind: "fixedBytes", value } }
|
|
342
371
|
}
|
|
@@ -359,4 +388,17 @@ export type {
|
|
|
359
388
|
StrField,
|
|
360
389
|
U64Field
|
|
361
390
|
}
|
|
362
|
-
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
|
@@ -8,16 +8,21 @@
|
|
|
8
8
|
* runtime (path-cached stores, transactions, typed violations, scoped
|
|
9
9
|
* snapshot reads, the witnessed write loop with `abandon` — PRD-07, zero
|
|
10
10
|
* closables), the query surface (Datalog as values, kysely-shaped:
|
|
11
|
-
* `query(S).rule(r => r.match(
|
|
12
|
-
*
|
|
11
|
+
* `query(S).rule(r => { const { id, name } = v(Holder); return r.match(Holder, { id, name }).find({ name }) })` —
|
|
12
|
+
* variables minted by `v()` and joined by OBJECT REFERENCE (reuse is the
|
|
13
|
+
* join), the head a `find` RECORD whose keys name the answer columns
|
|
14
|
+
* (renames are real), params still STRING-named, plus negation,
|
|
13
15
|
* conditions, aggregates, and stratified recursion via `program()`/`rec` —
|
|
14
16
|
* `db.prepare` as a plain value; the comparison/connective builders are
|
|
15
17
|
* also free exports, and the free names `eq`/`not`/`and`/`or` collide with
|
|
16
18
|
* common host identifiers — import aliasing is the answer; the SDK does
|
|
17
|
-
* not rename for collision-avoidance),
|
|
19
|
+
* not rename for collision-avoidance), the exhume surface
|
|
18
20
|
* (`Db.exhume` — the one schema-independent read path: the store's
|
|
19
21
|
* self-described shapes and raw facts by name, typed at bare structural
|
|
20
|
-
* values, deliberately schema-free)
|
|
22
|
+
* values, deliberately schema-free), and the answer-ordering helpers
|
|
23
|
+
* (`by`/`desc` — sort keys as data for the language's own `.sort`; answers
|
|
24
|
+
* are sets, the engine never orders, and limit is the language's own
|
|
25
|
+
* `.slice`). The raw native bridge is not exported.
|
|
21
26
|
*/
|
|
22
27
|
|
|
23
28
|
export type {
|
|
@@ -50,7 +55,7 @@ export type {
|
|
|
50
55
|
WitnessedWriteResult,
|
|
51
56
|
WriteResult
|
|
52
57
|
} from "#db.ts"
|
|
53
|
-
export { abandon, Db, ErrNewtypeMismatch } from "#db.ts"
|
|
58
|
+
export { abandon, Db, ErrNewtypeMismatch, ErrWitnessedLivelock, WITNESSED_ATTEMPT_CAP } from "#db.ts"
|
|
54
59
|
export type {
|
|
55
60
|
Exhumed,
|
|
56
61
|
ExhumedAxiom,
|
|
@@ -105,21 +110,22 @@ export type {
|
|
|
105
110
|
Staleness,
|
|
106
111
|
StatementKindTag
|
|
107
112
|
} from "#native.ts"
|
|
113
|
+
export type { Desc, SortKey } from "#order.ts"
|
|
114
|
+
export { by, desc } from "#order.ts"
|
|
108
115
|
|
|
109
116
|
export type {
|
|
110
117
|
AnyCond,
|
|
111
118
|
BindingInput,
|
|
112
119
|
Cmp,
|
|
113
|
-
|
|
114
|
-
MatchOwner,
|
|
120
|
+
FindColumn,
|
|
115
121
|
MatchShape,
|
|
116
122
|
NotAtom,
|
|
117
123
|
RecData,
|
|
118
124
|
RuleData,
|
|
119
|
-
SelectColumn,
|
|
120
125
|
Tree
|
|
121
126
|
} from "#query/atom.ts"
|
|
122
|
-
export { ALLEN, allen, and,
|
|
127
|
+
export { ALLEN, allen, and, eq, ge, gt, le, lt, ne, not, or, pointIn } from "#query/atom.ts"
|
|
128
|
+
export type { Agg, FindEntry } from "#query/find.ts"
|
|
123
129
|
export type {
|
|
124
130
|
AnyQuery,
|
|
125
131
|
AnyRuleValue,
|
|
@@ -146,20 +152,20 @@ export type {
|
|
|
146
152
|
ClassedField,
|
|
147
153
|
Duration,
|
|
148
154
|
MaskParam,
|
|
155
|
+
MatchFields,
|
|
156
|
+
MatchOwner,
|
|
149
157
|
Param,
|
|
150
158
|
ParamEntry,
|
|
151
159
|
ParamsRecord,
|
|
152
160
|
SetParam,
|
|
153
161
|
Var,
|
|
154
|
-
|
|
162
|
+
VarsOf
|
|
155
163
|
} from "#query/scope.ts"
|
|
156
|
-
export
|
|
164
|
+
export { v } from "#query/scope.ts"
|
|
157
165
|
export type {
|
|
158
166
|
AnyRelation,
|
|
159
167
|
AnySelected,
|
|
160
168
|
Fact,
|
|
161
|
-
FieldRef,
|
|
162
|
-
FieldRefs,
|
|
163
169
|
FieldsShape,
|
|
164
170
|
FreshKeys,
|
|
165
171
|
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
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
|
|
30
30
|
import * as errors from "@superbuilders/errors"
|
|
31
31
|
import type { AnyField, ClosedRoster } from "#fields.ts"
|
|
32
|
+
import { isIntervalValue, literalShapeError, rosterOf } from "#fields.ts"
|
|
32
33
|
import type { FactValue } from "#native.ts"
|
|
33
34
|
import type { AnyRelation, Fact, FreshKeys, RelationData } from "#relation.ts"
|
|
34
35
|
|
|
@@ -66,23 +67,6 @@ type KeyFact<R extends AnyRelation> = [FreshKeys<R>] extends [never]
|
|
|
66
67
|
? Partial<Fact<R>>
|
|
67
68
|
: { [K in FreshKeys<R>]: Fact<R>[K] }
|
|
68
69
|
|
|
69
|
-
/** The typed shape refusal of the row marshaler — a genuine failure, never data. */
|
|
70
|
-
function cellShapeError(context: string, expected: string, value: unknown): Error {
|
|
71
|
-
return errors.new(`${context}: expected ${expected}, got ${typeof value}`)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** Narrows an interval cell: a plain object with bigint start/end. */
|
|
75
|
-
function isIntervalCell(value: unknown): value is { readonly start: bigint; readonly end: bigint } {
|
|
76
|
-
return (
|
|
77
|
-
typeof value === "object" &&
|
|
78
|
-
value !== null &&
|
|
79
|
-
"start" in value &&
|
|
80
|
-
"end" in value &&
|
|
81
|
-
typeof value.start === "bigint" &&
|
|
82
|
-
typeof value.end === "bigint"
|
|
83
|
-
)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
70
|
/**
|
|
87
71
|
* Reprojects any host object to a string-indexed record — the boundary
|
|
88
72
|
* through which generic fact objects (whose type parameters carry no index
|
|
@@ -121,7 +105,7 @@ function closedCellOf(context: string, closed: ClosedRoster, name: string): Fact
|
|
|
121
105
|
*/
|
|
122
106
|
function handleOf(context: string, closed: ClosedRoster, cell: FactValue): string {
|
|
123
107
|
if (typeof cell !== "bigint") {
|
|
124
|
-
throw
|
|
108
|
+
throw literalShapeError(context, `a ${closed.name} handle id (bigint)`, cell)
|
|
125
109
|
}
|
|
126
110
|
const handle = closed.handles[Number(cell)]
|
|
127
111
|
if (handle === undefined) {
|
|
@@ -144,29 +128,30 @@ function handleOf(context: string, closed: ClosedRoster, cell: FactValue): strin
|
|
|
144
128
|
* boundary.
|
|
145
129
|
*/
|
|
146
130
|
function cellOf(context: string, field: AnyField, value: unknown): FactValue {
|
|
147
|
-
|
|
131
|
+
const roster = rosterOf(field)
|
|
132
|
+
if (roster !== undefined) {
|
|
148
133
|
if (typeof value !== "string") {
|
|
149
|
-
throw
|
|
134
|
+
throw literalShapeError(context, `a ${roster.name} handle name (string)`, value)
|
|
150
135
|
}
|
|
151
|
-
return closedCellOf(context,
|
|
136
|
+
return closedCellOf(context, roster, value)
|
|
152
137
|
}
|
|
153
138
|
switch (field.kind) {
|
|
154
139
|
case "bool": {
|
|
155
140
|
if (typeof value !== "boolean") {
|
|
156
|
-
throw
|
|
141
|
+
throw literalShapeError(context, "boolean", value)
|
|
157
142
|
}
|
|
158
143
|
return value
|
|
159
144
|
}
|
|
160
145
|
case "u64":
|
|
161
146
|
case "i64": {
|
|
162
147
|
if (typeof value !== "bigint") {
|
|
163
|
-
throw
|
|
148
|
+
throw literalShapeError(context, "bigint", value)
|
|
164
149
|
}
|
|
165
150
|
return value
|
|
166
151
|
}
|
|
167
152
|
case "str": {
|
|
168
153
|
if (typeof value !== "string") {
|
|
169
|
-
throw
|
|
154
|
+
throw literalShapeError(context, "string", value)
|
|
170
155
|
}
|
|
171
156
|
/**
|
|
172
157
|
* A lone surrogate would be lossily replaced with U+FFFD at the
|
|
@@ -176,19 +161,19 @@ function cellOf(context: string, field: AnyField, value: unknown): FactValue {
|
|
|
176
161
|
* lookup lowers through.
|
|
177
162
|
*/
|
|
178
163
|
if (!value.isWellFormed()) {
|
|
179
|
-
throw
|
|
164
|
+
throw literalShapeError(context, "well-formed string", value)
|
|
180
165
|
}
|
|
181
166
|
return value
|
|
182
167
|
}
|
|
183
168
|
case "bytes": {
|
|
184
169
|
if (!(value instanceof Uint8Array)) {
|
|
185
|
-
throw
|
|
170
|
+
throw literalShapeError(context, "Uint8Array", value)
|
|
186
171
|
}
|
|
187
172
|
return value
|
|
188
173
|
}
|
|
189
174
|
case "interval": {
|
|
190
|
-
if (!
|
|
191
|
-
throw
|
|
175
|
+
if (!isIntervalValue(value)) {
|
|
176
|
+
throw literalShapeError(context, "interval ({ start, end } bigints)", value)
|
|
192
177
|
}
|
|
193
178
|
return { start: value.start, end: value.end }
|
|
194
179
|
}
|
|
@@ -289,10 +274,9 @@ function factOf<R extends AnyRelation>(relation: R, row: readonly FactValue[]):
|
|
|
289
274
|
if (cell === undefined) {
|
|
290
275
|
throw errors.new(`relation ${data.name}: row cell ${ordinal} (${declared.name}) is absent`)
|
|
291
276
|
}
|
|
277
|
+
const roster = rosterOf(declared.field)
|
|
292
278
|
decoded[declared.name] =
|
|
293
|
-
|
|
294
|
-
? handleOf(`relation ${data.name} field ${declared.name}`, declared.field.closed, cell)
|
|
295
|
-
: cell
|
|
279
|
+
roster !== undefined ? handleOf(`relation ${data.name} field ${declared.name}`, roster, cell) : cell
|
|
296
280
|
})
|
|
297
281
|
Object.freeze(decoded)
|
|
298
282
|
if (!isCompleteFact(relation, decoded)) {
|
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 }
|
package/src/order.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-side answer ordering — the census-fired convenience
|
|
3
|
+
* (`docs/architecture/70-api.md` § the freeze ledger). Answers are SETS and
|
|
4
|
+
* the ENGINE NEVER ORDERS; the language owns the sort (`rows.sort(...)`)
|
|
5
|
+
* and the limit (`.slice(0, n)`) — the drizzle law. What JS lacks is a
|
|
6
|
+
* number-returning comparator over the SDK's bigint-bearing cells, so the
|
|
7
|
+
* SDK ships exactly that: sort keys are DATA — a bare column name is
|
|
8
|
+
* ascending (the punning spelling; no `asc` wrapper exists — one spelling
|
|
9
|
+
* per meaning) and `desc(name)` is the one descending spelling — folded by
|
|
10
|
+
* `by(...)` into one row-typed comparator. Cross-type cells cannot arise
|
|
11
|
+
* within one column (one column, one domain), and the cell order is TOTAL
|
|
12
|
+
* anyway (the type-rank wall), so the comparator never throws.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { FactValue } from "#native.ts"
|
|
16
|
+
|
|
17
|
+
/** One DESCENDING sort key, plain data — built by {@link desc}. */
|
|
18
|
+
interface Desc<K extends string> {
|
|
19
|
+
readonly key: K
|
|
20
|
+
readonly desc: true
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** One sort key: a bare column name (ascending — the punning spelling) or `desc(name)`. */
|
|
24
|
+
type SortKey<K extends string> = K | Desc<K>
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The type-rank wall: boolean 0, bigint 1, string 2, bytes 3, interval 4.
|
|
28
|
+
* One column carries one domain, so a mixed pair never arises from decoded
|
|
29
|
+
* answer rows — the wall exists to keep the cell order TOTAL (never a
|
|
30
|
+
* throw), not to be reached.
|
|
31
|
+
*/
|
|
32
|
+
function cellRank(value: FactValue): number {
|
|
33
|
+
if (typeof value === "boolean") {
|
|
34
|
+
return 0
|
|
35
|
+
}
|
|
36
|
+
if (typeof value === "bigint") {
|
|
37
|
+
return 1
|
|
38
|
+
}
|
|
39
|
+
if (typeof value === "string") {
|
|
40
|
+
return 2
|
|
41
|
+
}
|
|
42
|
+
if (value instanceof Uint8Array) {
|
|
43
|
+
return 3
|
|
44
|
+
}
|
|
45
|
+
return 4
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* One cell against one cell. Same-type arms: boolean orders false < true;
|
|
50
|
+
* bigint by `<`/`>`; string by the host language's own `<`/`>` (flavor,
|
|
51
|
+
* recorded); bytes bytewise over the shared prefix, then by length;
|
|
52
|
+
* intervals by start, then end. A mixed pair falls through to the
|
|
53
|
+
* type-rank wall.
|
|
54
|
+
*/
|
|
55
|
+
function cellCmp(left: FactValue, right: FactValue): number {
|
|
56
|
+
if (typeof left === "boolean" && typeof right === "boolean") {
|
|
57
|
+
if (left === right) {
|
|
58
|
+
return 0
|
|
59
|
+
}
|
|
60
|
+
if (left) {
|
|
61
|
+
return 1
|
|
62
|
+
}
|
|
63
|
+
return -1
|
|
64
|
+
}
|
|
65
|
+
if (typeof left === "bigint" && typeof right === "bigint") {
|
|
66
|
+
if (left < right) {
|
|
67
|
+
return -1
|
|
68
|
+
}
|
|
69
|
+
if (left > right) {
|
|
70
|
+
return 1
|
|
71
|
+
}
|
|
72
|
+
return 0
|
|
73
|
+
}
|
|
74
|
+
if (typeof left === "string" && typeof right === "string") {
|
|
75
|
+
if (left < right) {
|
|
76
|
+
return -1
|
|
77
|
+
}
|
|
78
|
+
if (left > right) {
|
|
79
|
+
return 1
|
|
80
|
+
}
|
|
81
|
+
return 0
|
|
82
|
+
}
|
|
83
|
+
if (left instanceof Uint8Array && right instanceof Uint8Array) {
|
|
84
|
+
const shared = Math.min(left.length, right.length)
|
|
85
|
+
for (let index = 0; index < shared; index += 1) {
|
|
86
|
+
const leftByte = left[index]
|
|
87
|
+
const rightByte = right[index]
|
|
88
|
+
// `index < shared` keeps both reads in bounds; the `undefined`
|
|
89
|
+
// arms are the checker's indexed-access tax, never taken.
|
|
90
|
+
if (leftByte !== undefined && rightByte !== undefined && leftByte !== rightByte) {
|
|
91
|
+
return leftByte - rightByte
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return left.length - right.length
|
|
95
|
+
}
|
|
96
|
+
if (
|
|
97
|
+
typeof left === "object" &&
|
|
98
|
+
!(left instanceof Uint8Array) &&
|
|
99
|
+
typeof right === "object" &&
|
|
100
|
+
!(right instanceof Uint8Array)
|
|
101
|
+
) {
|
|
102
|
+
if (left.start < right.start) {
|
|
103
|
+
return -1
|
|
104
|
+
}
|
|
105
|
+
if (left.start > right.start) {
|
|
106
|
+
return 1
|
|
107
|
+
}
|
|
108
|
+
if (left.end < right.end) {
|
|
109
|
+
return -1
|
|
110
|
+
}
|
|
111
|
+
if (left.end > right.end) {
|
|
112
|
+
return 1
|
|
113
|
+
}
|
|
114
|
+
return 0
|
|
115
|
+
}
|
|
116
|
+
return cellRank(left) - cellRank(right)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Marks one sort key DESCENDING — the single descending spelling (a bare name is already ascending). */
|
|
120
|
+
function desc<const K extends string>(key: K): Desc<K> {
|
|
121
|
+
const marker: Desc<K> = { key, desc: true }
|
|
122
|
+
return Object.freeze(marker)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Folds sort keys into ONE comparator typed against the row —
|
|
127
|
+
* `Row extends Readonly<Record<K, FactValue>>` — so a key the row lacks, or
|
|
128
|
+
* a column typed `number` (outside the cell domain), is a COMPILE error at
|
|
129
|
+
* the `.sort` call site: the laws typed the columns, and the row type
|
|
130
|
+
* carries that proof here (parse-don't-validate). The generic RETURN is the
|
|
131
|
+
* load-bearing trick: `rows.sort(by("rank"))` instantiates `Row` from the
|
|
132
|
+
* array's own element type and checks the key set right there.
|
|
133
|
+
*/
|
|
134
|
+
function by<const K extends string>(
|
|
135
|
+
first: SortKey<K>,
|
|
136
|
+
...rest: ReadonlyArray<SortKey<K>>
|
|
137
|
+
): <Row extends Readonly<Record<K, FactValue>>>(left: Row, right: Row) => number {
|
|
138
|
+
const entries = [first, ...rest].map(function normalizeKey(sortKey): { readonly key: K; readonly factor: 1 | -1 } {
|
|
139
|
+
if (typeof sortKey === "string") {
|
|
140
|
+
return { key: sortKey, factor: 1 }
|
|
141
|
+
}
|
|
142
|
+
return { key: sortKey.key, factor: -1 }
|
|
143
|
+
})
|
|
144
|
+
return function compare<Row extends Readonly<Record<K, FactValue>>>(left: Row, right: Row): number {
|
|
145
|
+
for (const entry of entries) {
|
|
146
|
+
const order = cellCmp(left[entry.key], right[entry.key]) * entry.factor
|
|
147
|
+
if (order !== 0) {
|
|
148
|
+
return order
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return 0
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export type { Desc, SortKey }
|
|
156
|
+
export { by, desc }
|