@bjornpagen/bumbledb 1.0.1 → 1.2.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 +176 -36
- package/README.md +70 -26
- package/dist/capacity.d.ts +15 -55
- package/dist/capacity.d.ts.map +1 -1
- package/dist/capacity.js +63 -12
- package/dist/capacity.js.map +1 -1
- package/dist/changes.d.ts +28 -6
- package/dist/changes.d.ts.map +1 -1
- package/dist/changes.js +119 -71
- 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 +8 -14
- package/dist/codec.d.ts.map +1 -1
- package/dist/codec.js +113 -188
- package/dist/codec.js.map +1 -1
- package/dist/compile.d.ts +17 -18
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +41 -20
- package/dist/compile.js.map +1 -1
- package/dist/db-native.d.ts +76 -41
- 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 +61 -26
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +152 -85
- 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 +6 -4
- package/dist/index.js.map +1 -1
- package/dist/internal/log.d.ts +6 -4
- package/dist/internal/log.d.ts.map +1 -1
- package/dist/internal/log.js +3 -1
- package/dist/internal/log.js.map +1 -1
- package/dist/law.d.ts +11 -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/migration.d.ts +2 -3
- package/dist/migration.d.ts.map +1 -1
- package/dist/migration.js +5 -5
- package/dist/migration.js.map +1 -1
- package/dist/native.d.ts +2 -2
- 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/lower.d.ts +60 -4
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +126 -161
- 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 +3 -4
- 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/result.d.ts +10 -25
- package/dist/result.d.ts.map +1 -1
- package/dist/result.js +7 -12
- package/dist/result.js.map +1 -1
- package/dist/rows.d.ts +11 -14
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +49 -237
- package/dist/rows.js.map +1 -1
- package/dist/runtime-codes.d.ts +1 -1
- package/dist/runtime-codes.d.ts.map +1 -1
- package/dist/runtime-codes.js +1 -2
- package/dist/runtime-codes.js.map +1 -1
- package/dist/runtime-errors.d.ts +27 -15
- package/dist/runtime-errors.d.ts.map +1 -1
- package/dist/runtime-errors.js +44 -8
- package/dist/runtime-errors.js.map +1 -1
- package/dist/runtime-native.d.ts +16 -34
- package/dist/runtime-native.d.ts.map +1 -1
- package/dist/runtime-native.js.map +1 -1
- package/dist/runtime.d.ts +11 -28
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +14 -54
- package/dist/runtime.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 +6 -9
- 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 +86 -76
- package/src/changes.ts +225 -94
- package/src/closed.ts +161 -314
- package/src/codec.ts +123 -213
- package/src/compile.ts +61 -38
- package/src/db-native.ts +73 -68
- package/src/db.ts +268 -165
- 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 +38 -31
- package/src/internal/log.ts +4 -5
- package/src/law.ts +14 -14
- package/src/lower.ts +13 -10
- package/src/migration.ts +5 -12
- package/src/native.ts +2 -1
- package/src/query/atom.ts +1 -0
- package/src/query/description.ts +394 -0
- package/src/query/lower.ts +139 -173
- package/src/query/parse-ir.ts +247 -54
- package/src/query/run.ts +15 -9
- package/src/query/scope.ts +10 -18
- package/src/relation.ts +58 -142
- package/src/result.ts +16 -45
- package/src/rows.ts +57 -250
- package/src/runtime-codes.ts +1 -2
- package/src/runtime-errors.ts +48 -8
- package/src/runtime-native.ts +13 -46
- package/src/runtime.ts +51 -119
- package/src/schema.ts +99 -29
- package/src/selection.ts +124 -0
- package/src/shape.ts +9 -9
- package/src/statements.ts +138 -189
- package/src/values.ts +223 -0
package/src/codec.ts
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { Effect, Schema as EffectSchema, Result } from "effect"
|
|
2
|
+
import { membersAgree } from "#closed.ts"
|
|
2
3
|
import { schemaTables } from "#compile.ts"
|
|
3
4
|
import { dbNative } from "#db-native.ts"
|
|
5
|
+
import { AuthoringError } from "#errors.ts"
|
|
4
6
|
import type { AnyField } from "#fields.ts"
|
|
5
|
-
import { isFloatIntervalValue, isIntervalValue, rosterOf } from "#fields.ts"
|
|
6
7
|
import { lower } from "#lower.ts"
|
|
7
|
-
import type
|
|
8
|
+
import { type AnyRelation, type Fact, relationDescriptor, relationFields } from "#relation.ts"
|
|
8
9
|
import type { CellValue } from "#rows.ts"
|
|
9
10
|
import { factOfCells, flatRowsOf } from "#rows.ts"
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
12
|
-
import { DbError } from "#runtime-errors.ts"
|
|
11
|
+
import { nativeOperationWith, runtimeHandle } from "#runtime.ts"
|
|
12
|
+
import { argumentError, DbError } from "#runtime-errors.ts"
|
|
13
13
|
import type { AnySchema } from "#schema.ts"
|
|
14
|
+
import { schemaDescriptor } from "#schema.ts"
|
|
14
15
|
import type { Rel } from "#shape.ts"
|
|
15
16
|
import { f64BitsHex } from "#spec.ts"
|
|
16
|
-
import {
|
|
17
|
+
import { fieldValue, recordValue } from "#values.ts"
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Boundary row codecs, derived from the core relation descriptors — never a
|
|
@@ -22,7 +23,7 @@ import { Uuid } from "#uuid.ts"
|
|
|
22
23
|
* Two layers:
|
|
23
24
|
*
|
|
24
25
|
* 1. The CANONICAL native row codec: `encodeRows`/`decodeRows` run the
|
|
25
|
-
* same
|
|
26
|
+
* same native implementation the engine, log and migrations
|
|
26
27
|
* share (owned bytes in, owned typed rows out; untrusted input cannot
|
|
27
28
|
* inject native capabilities). Effect-only, `NativeRuntime` required.
|
|
28
29
|
* 2. The schema-tagged JSON VALUE form for HTTP/export boundaries
|
|
@@ -44,14 +45,27 @@ interface RowShape<R extends AnyRelation> {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
function rowShape<S extends AnySchema, R extends Rel<S>>(schema: S, relation: R): RowShape<R> {
|
|
47
|
-
|
|
48
|
+
const ownedSchema = schemaDescriptor(schema)
|
|
49
|
+
const ownedRelation = relationDescriptor(relation)
|
|
50
|
+
return Object.freeze({ schema: ownedSchema, relation: ownedRelation })
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function checkedRowShape<R extends AnyRelation>(operation: string, input: RowShape<R>) {
|
|
54
|
+
recordValue(`${operation} shape`, input, ["schema", "relation"])
|
|
55
|
+
const schema = schemaDescriptor(input.schema)
|
|
56
|
+
const relation = relationDescriptor(input.relation)
|
|
57
|
+
const relationId = schemaTables(schema).relationIds.get(relation.name)
|
|
58
|
+
if (relationId === undefined || !membersAgree(schema.relations[relation.name], relation)) {
|
|
59
|
+
throw new AuthoringError({ message: `${operation}: relation is not declared in the schema` })
|
|
60
|
+
}
|
|
61
|
+
return { relation, relationId, spec: lower(schema) }
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
function invalid(operation: string): DbError {
|
|
51
65
|
return new DbError({ operation, reason: { _tag: "InvalidArgument" } })
|
|
52
66
|
}
|
|
53
67
|
|
|
54
|
-
const DECIMAL =
|
|
68
|
+
const DECIMAL = /^(?:0|-?[1-9][0-9]*)$/
|
|
55
69
|
const HEX16 = /^[0-9a-f]{16}$/
|
|
56
70
|
const LOWER_HEX = /^(?:[0-9a-f]{2})*$/
|
|
57
71
|
|
|
@@ -89,146 +103,71 @@ function f64OfHex(text: unknown): number | undefined {
|
|
|
89
103
|
return value
|
|
90
104
|
}
|
|
91
105
|
|
|
92
|
-
/** One host value to its
|
|
93
|
-
function encodeBoundaryValue(field: AnyField,
|
|
94
|
-
|
|
95
|
-
if (roster !== undefined) {
|
|
96
|
-
if (typeof value !== "string" || !roster.handles.includes(value)) {
|
|
97
|
-
throw invalid("encodeBoundaryRows")
|
|
98
|
-
}
|
|
99
|
-
return value
|
|
100
|
-
}
|
|
106
|
+
/** One host value to its canonical, field-directed JSON representation. */
|
|
107
|
+
function encodeBoundaryValue(context: string, field: AnyField, input: unknown): unknown {
|
|
108
|
+
if ("closed" in field) return fieldValue(context, field, input)
|
|
101
109
|
switch (field.kind) {
|
|
102
|
-
case "bool":
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return value
|
|
107
|
-
}
|
|
110
|
+
case "bool":
|
|
111
|
+
case "str":
|
|
112
|
+
case "uuid":
|
|
113
|
+
return fieldValue(context, field, input)
|
|
108
114
|
case "u64":
|
|
109
|
-
case "i64":
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
case "f64": {
|
|
116
|
-
if (typeof value !== "number") {
|
|
117
|
-
throw invalid("encodeBoundaryRows")
|
|
118
|
-
}
|
|
119
|
-
return { $f64: f64BitsHex(value) }
|
|
120
|
-
}
|
|
121
|
-
case "uuid": {
|
|
122
|
-
if (!Uuid.isUuid(value)) {
|
|
123
|
-
throw invalid("encodeBoundaryRows")
|
|
124
|
-
}
|
|
125
|
-
return value
|
|
126
|
-
}
|
|
127
|
-
case "str": {
|
|
128
|
-
if (typeof value !== "string" || !value.isWellFormed()) {
|
|
129
|
-
throw invalid("encodeBoundaryRows")
|
|
130
|
-
}
|
|
131
|
-
return value
|
|
132
|
-
}
|
|
133
|
-
case "bytes": {
|
|
134
|
-
if (!(value instanceof Uint8Array) || value.byteLength !== field.width) {
|
|
135
|
-
throw invalid("encodeBoundaryRows")
|
|
136
|
-
}
|
|
137
|
-
return hexOfBytes(value)
|
|
138
|
-
}
|
|
115
|
+
case "i64":
|
|
116
|
+
return fieldValue(context, field, input).toString(10)
|
|
117
|
+
case "f64":
|
|
118
|
+
return { $f64: f64BitsHex(fieldValue(context, field, input)) }
|
|
119
|
+
case "bytes":
|
|
120
|
+
return hexOfBytes(fieldValue(context, field, input))
|
|
139
121
|
case "interval": {
|
|
140
122
|
if (field.element === "f64") {
|
|
141
|
-
|
|
142
|
-
throw invalid("encodeBoundaryRows")
|
|
143
|
-
}
|
|
123
|
+
const value = fieldValue(context, { ...field, element: "f64" }, input)
|
|
144
124
|
return { start: { $f64: f64BitsHex(value.start) }, end: { $f64: f64BitsHex(value.end) } }
|
|
145
125
|
}
|
|
146
|
-
|
|
147
|
-
throw invalid("encodeBoundaryRows")
|
|
148
|
-
}
|
|
126
|
+
const value = fieldValue(context, { ...field, element: field.element }, input)
|
|
149
127
|
return { start: value.start.toString(10), end: value.end.toString(10) }
|
|
150
128
|
}
|
|
151
129
|
}
|
|
152
130
|
}
|
|
153
131
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
function decodeInteger(kind: "u64" | "i64", value: unknown): bigint | undefined {
|
|
159
|
-
if (typeof value !== "string" || !DECIMAL.test(value)) {
|
|
160
|
-
return undefined
|
|
161
|
-
}
|
|
162
|
-
const parsed = BigInt(value)
|
|
163
|
-
if (kind === "u64" && (parsed < 0n || parsed > U64_MAX)) {
|
|
164
|
-
return undefined
|
|
165
|
-
}
|
|
166
|
-
if (kind === "i64" && (parsed < I64_MIN || parsed > I64_MAX)) {
|
|
167
|
-
return undefined
|
|
168
|
-
}
|
|
169
|
-
// Canonical decimal only (no leading zeros, no "-0") — DECIMAL enforces.
|
|
170
|
-
return parsed
|
|
132
|
+
function decodeInteger(value: unknown): bigint | undefined {
|
|
133
|
+
// No accepted integer spelling is longer than 20 ASCII characters.
|
|
134
|
+
if (typeof value !== "string" || value.length > 20 || !DECIMAL.test(value)) return undefined
|
|
135
|
+
return BigInt(value)
|
|
171
136
|
}
|
|
172
137
|
|
|
173
138
|
function decodeTaggedF64(value: unknown): number | undefined {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
const keys = Object.keys(value)
|
|
178
|
-
if (keys.length !== 1) {
|
|
179
|
-
return undefined
|
|
180
|
-
}
|
|
181
|
-
return f64OfHex((value as { readonly $f64: unknown }).$f64)
|
|
139
|
+
const record = recordValue("binary64 image", value, ["$f64"])
|
|
140
|
+
return f64OfHex(record.$f64)
|
|
182
141
|
}
|
|
183
142
|
|
|
184
|
-
/**
|
|
185
|
-
function decodeBoundaryValue(field: AnyField,
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
return typeof value === "string" && roster.handles.includes(value) ? value : undefined
|
|
189
|
-
}
|
|
143
|
+
/** Decode representation, then judge the value through the shared interpreter. */
|
|
144
|
+
function decodeBoundaryValue(context: string, field: AnyField, input: unknown): unknown {
|
|
145
|
+
if ("closed" in field) return fieldValue(context, field, input)
|
|
146
|
+
let decoded: unknown = input
|
|
190
147
|
switch (field.kind) {
|
|
191
|
-
case "bool":
|
|
192
|
-
return typeof value === "boolean" ? value : undefined
|
|
193
148
|
case "u64":
|
|
194
149
|
case "i64":
|
|
195
|
-
|
|
150
|
+
decoded = decodeInteger(input)
|
|
151
|
+
break
|
|
196
152
|
case "f64":
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
case "bytes": {
|
|
203
|
-
if (typeof value !== "string" || value.length !== field.width * 2) {
|
|
204
|
-
return undefined
|
|
205
|
-
}
|
|
206
|
-
return bytesOfHex(value)
|
|
207
|
-
}
|
|
153
|
+
decoded = decodeTaggedF64(input)
|
|
154
|
+
break
|
|
155
|
+
case "bytes":
|
|
156
|
+
decoded = typeof input === "string" && input.length === field.width * 2 ? bytesOfHex(input) : undefined
|
|
157
|
+
break
|
|
208
158
|
case "interval": {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const end = decodeTaggedF64(raw.end)
|
|
216
|
-
if (start === undefined || end === undefined || Number.isNaN(start) || Number.isNaN(end) || !(start < end)) {
|
|
217
|
-
return undefined
|
|
218
|
-
}
|
|
219
|
-
return Object.freeze({ start, end })
|
|
220
|
-
}
|
|
221
|
-
const start = decodeInteger(field.element, raw.start)
|
|
222
|
-
const end = decodeInteger(field.element, raw.end)
|
|
223
|
-
if (start === undefined || end === undefined || start >= end) {
|
|
224
|
-
return undefined
|
|
225
|
-
}
|
|
226
|
-
return Object.freeze({ start, end })
|
|
159
|
+
const record = recordValue(context, input, ["start", "end"])
|
|
160
|
+
decoded =
|
|
161
|
+
field.element === "f64"
|
|
162
|
+
? { start: decodeTaggedF64(record.start), end: decodeTaggedF64(record.end) }
|
|
163
|
+
: { start: decodeInteger(record.start), end: decodeInteger(record.end) }
|
|
164
|
+
break
|
|
227
165
|
}
|
|
228
166
|
}
|
|
167
|
+
return fieldValue(context, field, decoded)
|
|
229
168
|
}
|
|
230
169
|
|
|
231
|
-
/** Pure schema-tagged JSON encoding of
|
|
170
|
+
/** Pure schema-tagged JSON encoding of complete rows. */
|
|
232
171
|
function encodeBoundaryRows<R extends AnyRelation>(
|
|
233
172
|
relation: R,
|
|
234
173
|
rows: Iterable<Fact<R>>
|
|
@@ -236,83 +175,73 @@ function encodeBoundaryRows<R extends AnyRelation>(
|
|
|
236
175
|
return Result.try({
|
|
237
176
|
try: () => {
|
|
238
177
|
const out: Array<Readonly<Record<string, unknown>>> = []
|
|
178
|
+
const fields = relationFields(relation)
|
|
179
|
+
const names = fields.map((declared) => declared.name)
|
|
239
180
|
for (const row of rows) {
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
181
|
+
const input = recordValue(`relation ${relation.name}`, row, names)
|
|
182
|
+
out.push(
|
|
183
|
+
Object.freeze(
|
|
184
|
+
Object.fromEntries(
|
|
185
|
+
fields.map((declared) => [
|
|
186
|
+
declared.name,
|
|
187
|
+
encodeBoundaryValue(`relation ${relation.name}.${declared.name}`, declared.field, input[declared.name])
|
|
188
|
+
])
|
|
189
|
+
)
|
|
245
190
|
)
|
|
246
|
-
|
|
247
|
-
out.push(Object.freeze(record))
|
|
191
|
+
)
|
|
248
192
|
}
|
|
249
193
|
return Object.freeze(out)
|
|
250
194
|
},
|
|
251
|
-
catch: (cause) => (
|
|
195
|
+
catch: (cause) => argumentError("encodeBoundaryRows", cause)
|
|
252
196
|
})
|
|
253
197
|
}
|
|
254
198
|
|
|
255
|
-
/** Pure strict decoding
|
|
199
|
+
/** Pure strict decoding; malformed records, getters and values fail as data. */
|
|
256
200
|
function decodeBoundaryRows<R extends AnyRelation>(
|
|
257
201
|
relation: R,
|
|
258
202
|
input: unknown
|
|
259
203
|
): Result.Result<ReadonlyArray<Fact<R>>, DbError> {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
out.push(Object.freeze(record) as Fact<R>)
|
|
283
|
-
}
|
|
284
|
-
return Result.succeed(Object.freeze(out))
|
|
204
|
+
return Result.try({
|
|
205
|
+
try: () => {
|
|
206
|
+
if (!Array.isArray(input)) throw invalid("decodeBoundaryRows")
|
|
207
|
+
const fields = relationFields(relation)
|
|
208
|
+
const names = fields.map((declared) => declared.name)
|
|
209
|
+
return Object.freeze(
|
|
210
|
+
input.map((row) => {
|
|
211
|
+
const raw = recordValue(`relation ${relation.name}`, row, names)
|
|
212
|
+
return Object.freeze(
|
|
213
|
+
Object.fromEntries(
|
|
214
|
+
fields.map((declared) => [
|
|
215
|
+
declared.name,
|
|
216
|
+
decodeBoundaryValue(`relation ${relation.name}.${declared.name}`, declared.field, raw[declared.name])
|
|
217
|
+
])
|
|
218
|
+
)
|
|
219
|
+
) as Fact<R>
|
|
220
|
+
})
|
|
221
|
+
)
|
|
222
|
+
},
|
|
223
|
+
catch: (cause) => argumentError("decodeBoundaryRows", cause)
|
|
224
|
+
})
|
|
285
225
|
}
|
|
286
226
|
|
|
287
|
-
/**
|
|
288
|
-
* An Effect Schema for one relation's typed row, DERIVED from the core
|
|
289
|
-
* descriptors (a validation-only declaration; the canonical `$f64`/decimal
|
|
290
|
-
* wire form stays this module's explicit codec — Effect Schema's generic
|
|
291
|
-
* JSON number encoding is not Bumbledb's `$f64` codec).
|
|
292
|
-
*/
|
|
227
|
+
/** An Effect Schema derived from the same complete-row value interpreter. */
|
|
293
228
|
function rowSchema<R extends AnyRelation>(relation: R) {
|
|
229
|
+
const fields = relationFields(relation)
|
|
230
|
+
const names = fields.map((declared) => declared.name)
|
|
294
231
|
return EffectSchema.declare((value: unknown): value is Fact<R> => {
|
|
295
|
-
|
|
232
|
+
try {
|
|
233
|
+
const record = recordValue(`relation ${relation.name}`, value, names)
|
|
234
|
+
for (const declared of fields)
|
|
235
|
+
fieldValue(`relation ${relation.name}.${declared.name}`, declared.field, record[declared.name])
|
|
236
|
+
return true
|
|
237
|
+
} catch {
|
|
296
238
|
return false
|
|
297
239
|
}
|
|
298
|
-
const record = value as Readonly<Record<string, unknown>>
|
|
299
|
-
return relation.data.fields.every(function checkField(declared) {
|
|
300
|
-
const cell = record[declared.name]
|
|
301
|
-
if (cell === undefined) {
|
|
302
|
-
return false
|
|
303
|
-
}
|
|
304
|
-
return Result.isSuccess(
|
|
305
|
-
Result.try({
|
|
306
|
-
try: () => encodeBoundaryValue(declared.field, cell),
|
|
307
|
-
catch: () => invalid("rowSchema")
|
|
308
|
-
})
|
|
309
|
-
)
|
|
310
|
-
})
|
|
311
240
|
})
|
|
312
241
|
}
|
|
313
242
|
|
|
314
243
|
/**
|
|
315
|
-
* The
|
|
244
|
+
* The canonical native row codec (chapter 35 roster): owned bytes
|
|
316
245
|
* out, owned typed rows back in — the same implementation log sealing and
|
|
317
246
|
* migrations use. Binding parameters is ingestion: input must stay stable
|
|
318
247
|
* through execution, and no native work starts before the checked owned
|
|
@@ -320,58 +249,39 @@ function rowSchema<R extends AnyRelation>(relation: R) {
|
|
|
320
249
|
*/
|
|
321
250
|
const encodeRows = Effect.fn("encodeRows")(function* <R extends AnyRelation>(
|
|
322
251
|
shape: RowShape<R>,
|
|
323
|
-
rows: Iterable<Fact<R
|
|
324
|
-
work: ExecutionPolicy
|
|
252
|
+
rows: Iterable<Fact<R>>
|
|
325
253
|
) {
|
|
326
254
|
const runtime = yield* runtimeHandle()
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
255
|
+
const { relation, relationId, spec } = yield* Effect.try({
|
|
256
|
+
try: () => checkedRowShape("encodeRows", shape),
|
|
257
|
+
catch: (cause) => argumentError("encodeRows", cause)
|
|
258
|
+
})
|
|
332
259
|
const flat = yield* Effect.try({
|
|
333
|
-
try: () => flatRowsOf(
|
|
334
|
-
catch: (cause) => (
|
|
260
|
+
try: () => flatRowsOf(relation, rows as Iterable<object>),
|
|
261
|
+
catch: (cause) => argumentError("encodeRows", cause)
|
|
335
262
|
})
|
|
336
|
-
const spec = lower(shape.schema)
|
|
337
263
|
return yield* nativeOperationWith(
|
|
338
264
|
"encodeRows",
|
|
339
|
-
(callback) =>
|
|
340
|
-
dbNative.runtimeEncodeRows(
|
|
341
|
-
runtime,
|
|
342
|
-
policyWire(work, "encodeRows"),
|
|
343
|
-
spec,
|
|
344
|
-
relationId,
|
|
345
|
-
flat.rows,
|
|
346
|
-
flat.cells,
|
|
347
|
-
callback
|
|
348
|
-
),
|
|
265
|
+
(callback) => dbNative.runtimeEncodeRows(runtime, spec, relationId, flat.rows, flat.cells, callback),
|
|
349
266
|
dbNative.runtimeBytesTake,
|
|
350
267
|
(bytes) => bytes
|
|
351
268
|
)
|
|
352
269
|
})
|
|
353
270
|
|
|
354
|
-
const decodeRows = Effect.fn("decodeRows")(function* <R extends AnyRelation>(
|
|
355
|
-
shape: RowShape<R>,
|
|
356
|
-
input: Uint8Array,
|
|
357
|
-
work: ExecutionPolicy
|
|
358
|
-
) {
|
|
271
|
+
const decodeRows = Effect.fn("decodeRows")(function* <R extends AnyRelation>(shape: RowShape<R>, input: Uint8Array) {
|
|
359
272
|
const runtime = yield* runtimeHandle()
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}
|
|
273
|
+
const { relation, relationId, spec } = yield* Effect.try({
|
|
274
|
+
try: () => checkedRowShape("decodeRows", shape),
|
|
275
|
+
catch: (cause) => argumentError("decodeRows", cause)
|
|
276
|
+
})
|
|
365
277
|
if (!(input instanceof Uint8Array) || !(input.buffer instanceof ArrayBuffer)) {
|
|
366
278
|
return yield* Effect.fail(invalid("decodeRows"))
|
|
367
279
|
}
|
|
368
|
-
const spec = lower(shape.schema)
|
|
369
280
|
return yield* nativeOperationWith(
|
|
370
281
|
"decodeRows",
|
|
371
|
-
(callback) =>
|
|
372
|
-
dbNative.runtimeDecodeRows(runtime, policyWire(work, "decodeRows"), spec, relationId, input, callback),
|
|
282
|
+
(callback) => dbNative.runtimeDecodeRows(runtime, spec, relationId, input, callback),
|
|
373
283
|
dbNative.runtimeRowsTake,
|
|
374
|
-
(rows) => Object.freeze(rows.map((row) => factOfCells(
|
|
284
|
+
(rows) => Object.freeze(rows.map((row) => factOfCells(relation, row as readonly CellValue[])))
|
|
375
285
|
)
|
|
376
286
|
})
|
|
377
287
|
|
package/src/compile.ts
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import { Effect } from "effect"
|
|
2
|
-
import { isClosedMember } from "#closed.ts"
|
|
2
|
+
import { isClosedMember, membersAgree } from "#closed.ts"
|
|
3
3
|
import { dbNative } from "#db-native.ts"
|
|
4
4
|
import { SdkInvariantError } from "#errors.ts"
|
|
5
|
+
import { isImmutable, snapshotData } from "#immutable.ts"
|
|
5
6
|
import type { SchemaClasses } from "#law.ts"
|
|
6
7
|
import { lower } from "#lower.ts"
|
|
7
8
|
import type { SealedDescriptor } from "#native.ts"
|
|
8
|
-
import type {
|
|
9
|
-
import { nativeOperationWith,
|
|
9
|
+
import type { AnyRelation } from "#relation.ts"
|
|
10
|
+
import { nativeOperationWith, runtimeHandle } from "#runtime.ts"
|
|
11
|
+
import { argumentError } from "#runtime-errors.ts"
|
|
10
12
|
import type { AnySchema, Schema as SchemaDeclaration, SchemaRelations } from "#schema.ts"
|
|
11
|
-
import
|
|
13
|
+
import { schemaDescriptor } from "#schema.ts"
|
|
14
|
+
import { type KeyStatement, type Statement, statementDescriptor } from "#statements.ts"
|
|
12
15
|
|
|
13
16
|
/**
|
|
14
17
|
* `SchemaId` — the engine's canonical schema fingerprint as lowercase hex.
|
|
15
|
-
* Independent of database identity;
|
|
16
|
-
*
|
|
18
|
+
* Independent of database identity; ordinary structural text. Native
|
|
19
|
+
* boundaries verify the fingerprint wherever it grants admission.
|
|
17
20
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
type SchemaId = string & { readonly [schemaIdBrand]: "bumbledb.SchemaId" }
|
|
21
|
+
type SchemaId = string
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* `CompiledSchema<S>` — bounded detached immutable descriptor data plus the
|
|
@@ -37,44 +38,35 @@ interface CompiledSchema<S extends AnySchema> {
|
|
|
37
38
|
* ids (declaration order = ids), materialized statement ids (closed
|
|
38
39
|
* relations' auto-handle keys FIRST in relation-declaration order, then
|
|
39
40
|
* declared statements in declaration order with `mirrors` occupying two
|
|
40
|
-
* consecutive slots, source-first — the theory's `StatementId` law)
|
|
41
|
-
*
|
|
42
|
-
*
|
|
41
|
+
* consecutive slots, source-first — the theory's `StatementId` law).
|
|
42
|
+
* Every declared statement retains its own native identity; no key is
|
|
43
|
+
* privileged by its position in the declaration.
|
|
43
44
|
*/
|
|
44
|
-
interface PrimaryKey {
|
|
45
|
-
readonly statementId: number
|
|
46
|
-
readonly projection: readonly string[]
|
|
47
|
-
}
|
|
48
|
-
|
|
49
45
|
interface SchemaTables {
|
|
50
46
|
readonly relationIds: ReadonlyMap<string, number>
|
|
51
|
-
readonly
|
|
47
|
+
readonly statementIds: ReadonlyMap<Statement, number>
|
|
52
48
|
}
|
|
53
49
|
|
|
54
50
|
function declaredWidth(statement: Statement): number {
|
|
55
|
-
return statement.
|
|
51
|
+
return statement.kind === "mirrors" ? 2 : 1
|
|
56
52
|
}
|
|
57
53
|
|
|
58
54
|
function tablesOf(theory: AnySchema): SchemaTables {
|
|
59
55
|
const relationIds = new Map<string, number>()
|
|
60
|
-
const
|
|
56
|
+
const statementIds = new Map<Statement, number>()
|
|
61
57
|
let autoKeys = 0
|
|
62
58
|
Object.entries(theory.relations).forEach(function assignRelation([name, member], ordinal) {
|
|
63
59
|
relationIds.set(name, ordinal)
|
|
64
60
|
if (isClosedMember(member)) {
|
|
65
|
-
primaryKeys.set(name, Object.freeze({ statementId: autoKeys, projection: Object.freeze(["id"]) }))
|
|
66
61
|
autoKeys += 1
|
|
67
62
|
}
|
|
68
63
|
})
|
|
69
64
|
let offset = autoKeys
|
|
70
65
|
for (const statement of theory.statements) {
|
|
71
|
-
|
|
72
|
-
if (data.kind === "key" && !primaryKeys.has(data.owner.name)) {
|
|
73
|
-
primaryKeys.set(data.owner.name, Object.freeze({ statementId: offset, projection: data.projection }))
|
|
74
|
-
}
|
|
66
|
+
statementIds.set(statement, offset)
|
|
75
67
|
offset += declaredWidth(statement)
|
|
76
68
|
}
|
|
77
|
-
return Object.freeze({ relationIds,
|
|
69
|
+
return Object.freeze({ relationIds, statementIds })
|
|
78
70
|
}
|
|
79
71
|
|
|
80
72
|
const compiledCache = new WeakMap<AnySchema, SchemaTables>()
|
|
@@ -86,38 +78,69 @@ function schemaTables(theory: AnySchema): SchemaTables {
|
|
|
86
78
|
return cached
|
|
87
79
|
}
|
|
88
80
|
const built = tablesOf(theory)
|
|
89
|
-
compiledCache.set(theory, built)
|
|
81
|
+
if (isImmutable(theory)) compiledCache.set(theory, built)
|
|
90
82
|
return built
|
|
91
83
|
}
|
|
92
84
|
|
|
85
|
+
/** Resolve a logical key declaration, never an opaque constructor token. */
|
|
86
|
+
function declaredKey<R extends AnyRelation>(
|
|
87
|
+
theory: AnySchema,
|
|
88
|
+
input: KeyStatement<R>
|
|
89
|
+
): { readonly key: KeyStatement<R>; readonly statementId: number } | undefined
|
|
90
|
+
function declaredKey(
|
|
91
|
+
theory: AnySchema,
|
|
92
|
+
input: KeyStatement
|
|
93
|
+
): { readonly key: KeyStatement; readonly statementId: number } | undefined {
|
|
94
|
+
const key = statementDescriptor(input)
|
|
95
|
+
for (const [candidate, statementId] of schemaTables(theory).statementIds) {
|
|
96
|
+
if (
|
|
97
|
+
candidate.kind === "key" &&
|
|
98
|
+
membersAgree(candidate.owner, key.owner) &&
|
|
99
|
+
candidate.projection.length === key.projection.length &&
|
|
100
|
+
candidate.projection.every((field) => key.projection.includes(field))
|
|
101
|
+
)
|
|
102
|
+
return { key: candidate, statementId }
|
|
103
|
+
}
|
|
104
|
+
return undefined
|
|
105
|
+
}
|
|
106
|
+
|
|
93
107
|
function admitSchemaId(fingerprint: string): SchemaId {
|
|
94
|
-
if (typeof fingerprint !== "string" || fingerprint
|
|
108
|
+
if (typeof fingerprint !== "string" || !/^[0-9a-f]{64}$/.test(fingerprint)) {
|
|
95
109
|
throw new SdkInvariantError({ message: "Schema.compile: the engine returned no canonical fingerprint" })
|
|
96
110
|
}
|
|
97
|
-
return fingerprint
|
|
111
|
+
return fingerprint
|
|
98
112
|
}
|
|
99
113
|
|
|
100
114
|
/**
|
|
101
|
-
*
|
|
115
|
+
* Effectful native schema admission/compilation: a
|
|
102
116
|
* schema declaration is pure metadata and never a claim its theory has been
|
|
103
|
-
* admitted. Runs on the
|
|
117
|
+
* admitted. Runs on the shared executor, requires the
|
|
104
118
|
* acquired {@link NativeRuntime}, and yields detached immutable descriptor
|
|
105
119
|
* data plus the canonical schema identity. No database is opened and no
|
|
106
120
|
* native finalizer is created.
|
|
107
121
|
*/
|
|
108
|
-
const compile = Effect.fn("Schema.compile")(function* <S extends AnySchema>(schema: S
|
|
122
|
+
const compile = Effect.fn("Schema.compile")(function* <S extends AnySchema>(schema: S) {
|
|
123
|
+
const owned = yield* Effect.try({
|
|
124
|
+
try: () => schemaDescriptor(schema),
|
|
125
|
+
catch: (cause) => argumentError("Schema.compile", cause)
|
|
126
|
+
})
|
|
127
|
+
const spec = yield* Effect.try({ try: () => lower(owned), catch: (cause) => argumentError("Schema.compile", cause) })
|
|
109
128
|
const handle = yield* runtimeHandle()
|
|
110
|
-
const spec = lower(schema)
|
|
111
129
|
const descriptor = yield* nativeOperationWith(
|
|
112
130
|
"Schema.compile",
|
|
113
|
-
(callback) => dbNative.runtimeSchemaCompile(handle,
|
|
131
|
+
(callback) => dbNative.runtimeSchemaCompile(handle, spec, callback),
|
|
114
132
|
dbNative.runtimeSchemaTake,
|
|
115
133
|
(value) => value
|
|
116
134
|
)
|
|
135
|
+
const ownedDescriptor = snapshotData(descriptor)
|
|
117
136
|
const compiled: CompiledSchema<S> = Object.freeze({
|
|
118
|
-
schema
|
|
137
|
+
get schema() {
|
|
138
|
+
return snapshotData(owned)
|
|
139
|
+
},
|
|
119
140
|
schemaId: admitSchemaId(descriptor.fingerprint),
|
|
120
|
-
descriptor
|
|
141
|
+
get descriptor() {
|
|
142
|
+
return snapshotData(ownedDescriptor)
|
|
143
|
+
}
|
|
121
144
|
})
|
|
122
145
|
return compiled
|
|
123
146
|
})
|
|
@@ -144,5 +167,5 @@ type Schema<Rels extends SchemaRelations, Classes extends SchemaClasses = Schema
|
|
|
144
167
|
*/
|
|
145
168
|
const Schema = Object.freeze({ compile })
|
|
146
169
|
|
|
147
|
-
export type { CompiledSchema,
|
|
148
|
-
export { Schema, schemaTables }
|
|
170
|
+
export type { CompiledSchema, SchemaId, SchemaTables }
|
|
171
|
+
export { declaredKey, Schema, schemaTables }
|