@bjornpagen/bumbledb 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COOKBOOK.md +114 -11
- package/README.md +52 -9
- package/dist/capacity.d.ts +22 -59
- package/dist/capacity.d.ts.map +1 -1
- package/dist/capacity.js +66 -13
- package/dist/capacity.js.map +1 -1
- package/dist/changes.d.ts +26 -3
- package/dist/changes.d.ts.map +1 -1
- package/dist/changes.js +89 -44
- package/dist/changes.js.map +1 -1
- package/dist/closed.d.ts +23 -51
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +110 -168
- package/dist/closed.js.map +1 -1
- package/dist/codec.d.ts +4 -9
- package/dist/codec.d.ts.map +1 -1
- package/dist/codec.js +108 -183
- package/dist/codec.js.map +1 -1
- package/dist/compile.d.ts +16 -16
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +36 -15
- package/dist/compile.js.map +1 -1
- package/dist/db-native.d.ts +39 -0
- package/dist/db-native.d.ts.map +1 -1
- package/dist/db-native.js +8 -0
- package/dist/db-native.js.map +1 -1
- package/dist/db.d.ts +28 -8
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +91 -38
- package/dist/db.js.map +1 -1
- package/dist/errors.d.ts +11 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +13 -1
- package/dist/errors.js.map +1 -1
- package/dist/face.d.ts +19 -25
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +30 -24
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +13 -26
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +89 -116
- package/dist/fields.js.map +1 -1
- package/dist/immutable.d.ts +10 -0
- package/dist/immutable.d.ts.map +1 -0
- package/dist/immutable.js +75 -0
- package/dist/immutable.js.map +1 -0
- package/dist/index.d.ts +16 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/internal/log.d.ts +2 -0
- package/dist/internal/log.d.ts.map +1 -1
- package/dist/internal/log.js +2 -0
- package/dist/internal/log.js.map +1 -1
- package/dist/law.d.ts +13 -11
- package/dist/law.d.ts.map +1 -1
- package/dist/law.js +1 -1
- package/dist/law.js.map +1 -1
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +11 -6
- package/dist/lower.js.map +1 -1
- package/dist/native.d.ts +1 -1
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +1 -1
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js.map +1 -1
- package/dist/query/description.d.ts +33 -0
- package/dist/query/description.d.ts.map +1 -0
- package/dist/query/description.js +302 -0
- package/dist/query/description.js.map +1 -0
- package/dist/query/find.d.ts +21 -6
- package/dist/query/find.d.ts.map +1 -1
- package/dist/query/find.js.map +1 -1
- package/dist/query/lower.d.ts +75 -10
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +132 -163
- package/dist/query/lower.js.map +1 -1
- package/dist/query/parse-ir.d.ts +7 -2
- package/dist/query/parse-ir.d.ts.map +1 -1
- package/dist/query/parse-ir.js +240 -53
- package/dist/query/parse-ir.js.map +1 -1
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +8 -10
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +24 -16
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +5 -10
- package/dist/query/scope.js.map +1 -1
- package/dist/relation.d.ts +22 -48
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +30 -82
- package/dist/relation.js.map +1 -1
- package/dist/rows.d.ts +7 -4
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +44 -198
- package/dist/rows.js.map +1 -1
- package/dist/runtime-errors.d.ts +26 -2
- package/dist/runtime-errors.d.ts.map +1 -1
- package/dist/runtime-errors.js +43 -3
- package/dist/runtime-errors.js.map +1 -1
- package/dist/schema.d.ts +9 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +85 -24
- package/dist/schema.js.map +1 -1
- package/dist/selection.d.ts +27 -0
- package/dist/selection.d.ts.map +1 -0
- package/dist/selection.js +104 -0
- package/dist/selection.js.map +1 -0
- package/dist/shape.d.ts +12 -12
- package/dist/shape.d.ts.map +1 -1
- package/dist/statements.d.ts +17 -52
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +92 -111
- package/dist/statements.js.map +1 -1
- package/dist/values.d.ts +25 -0
- package/dist/values.d.ts.map +1 -0
- package/dist/values.js +203 -0
- package/dist/values.js.map +1 -0
- package/pack-provenance.json +3 -3
- package/package.json +4 -4
- package/src/capacity.ts +93 -80
- package/src/changes.ts +183 -54
- package/src/closed.ts +161 -314
- package/src/codec.ts +116 -190
- package/src/compile.ts +56 -32
- package/src/db-native.ts +42 -0
- package/src/db.ts +134 -52
- package/src/errors.ts +16 -1
- package/src/face.ts +44 -74
- package/src/fields.ts +100 -146
- package/src/immutable.ts +76 -0
- package/src/index.ts +36 -29
- package/src/internal/log.ts +2 -0
- package/src/law.ts +21 -18
- package/src/lower.ts +13 -10
- package/src/native.ts +2 -0
- package/src/query/atom.ts +1 -0
- package/src/query/description.ts +394 -0
- package/src/query/find.ts +20 -7
- package/src/query/lower.ts +177 -187
- package/src/query/parse-ir.ts +247 -54
- package/src/query/run.ts +15 -9
- package/src/query/scope.ts +42 -33
- package/src/relation.ts +58 -142
- package/src/rows.ts +62 -205
- package/src/runtime-errors.ts +47 -3
- package/src/schema.ts +99 -29
- package/src/selection.ts +124 -0
- package/src/shape.ts +18 -12
- package/src/statements.ts +138 -189
- package/src/values.ts +223 -0
package/src/relation.ts
CHANGED
|
@@ -1,160 +1,76 @@
|
|
|
1
1
|
import { AuthoringError } from "#errors.ts"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import { type LiteralSetSpec, type LiteralSpec, renderLiteral } from "#spec.ts"
|
|
18
|
-
|
|
19
|
-
function resolveEntry(context: string, field: AnyField, entry: unknown): LiteralSetSpec {
|
|
20
|
-
if (Array.isArray(entry)) {
|
|
21
|
-
if (entry.length < 2) {
|
|
22
|
-
throw new AuthoringError({
|
|
23
|
-
message:
|
|
24
|
-
entry.length === 0
|
|
25
|
-
? `${context}: an empty literal set selects nothing — write the selection you mean`
|
|
26
|
-
: `${context}: a one-element literal set is the bare literal respelled — write the literal (the canonical-utterance law: one meaning, one spelling)`
|
|
27
|
-
})
|
|
28
|
-
}
|
|
29
|
-
const seen = new Set<string>()
|
|
30
|
-
const literals: LiteralSpec[] = entry.map(function lowerSetLiteral(literal: unknown) {
|
|
31
|
-
const lowered = Object.freeze(literalOf(field, literal))
|
|
32
|
-
const rendered = renderLiteral(lowered)
|
|
33
|
-
if (seen.has(rendered)) {
|
|
34
|
-
throw new AuthoringError({
|
|
35
|
-
message: `${context}: the literal set spells ${rendered} twice — write it once (the canonical-utterance law: one meaning, one spelling)`
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
seen.add(rendered)
|
|
39
|
-
return lowered
|
|
40
|
-
})
|
|
41
|
-
return Object.freeze({ kind: "many", literals: Object.freeze(literals) })
|
|
42
|
-
}
|
|
43
|
-
return Object.freeze({ kind: "one", literal: Object.freeze(literalOf(field, entry)) })
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function resolveSelection(
|
|
47
|
-
name: string,
|
|
48
|
-
ordered: readonly RelationField[],
|
|
49
|
-
entries: ReadonlyArray<readonly [string, unknown]>
|
|
50
|
-
): readonly SelectionBinding[] {
|
|
51
|
-
const bindings: SelectionBinding[] = []
|
|
52
|
-
for (const [fieldName, entry] of entries) {
|
|
53
|
-
if (entry === undefined) {
|
|
54
|
-
continue
|
|
55
|
-
}
|
|
56
|
-
const declared = ordered.find(function byName(candidate) {
|
|
57
|
-
return candidate.name === fieldName
|
|
58
|
-
})
|
|
59
|
-
if (declared === undefined) {
|
|
60
|
-
throw new AuthoringError({ message: `relation ${name} has no field ${fieldName}` })
|
|
61
|
-
}
|
|
62
|
-
bindings.push(
|
|
63
|
-
Object.freeze({ field: fieldName, set: resolveEntry(`relation ${name}.${fieldName}`, declared.field, entry) })
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
if (bindings.length === 0) {
|
|
67
|
-
throw new AuthoringError({
|
|
68
|
-
message: `relation ${name}: an empty selection is the bare relation respelled — pass the relation itself (the canonical-utterance law: one meaning, one spelling)`
|
|
69
|
-
})
|
|
70
|
-
}
|
|
71
|
-
return Object.freeze(bindings)
|
|
2
|
+
import {
|
|
3
|
+
type AnyField,
|
|
4
|
+
assertDeclarationOrderKey,
|
|
5
|
+
assertDeclarationRecord,
|
|
6
|
+
fieldDescriptor,
|
|
7
|
+
type Infer
|
|
8
|
+
} from "#fields.ts"
|
|
9
|
+
import { descriptorCache } from "#immutable.ts"
|
|
10
|
+
import { recordValue } from "#values.ts"
|
|
11
|
+
|
|
12
|
+
/** Ordinary relations are structural declarations. Field order defines native ordinals. */
|
|
13
|
+
interface Relation<Name extends string = string, Fields extends FieldsShape = FieldsShape> {
|
|
14
|
+
readonly kind: "relation"
|
|
15
|
+
readonly name: Name
|
|
16
|
+
readonly fields: Fields
|
|
72
17
|
}
|
|
73
|
-
|
|
74
|
-
type
|
|
18
|
+
type FieldsShape = Readonly<Record<string, AnyField>>
|
|
19
|
+
type AnyRelation = Relation
|
|
20
|
+
type RelationFields<R extends AnyRelation> = R["fields"]
|
|
21
|
+
type Fact<R extends AnyRelation> = { -readonly [K in keyof R["fields"]]: Infer<R["fields"][K]> }
|
|
75
22
|
|
|
76
23
|
interface RelationField {
|
|
77
24
|
readonly name: string
|
|
78
25
|
readonly field: AnyField
|
|
79
26
|
}
|
|
80
27
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
readonly relation: Relation<Name, Fields>
|
|
97
|
-
readonly selection: readonly SelectionBinding[]
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
interface Relation<Name extends string, Fields extends FieldsShape> {
|
|
101
|
-
readonly name: Name
|
|
102
|
-
readonly data: RelationData
|
|
103
|
-
where(selection: SelectionInput<Fields>): Selected<Name, Fields>
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
type AnyRelation = Relation<string, FieldsShape>
|
|
107
|
-
|
|
108
|
-
interface AnySelected {
|
|
109
|
-
readonly relation: AnyRelation
|
|
110
|
-
readonly selection: readonly SelectionBinding[]
|
|
28
|
+
/** Validate and own every field, without freezing or retaining caller records. */
|
|
29
|
+
function ownFields<F extends FieldsShape>(context: string, input: F): F
|
|
30
|
+
function ownFields(context: string, input: unknown): FieldsShape
|
|
31
|
+
function ownFields(context: string, input: unknown): FieldsShape {
|
|
32
|
+
if (typeof input !== "object" || input === null)
|
|
33
|
+
throw new AuthoringError({ message: `${context}: expected a field record` })
|
|
34
|
+
assertDeclarationRecord(context, input)
|
|
35
|
+
return Object.freeze(
|
|
36
|
+
Object.fromEntries(
|
|
37
|
+
Object.entries(input).map(([name, field]) => {
|
|
38
|
+
assertDeclarationOrderKey(context, name)
|
|
39
|
+
return [name, fieldDescriptor(`${context}.${name}`, field)]
|
|
40
|
+
})
|
|
41
|
+
)
|
|
42
|
+
)
|
|
111
43
|
}
|
|
112
44
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
45
|
+
function relationDescriptor<R extends AnyRelation>(input: R): R
|
|
46
|
+
function relationDescriptor(input: unknown): AnyRelation
|
|
47
|
+
function relationDescriptor(input: unknown): AnyRelation {
|
|
48
|
+
return checkedRelation(input)
|
|
117
49
|
}
|
|
118
50
|
|
|
119
|
-
|
|
51
|
+
const checkedRelation = descriptorCache((input): AnyRelation => {
|
|
52
|
+
const raw = recordValue("relation", input, ["kind", "name", "fields"])
|
|
53
|
+
if (raw.kind !== "relation" || typeof raw.name !== "string")
|
|
54
|
+
throw new AuthoringError({ message: "relation: expected a relation declaration" })
|
|
55
|
+
assertDeclarationOrderKey("relation", raw.name)
|
|
56
|
+
return Object.freeze({
|
|
57
|
+
kind: "relation",
|
|
58
|
+
name: raw.name,
|
|
59
|
+
fields: ownFields(`relation ${raw.name} fields`, raw.fields)
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
function relation<const Name extends string, const Fields extends FieldsShape>(
|
|
120
64
|
name: Name,
|
|
121
65
|
fields: Fields
|
|
122
66
|
): Relation<Name, Fields> {
|
|
123
|
-
|
|
124
|
-
assertDeclarationRecord(`relation ${name} fields`, fields)
|
|
125
|
-
const ordered: RelationField[] = []
|
|
126
|
-
for (const [fieldName, field] of Object.entries(fields)) {
|
|
127
|
-
assertDeclarationOrderKey(`relation ${name} field`, fieldName)
|
|
128
|
-
ordered.push(Object.freeze({ name: fieldName, field }))
|
|
129
|
-
}
|
|
130
|
-
const data: RelationData = Object.freeze({ name, fields: Object.freeze(ordered) })
|
|
131
|
-
const holder: { value: Relation<Name, Fields> | undefined } = { value: undefined }
|
|
132
|
-
function where(selection: SelectionInput<Fields>): Selected<Name, Fields> {
|
|
133
|
-
const owner = holder.value
|
|
134
|
-
if (owner === undefined) {
|
|
135
|
-
throw new AuthoringError({ message: `relation ${name}: self-reference read before construction completed` })
|
|
136
|
-
}
|
|
137
|
-
return Object.freeze({
|
|
138
|
-
relation: owner,
|
|
139
|
-
selection: resolveSelection(name, ordered, Object.entries(selection))
|
|
140
|
-
})
|
|
141
|
-
}
|
|
142
|
-
const value: Relation<Name, Fields> = Object.freeze({ name, data, where })
|
|
143
|
-
holder.value = value
|
|
144
|
-
return value
|
|
67
|
+
return relationDescriptor({ kind: "relation", name, fields })
|
|
145
68
|
}
|
|
146
69
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
Fact,
|
|
151
|
-
FieldsShape,
|
|
152
|
-
Relation,
|
|
153
|
-
RelationData,
|
|
154
|
-
RelationField,
|
|
155
|
-
RelationFields,
|
|
156
|
-
Selected,
|
|
157
|
-
SelectionBinding,
|
|
158
|
-
SelectionInput
|
|
70
|
+
/** Ordered field view derived from the one declaration record. */
|
|
71
|
+
function relationFields(relation: AnyRelation): readonly RelationField[] {
|
|
72
|
+
return Object.entries(relation.fields).map(([name, field]) => ({ name, field }))
|
|
159
73
|
}
|
|
160
|
-
|
|
74
|
+
|
|
75
|
+
export type { AnyRelation, Fact, FieldsShape, Relation, RelationField, RelationFields }
|
|
76
|
+
export { ownFields, relation, relationDescriptor, relationFields }
|
package/src/rows.ts
CHANGED
|
@@ -17,9 +17,9 @@ import { AuthoringError, SdkInvariantError } from "#errors.ts"
|
|
|
17
17
|
* scans and byte copies.
|
|
18
18
|
*/
|
|
19
19
|
import type { AnyClosedRoster, AnyField } from "#fields.ts"
|
|
20
|
-
import {
|
|
21
|
-
import type
|
|
22
|
-
import {
|
|
20
|
+
import { literalShapeError, rosterOf } from "#fields.ts"
|
|
21
|
+
import { type AnyRelation, type Fact, relationFields } from "#relation.ts"
|
|
22
|
+
import { fieldValue, recordValue } from "#values.ts"
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* One owned cell at the private bridge boundary. The declared sealed field
|
|
@@ -44,20 +44,12 @@ interface FlatRows {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
function recordOf(fact: object): Readonly<Record<string, unknown>> {
|
|
47
|
-
if (typeof fact !== "object"
|
|
48
|
-
throw new AuthoringError({ message: "
|
|
47
|
+
if (typeof fact !== "object" || fact === null) {
|
|
48
|
+
throw new AuthoringError({ message: "expected a fact record" })
|
|
49
49
|
}
|
|
50
50
|
return fact as Readonly<Record<string, unknown>>
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
function refuseShared(context: string, value: Uint8Array): void {
|
|
54
|
-
if (!(value.buffer instanceof ArrayBuffer)) {
|
|
55
|
-
throw new AuthoringError({
|
|
56
|
-
message: `${context}: SharedArrayBuffer-backed views are refused — make a synchronized stable copy into ordinary unshared input first`
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
53
|
/**
|
|
62
54
|
* Approximate payload size for host batching, not a memory allowance.
|
|
63
55
|
* Strings use two bytes per UTF-16 code unit, not their UTF-8 wire size.
|
|
@@ -81,26 +73,11 @@ function hostCellCharge(value: unknown): bigint {
|
|
|
81
73
|
return 0n
|
|
82
74
|
}
|
|
83
75
|
|
|
84
|
-
/**
|
|
85
|
-
* The write half of the closed bijection: one handle NAME to its u64 row
|
|
86
|
-
* id (declaration order = row ids). An unknown name is a pointed refusal
|
|
87
|
-
* naming the vocabulary and its roster.
|
|
88
|
-
*/
|
|
89
|
-
function closedCellOf(context: string, closed: AnyClosedRoster, name: string): CellValue {
|
|
90
|
-
const id = closed.handles.indexOf(name)
|
|
91
|
-
if (id === -1) {
|
|
92
|
-
throw new AuthoringError({
|
|
93
|
-
message: `${context}: "${name}" is not a handle of ${closed.name} — the roster is ${closed.handles.join(", ")}`
|
|
94
|
-
})
|
|
95
|
-
}
|
|
96
|
-
return BigInt(id)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
76
|
function handleOf(context: string, closed: AnyClosedRoster, cell: unknown): string {
|
|
100
77
|
if (typeof cell !== "bigint") {
|
|
101
78
|
throw literalShapeError(context, `a ${closed.name} handle id (bigint)`, cell)
|
|
102
79
|
}
|
|
103
|
-
const handle = closed.handles[Number(cell)]
|
|
80
|
+
const handle = cell < 0n || cell >= BigInt(closed.handles.length) ? undefined : closed.handles[Number(cell)]
|
|
104
81
|
if (handle === undefined) {
|
|
105
82
|
throw new AuthoringError({
|
|
106
83
|
message: `${context}: id ${cell} is outside the ${closed.name} roster (${closed.handles.join(", ")})`
|
|
@@ -110,86 +87,21 @@ function handleOf(context: string, closed: AnyClosedRoster, cell: unknown): stri
|
|
|
110
87
|
}
|
|
111
88
|
|
|
112
89
|
function cellOf(context: string, field: AnyField, value: unknown): CellValue {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (typeof value !== "string") {
|
|
116
|
-
throw literalShapeError(context, `a ${roster.name} handle name (string)`, value)
|
|
117
|
-
}
|
|
118
|
-
return closedCellOf(context, roster, value)
|
|
119
|
-
}
|
|
120
|
-
switch (field.kind) {
|
|
121
|
-
case "bool": {
|
|
122
|
-
if (typeof value !== "boolean") {
|
|
123
|
-
throw literalShapeError(context, "boolean", value)
|
|
124
|
-
}
|
|
125
|
-
return value
|
|
126
|
-
}
|
|
127
|
-
case "u64":
|
|
128
|
-
case "i64": {
|
|
129
|
-
if (typeof value !== "bigint") {
|
|
130
|
-
throw literalShapeError(context, "bigint", value)
|
|
131
|
-
}
|
|
132
|
-
return value
|
|
133
|
-
}
|
|
134
|
-
case "str": {
|
|
135
|
-
if (typeof value !== "string") {
|
|
136
|
-
throw literalShapeError(context, "string", value)
|
|
137
|
-
}
|
|
138
|
-
if (!value.isWellFormed()) {
|
|
139
|
-
throw literalShapeError(context, "well-formed string", value)
|
|
140
|
-
}
|
|
141
|
-
return value
|
|
142
|
-
}
|
|
143
|
-
case "f64": {
|
|
144
|
-
if (typeof value !== "number") {
|
|
145
|
-
throw literalShapeError(context, "number", value)
|
|
146
|
-
}
|
|
147
|
-
return value
|
|
148
|
-
}
|
|
149
|
-
case "uuid": {
|
|
150
|
-
if (!Uuid.isUuid(value)) {
|
|
151
|
-
throw literalShapeError(context, "a UUID (canonical UUID text)", value)
|
|
152
|
-
}
|
|
153
|
-
// UUIDs cross as canonical hyphenated text, not raw bytes.
|
|
154
|
-
return value
|
|
155
|
-
}
|
|
156
|
-
case "bytes": {
|
|
157
|
-
if (!(value instanceof Uint8Array)) {
|
|
158
|
-
throw literalShapeError(context, "Uint8Array", value)
|
|
159
|
-
}
|
|
160
|
-
refuseShared(context, value)
|
|
161
|
-
if (value.byteLength !== field.width) {
|
|
162
|
-
throw new AuthoringError({
|
|
163
|
-
message: `${context}: bytes<${field.width}> takes exactly ${field.width} bytes (got ${value.byteLength})`
|
|
164
|
-
})
|
|
165
|
-
}
|
|
166
|
-
// Owned copy at the ownership boundary: later caller mutation
|
|
167
|
-
// cannot change an accepted cell.
|
|
168
|
-
return Uint8Array.from(value)
|
|
169
|
-
}
|
|
170
|
-
case "interval": {
|
|
171
|
-
if (field.element === "f64") {
|
|
172
|
-
if (!isFloatIntervalValue(value)) {
|
|
173
|
-
throw literalShapeError(context, "float interval ({ start, end } numbers)", value)
|
|
174
|
-
}
|
|
175
|
-
if (Number.isNaN(value.start) || Number.isNaN(value.end)) {
|
|
176
|
-
throw new AuthoringError({ message: `${context}: a float interval endpoint cannot be NaN` })
|
|
177
|
-
}
|
|
178
|
-
const start = Object.is(value.start, -0) ? 0 : value.start
|
|
179
|
-
const end = Object.is(value.end, -0) ? 0 : value.end
|
|
180
|
-
if (!(start < end)) {
|
|
181
|
-
throw new AuthoringError({
|
|
182
|
-
message: `${context}: a float interval is half-open and nonempty (start < end strictly)`
|
|
183
|
-
})
|
|
184
|
-
}
|
|
185
|
-
return { start, end }
|
|
186
|
-
}
|
|
187
|
-
if (!isIntervalValue(value)) {
|
|
188
|
-
throw literalShapeError(context, "interval ({ start, end } bigints)", value)
|
|
189
|
-
}
|
|
190
|
-
return { start: value.start, end: value.end }
|
|
191
|
-
}
|
|
90
|
+
if ("closed" in field) {
|
|
91
|
+
return BigInt(field.closed.handles.indexOf(fieldValue(context, field, value)))
|
|
192
92
|
}
|
|
93
|
+
return fieldValue(context, field, value)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function factCellsOf(data: AnyRelation, fact: unknown): CellValue[] {
|
|
97
|
+
const record = recordValue(
|
|
98
|
+
`relation ${data.name}`,
|
|
99
|
+
fact,
|
|
100
|
+
relationFields(data).map((declared) => declared.name)
|
|
101
|
+
)
|
|
102
|
+
return relationFields(data).map((declared) =>
|
|
103
|
+
cellOf(`relation ${data.name} field ${declared.name}`, declared.field, record[declared.name])
|
|
104
|
+
)
|
|
193
105
|
}
|
|
194
106
|
|
|
195
107
|
/**
|
|
@@ -198,115 +110,39 @@ function cellOf(context: string, field: AnyField, value: unknown): CellValue {
|
|
|
198
110
|
* Missing-field refusal and per-cell judgment are
|
|
199
111
|
* {@link cellOf}'s, byte for byte.
|
|
200
112
|
*/
|
|
201
|
-
function flatRowsOf(data:
|
|
113
|
+
function flatRowsOf(data: AnyRelation, facts: Iterable<object>): FlatRows {
|
|
202
114
|
const cells: CellValue[] = []
|
|
203
115
|
let rows = 0n
|
|
204
116
|
for (const fact of facts) {
|
|
117
|
+
for (const cell of factCellsOf(data, fact)) cells.push(cell)
|
|
205
118
|
rows += 1n
|
|
206
|
-
const record = recordOf(fact)
|
|
207
|
-
for (const declared of data.fields) {
|
|
208
|
-
const value = record[declared.name]
|
|
209
|
-
if (value === undefined) {
|
|
210
|
-
throw new AuthoringError({ message: `relation ${data.name}: fact is missing field ${declared.name}` })
|
|
211
|
-
}
|
|
212
|
-
const cell = cellOf(`relation ${data.name} field ${declared.name}`, declared.field, value)
|
|
213
|
-
cells.push(cell)
|
|
214
|
-
}
|
|
215
119
|
}
|
|
216
120
|
return { rows, cells }
|
|
217
121
|
}
|
|
218
122
|
|
|
219
123
|
function keyCellsOf(
|
|
220
|
-
data:
|
|
124
|
+
data: AnyRelation,
|
|
221
125
|
projection: readonly string[],
|
|
222
|
-
|
|
126
|
+
input: Readonly<Record<string, unknown>>
|
|
223
127
|
): CellValue[] {
|
|
224
|
-
|
|
225
|
-
if (key[supplied] !== undefined && !projection.includes(supplied)) {
|
|
226
|
-
throw new AuthoringError({
|
|
227
|
-
message: `relation ${data.name}: key object carries field ${supplied} outside the primary key projection (${projection.join(", ")})`
|
|
228
|
-
})
|
|
229
|
-
}
|
|
230
|
-
}
|
|
128
|
+
const key = recordValue(`relation ${data.name} key`, input, projection)
|
|
231
129
|
return projection.map(function marshalKeyCell(fieldName) {
|
|
232
|
-
const declared = data.
|
|
233
|
-
return candidate.name === fieldName
|
|
234
|
-
})
|
|
130
|
+
const declared = relationFields(data).find((candidate) => candidate.name === fieldName)
|
|
235
131
|
if (declared === undefined) {
|
|
236
|
-
throw new AuthoringError({
|
|
237
|
-
message: `relation ${data.name}: key projection cites unknown field ${fieldName}`
|
|
238
|
-
})
|
|
132
|
+
throw new AuthoringError({ message: `relation ${data.name}: key projection cites unknown field ${fieldName}` })
|
|
239
133
|
}
|
|
240
|
-
|
|
241
|
-
if (value === undefined) {
|
|
242
|
-
throw new AuthoringError({
|
|
243
|
-
message: `relation ${data.name}: key object is missing field ${fieldName} — get reads through the primary (first-declared) key, whose projection is (${projection.join(", ")})`
|
|
244
|
-
})
|
|
245
|
-
}
|
|
246
|
-
return cellOf(`relation ${data.name} key field ${fieldName}`, declared.field, value)
|
|
134
|
+
return cellOf(`relation ${data.name} key field ${fieldName}`, declared.field, key[fieldName])
|
|
247
135
|
})
|
|
248
136
|
}
|
|
249
137
|
|
|
250
138
|
function decodeCell(context: string, field: AnyField, cell: unknown): unknown {
|
|
251
139
|
const roster = rosterOf(field)
|
|
252
|
-
if (roster !== undefined)
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
throw new SdkInvariantError({ message: `${context}: expected boolean cell` })
|
|
259
|
-
}
|
|
260
|
-
return cell
|
|
261
|
-
}
|
|
262
|
-
case "u64":
|
|
263
|
-
case "i64": {
|
|
264
|
-
if (typeof cell !== "bigint") {
|
|
265
|
-
throw new SdkInvariantError({ message: `${context}: expected bigint cell` })
|
|
266
|
-
}
|
|
267
|
-
return cell
|
|
268
|
-
}
|
|
269
|
-
case "f64": {
|
|
270
|
-
if (typeof cell !== "number") {
|
|
271
|
-
throw new SdkInvariantError({ message: `${context}: expected number cell` })
|
|
272
|
-
}
|
|
273
|
-
return cell
|
|
274
|
-
}
|
|
275
|
-
case "str": {
|
|
276
|
-
if (typeof cell !== "string") {
|
|
277
|
-
throw new SdkInvariantError({ message: `${context}: expected string cell` })
|
|
278
|
-
}
|
|
279
|
-
return cell
|
|
280
|
-
}
|
|
281
|
-
case "uuid": {
|
|
282
|
-
// The native renderer spells uuid as the canonical
|
|
283
|
-
// canonical hyphenated UUID string (marshal.rs `uuid_hex`); the read
|
|
284
|
-
// side revalidates the spelling and keeps the string value.
|
|
285
|
-
if (!Uuid.isUuid(cell)) {
|
|
286
|
-
throw new SdkInvariantError({
|
|
287
|
-
message: `${context}: expected an uuid cell (canonical UUID text)`
|
|
288
|
-
})
|
|
289
|
-
}
|
|
290
|
-
return cell
|
|
291
|
-
}
|
|
292
|
-
case "bytes": {
|
|
293
|
-
if (!(cell instanceof Uint8Array)) {
|
|
294
|
-
throw new SdkInvariantError({ message: `${context}: expected owned bytes` })
|
|
295
|
-
}
|
|
296
|
-
return cell
|
|
297
|
-
}
|
|
298
|
-
case "interval": {
|
|
299
|
-
if (field.element === "f64") {
|
|
300
|
-
if (!isFloatIntervalValue(cell)) {
|
|
301
|
-
throw new SdkInvariantError({ message: `${context}: expected a float interval cell` })
|
|
302
|
-
}
|
|
303
|
-
return Object.freeze({ start: cell.start, end: cell.end })
|
|
304
|
-
}
|
|
305
|
-
if (!isIntervalValue(cell)) {
|
|
306
|
-
throw new SdkInvariantError({ message: `${context}: expected an integer interval cell` })
|
|
307
|
-
}
|
|
308
|
-
return Object.freeze({ start: cell.start, end: cell.end })
|
|
309
|
-
}
|
|
140
|
+
if (roster !== undefined) return handleOf(context, roster, cell)
|
|
141
|
+
try {
|
|
142
|
+
return fieldValue(context, field, cell)
|
|
143
|
+
} catch (cause) {
|
|
144
|
+
if (cause instanceof AuthoringError) throw new SdkInvariantError({ message: cause.message })
|
|
145
|
+
throw cause
|
|
310
146
|
}
|
|
311
147
|
}
|
|
312
148
|
|
|
@@ -314,7 +150,7 @@ function isCompleteFact<R extends AnyRelation>(
|
|
|
314
150
|
relation: R,
|
|
315
151
|
decoded: Readonly<Record<string, unknown>>
|
|
316
152
|
): decoded is Readonly<Record<string, unknown>> & Fact<R> {
|
|
317
|
-
return relation.
|
|
153
|
+
return relationFields(relation).every(function present(declared) {
|
|
318
154
|
return decoded[declared.name] !== undefined
|
|
319
155
|
})
|
|
320
156
|
}
|
|
@@ -324,21 +160,22 @@ function isCompleteFact<R extends AnyRelation>(
|
|
|
324
160
|
* declared field order: stable field names and shapes, no Proxy or per-cell closures.
|
|
325
161
|
*/
|
|
326
162
|
function factOfCells<R extends AnyRelation>(relation: R, row: readonly unknown[]): Fact<R> {
|
|
327
|
-
const data = relation
|
|
328
|
-
if (row.length !== data.
|
|
163
|
+
const data = relation
|
|
164
|
+
if (row.length !== relationFields(data).length) {
|
|
329
165
|
throw new SdkInvariantError({
|
|
330
|
-
message: `relation ${data.name}: row arity ${row.length} does not match the ${data.
|
|
166
|
+
message: `relation ${data.name}: row arity ${row.length} does not match the ${relationFields(data).length} declared fields`
|
|
331
167
|
})
|
|
332
168
|
}
|
|
333
169
|
const decoded: Record<string, unknown> = {}
|
|
334
|
-
data.
|
|
170
|
+
relationFields(data).forEach(function decodeOne(declared, ordinal) {
|
|
335
171
|
const cell = row[ordinal]
|
|
336
172
|
if (cell === undefined) {
|
|
337
173
|
throw new SdkInvariantError({
|
|
338
174
|
message: `relation ${data.name}: row cell ${ordinal} (${declared.name}) is absent`
|
|
339
175
|
})
|
|
340
176
|
}
|
|
341
|
-
|
|
177
|
+
const value = decodeCell(`relation ${data.name} field ${declared.name}`, declared.field, cell)
|
|
178
|
+
setOwnField(decoded, declared.name, value)
|
|
342
179
|
})
|
|
343
180
|
Object.freeze(decoded)
|
|
344
181
|
if (!isCompleteFact(relation, decoded)) {
|
|
@@ -347,5 +184,25 @@ function factOfCells<R extends AnyRelation>(relation: R, row: readonly unknown[]
|
|
|
347
184
|
return decoded
|
|
348
185
|
}
|
|
349
186
|
|
|
187
|
+
/** Preserve every declared name as data, including JavaScript's prototype setter. */
|
|
188
|
+
function setOwnField(record: Record<string, unknown>, name: string, value: unknown): void {
|
|
189
|
+
if (name === "__proto__") {
|
|
190
|
+
Object.defineProperty(record, name, { value, enumerable: true, writable: true, configurable: true })
|
|
191
|
+
} else {
|
|
192
|
+
record[name] = value
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
350
196
|
export type { CellValue, FlatRows }
|
|
351
|
-
export {
|
|
197
|
+
export {
|
|
198
|
+
cellOf,
|
|
199
|
+
decodeCell,
|
|
200
|
+
factCellsOf,
|
|
201
|
+
factOfCells,
|
|
202
|
+
flatRowsOf,
|
|
203
|
+
handleOf,
|
|
204
|
+
hostCellCharge,
|
|
205
|
+
keyCellsOf,
|
|
206
|
+
recordOf,
|
|
207
|
+
setOwnField
|
|
208
|
+
}
|
package/src/runtime-errors.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Schema } from "effect"
|
|
2
|
+
import { AuthoringDiagnostic, AuthoringError } from "#errors.ts"
|
|
2
3
|
import { runtimeErrorCodes } from "#runtime-codes.ts"
|
|
3
4
|
|
|
4
5
|
export { runtimeErrorCodes } from "#runtime-codes.ts"
|
|
@@ -12,7 +13,18 @@ const ResourceLimit = Schema.Struct({
|
|
|
12
13
|
})
|
|
13
14
|
const PlainReason = Schema.Struct({
|
|
14
15
|
_tag: Schema.Literals(
|
|
15
|
-
runtimeErrorCodes.filter(
|
|
16
|
+
runtimeErrorCodes.filter(
|
|
17
|
+
(code) => code !== "ResourceLimit" && code !== "Io" && code !== "Engine" && code !== "InvalidArgument"
|
|
18
|
+
)
|
|
19
|
+
)
|
|
20
|
+
})
|
|
21
|
+
const InvalidArgument = Schema.Struct({
|
|
22
|
+
_tag: Schema.Literal("InvalidArgument"),
|
|
23
|
+
detail: Schema.optional(
|
|
24
|
+
Schema.Struct({
|
|
25
|
+
message: Schema.String,
|
|
26
|
+
diagnostic: Schema.optional(AuthoringDiagnostic)
|
|
27
|
+
})
|
|
16
28
|
)
|
|
17
29
|
})
|
|
18
30
|
const Io = Schema.Struct({
|
|
@@ -26,7 +38,7 @@ const Engine = Schema.Struct({
|
|
|
26
38
|
kind: Schema.String,
|
|
27
39
|
message: Schema.String
|
|
28
40
|
})
|
|
29
|
-
export const DbReason = Schema.Union([ResourceLimit, Io, Engine, PlainReason])
|
|
41
|
+
export const DbReason = Schema.Union([ResourceLimit, Io, Engine, InvalidArgument, PlainReason])
|
|
30
42
|
export class DbError extends Schema.TaggedError<DbError>()("DbError", {
|
|
31
43
|
operation: Schema.String,
|
|
32
44
|
reason: DbReason
|
|
@@ -34,12 +46,44 @@ export class DbError extends Schema.TaggedError<DbError>()("DbError", {
|
|
|
34
46
|
get code() {
|
|
35
47
|
return this.reason._tag
|
|
36
48
|
}
|
|
49
|
+
/** Safe default display; callers explicitly inspect structured detail. */
|
|
50
|
+
override get message(): string {
|
|
51
|
+
return `${this.operation}: ${this.code}`
|
|
52
|
+
}
|
|
37
53
|
}
|
|
38
54
|
|
|
39
55
|
const decodeReason = Schema.decodeUnknownOption(DbReason)
|
|
40
56
|
export function dbError(operation: string, cause: unknown): DbError {
|
|
57
|
+
if (cause instanceof DbError) return cause
|
|
58
|
+
if (cause instanceof AuthoringError) return argumentError(operation, cause)
|
|
41
59
|
const decoded = decodeReason(cause)
|
|
42
|
-
|
|
60
|
+
if (decoded._tag === "Some") return new DbError({ operation, reason: decoded.value })
|
|
61
|
+
// Synchronous native schema/marshalling refusals precede the executor's
|
|
62
|
+
// tagged error wire. Preserve their engine family instead of losing it.
|
|
63
|
+
if (
|
|
64
|
+
typeof cause === "object" &&
|
|
65
|
+
cause !== null &&
|
|
66
|
+
"kind" in cause &&
|
|
67
|
+
typeof cause.kind === "string" &&
|
|
68
|
+
"message" in cause &&
|
|
69
|
+
typeof cause.message === "string"
|
|
70
|
+
) {
|
|
71
|
+
return new DbError({ operation, reason: { _tag: "Engine", kind: cause.kind, message: cause.message } })
|
|
72
|
+
}
|
|
73
|
+
return new DbError({ operation, reason: { _tag: "Internal" } })
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Host input failures retain authoring details without displaying values. */
|
|
77
|
+
export function argumentError(operation: string, cause: unknown): DbError {
|
|
78
|
+
if (cause instanceof DbError) return cause
|
|
79
|
+
if (cause instanceof AuthoringError) {
|
|
80
|
+
const detail =
|
|
81
|
+
cause.diagnostic === undefined
|
|
82
|
+
? { message: cause.message }
|
|
83
|
+
: { message: cause.message, diagnostic: cause.diagnostic }
|
|
84
|
+
return new DbError({ operation, reason: { _tag: "InvalidArgument", detail } })
|
|
85
|
+
}
|
|
86
|
+
return new DbError({ operation, reason: { _tag: "InvalidArgument" } })
|
|
43
87
|
}
|
|
44
88
|
|
|
45
89
|
const Outstanding = Schema.Struct({
|