@bjornpagen/bumbledb 0.20.2 → 1.0.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 +283 -1611
- package/README.md +156 -118
- package/dist/capacity.d.ts +16 -12
- package/dist/capacity.d.ts.map +1 -1
- package/dist/capacity.js +16 -48
- package/dist/capacity.js.map +1 -1
- package/dist/changes.d.ts +54 -0
- package/dist/changes.d.ts.map +1 -0
- package/dist/changes.js +245 -0
- package/dist/changes.js.map +1 -0
- package/dist/close.d.ts +24 -0
- package/dist/close.d.ts.map +1 -0
- package/dist/close.js +34 -0
- package/dist/close.js.map +1 -0
- package/dist/closed.d.ts +1 -3
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +24 -12
- package/dist/closed.js.map +1 -1
- package/dist/codec.d.ts +56 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +289 -0
- package/dist/codec.js.map +1 -0
- package/dist/compile.d.ts +68 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +75 -0
- package/dist/compile.js.map +1 -0
- package/dist/db-native.d.ts +170 -0
- package/dist/db-native.d.ts.map +1 -0
- package/dist/db-native.js +5 -0
- package/dist/db-native.js.map +1 -0
- package/dist/db.d.ts +101 -233
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +232 -1084
- package/dist/db.js.map +1 -1
- package/dist/errors.d.ts +57 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +32 -0
- package/dist/errors.js.map +1 -0
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +2 -2
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +65 -24
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +94 -24
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +37 -32
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -21
- package/dist/index.js.map +1 -1
- package/dist/internal/log.d.ts +24 -0
- package/dist/internal/log.d.ts.map +1 -0
- package/dist/internal/log.js +10 -0
- package/dist/internal/log.js.map +1 -0
- package/dist/law.d.ts +8 -13
- package/dist/law.d.ts.map +1 -1
- package/dist/law.js +6 -59
- package/dist/law.js.map +1 -1
- package/dist/lower.d.ts +0 -13
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +9 -17
- package/dist/lower.js.map +1 -1
- package/dist/migration.d.ts +33 -0
- package/dist/migration.d.ts.map +1 -0
- package/dist/migration.js +48 -0
- package/dist/migration.js.map +1 -0
- package/dist/native.d.ts +256 -301
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +65 -108
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +24 -13
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +7 -3
- package/dist/query/atom.js.map +1 -1
- package/dist/query/compute.d.ts +68 -0
- package/dist/query/compute.d.ts.map +1 -0
- package/dist/query/compute.js +141 -0
- package/dist/query/compute.js.map +1 -0
- package/dist/query/find.d.ts +15 -8
- package/dist/query/find.d.ts.map +1 -1
- package/dist/query/find.js +6 -2
- package/dist/query/find.js.map +1 -1
- package/dist/query/lower.d.ts +23 -2
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +433 -80
- package/dist/query/lower.js.map +1 -1
- package/dist/query/parse-ir.d.ts.map +1 -1
- package/dist/query/parse-ir.js +17 -8
- package/dist/query/parse-ir.js.map +1 -1
- package/dist/query/run.d.ts +12 -2
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +26 -9
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +47 -3
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +83 -19
- package/dist/query/scope.js.map +1 -1
- package/dist/relation.d.ts +3 -8
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +16 -10
- package/dist/relation.js.map +1 -1
- package/dist/result.d.ts +47 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +50 -0
- package/dist/result.js.map +1 -0
- package/dist/rows.d.ts +69 -0
- package/dist/rows.d.ts.map +1 -0
- package/dist/rows.js +328 -0
- package/dist/rows.js.map +1 -0
- package/dist/runtime-codes.d.ts +2 -0
- package/dist/runtime-codes.d.ts.map +1 -0
- package/dist/runtime-codes.js +19 -0
- package/dist/runtime-codes.js.map +1 -0
- package/dist/runtime-errors.d.ts +106 -0
- package/dist/runtime-errors.d.ts.map +1 -0
- package/dist/runtime-errors.js +62 -0
- package/dist/runtime-errors.js.map +1 -0
- package/dist/runtime-native.d.ts +171 -0
- package/dist/runtime-native.d.ts.map +1 -0
- package/dist/runtime-native.js +5 -0
- package/dist/runtime-native.js.map +1 -0
- package/dist/runtime.d.ts +73 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +293 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scalar.d.ts +165 -0
- package/dist/scalar.d.ts.map +1 -0
- package/dist/scalar.js +344 -0
- package/dist/scalar.js.map +1 -0
- package/dist/schema.d.ts +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +34 -37
- package/dist/schema.js.map +1 -1
- package/dist/shape.d.ts +28 -0
- package/dist/shape.d.ts.map +1 -0
- package/dist/shape.js +2 -0
- package/dist/shape.js.map +1 -0
- package/dist/spec.d.ts +27 -3
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js +22 -1
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +16 -3
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +54 -28
- package/dist/statements.js.map +1 -1
- package/dist/uuid.d.ts +23 -0
- package/dist/uuid.d.ts.map +1 -0
- package/dist/uuid.js +54 -0
- package/dist/uuid.js.map +1 -0
- package/pack-provenance.json +7 -0
- package/package.json +12 -16
- package/src/capacity.ts +34 -54
- package/src/changes.ts +361 -0
- package/src/close.ts +53 -0
- package/src/closed.ts +25 -19
- package/src/codec.ts +379 -0
- package/src/compile.ts +149 -0
- package/src/db-native.ts +247 -0
- package/src/db.ts +461 -1632
- package/src/errors.ts +50 -0
- package/src/face.ts +2 -2
- package/src/fields.ts +182 -55
- package/src/index.ts +62 -126
- package/src/internal/log.ts +51 -0
- package/src/law.ts +13 -28
- package/src/lower.ts +9 -4
- package/src/migration.ts +69 -0
- package/src/native.ts +326 -487
- package/src/query/atom.ts +61 -25
- package/src/query/compute.ts +237 -0
- package/src/query/find.ts +31 -12
- package/src/query/lower.ts +531 -143
- package/src/query/parse-ir.ts +18 -13
- package/src/query/run.ts +32 -16
- package/src/query/scope.ts +140 -8
- package/src/relation.ts +17 -21
- package/src/result.ts +130 -0
- package/src/rows.ts +404 -0
- package/src/runtime-codes.ts +18 -0
- package/src/runtime-errors.ts +69 -0
- package/src/runtime-native.ts +242 -0
- package/src/runtime.ts +440 -0
- package/src/scalar.ts +560 -0
- package/src/schema.ts +35 -37
- package/src/shape.ts +31 -0
- package/src/spec.ts +33 -3
- package/src/statements.ts +75 -68
- package/src/uuid.ts +69 -0
- package/dist/marshal.d.ts +0 -44
- package/dist/marshal.d.ts.map +0 -1
- package/dist/marshal.js +0 -165
- package/dist/marshal.js.map +0 -1
- package/src/marshal.ts +0 -200
package/src/codec.ts
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import { Effect, Schema as EffectSchema, Result } from "effect"
|
|
2
|
+
import { schemaTables } from "#compile.ts"
|
|
3
|
+
import { dbNative } from "#db-native.ts"
|
|
4
|
+
import type { AnyField } from "#fields.ts"
|
|
5
|
+
import { isFloatIntervalValue, isIntervalValue, rosterOf } from "#fields.ts"
|
|
6
|
+
import { lower } from "#lower.ts"
|
|
7
|
+
import type { AnyRelation, Fact } from "#relation.ts"
|
|
8
|
+
import type { CellValue } from "#rows.ts"
|
|
9
|
+
import { factOfCells, flatRowsOf } from "#rows.ts"
|
|
10
|
+
import type { ExecutionPolicy } from "#runtime.ts"
|
|
11
|
+
import { nativeOperationWith, policyWire, runtimeHandle } from "#runtime.ts"
|
|
12
|
+
import { DbError } from "#runtime-errors.ts"
|
|
13
|
+
import type { AnySchema } from "#schema.ts"
|
|
14
|
+
import type { Rel } from "#shape.ts"
|
|
15
|
+
import { f64BitsHex } from "#spec.ts"
|
|
16
|
+
import { Uuid } from "#uuid.ts"
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Boundary row codecs, derived from the core relation descriptors — never a
|
|
20
|
+
* second hand-maintained field roster (chapter 35).
|
|
21
|
+
*
|
|
22
|
+
* Two layers:
|
|
23
|
+
*
|
|
24
|
+
* 1. The CANONICAL native row codec: `encodeRows`/`decodeRows` run the
|
|
25
|
+
* same charged native implementation the engine, log and migrations
|
|
26
|
+
* share (owned bytes in, owned typed rows out; untrusted input cannot
|
|
27
|
+
* inject native capabilities). Effect-only, `NativeRuntime` required.
|
|
28
|
+
* 2. The schema-tagged JSON VALUE form for HTTP/export boundaries
|
|
29
|
+
* (chapter 30): every `f64` — finite included — is
|
|
30
|
+
* `{"$f64":"<16 lowercase hex digits>"}` of canonical binary64 bits;
|
|
31
|
+
* integers are canonical decimal strings; `Uuid` is canonical UUID;
|
|
32
|
+
* bytes use ONE strict lowercase-hex encoding; intervals are
|
|
33
|
+
* `{start,end}` in their element encoding; closed references are handle
|
|
34
|
+
* names. `JSON.stringify` of raw numbers is NOT the database value
|
|
35
|
+
* wire codec — it loses infinities/NaN and cannot encode BigInt.
|
|
36
|
+
* Decoders reject malformed widths, unknown tags and noncanonical
|
|
37
|
+
* representations. These are pure bounded per-row functions.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/** The typed row shape: a schema plus one of its relations. */
|
|
41
|
+
interface RowShape<R extends AnyRelation> {
|
|
42
|
+
readonly schema: AnySchema
|
|
43
|
+
readonly relation: R
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function rowShape<S extends AnySchema, R extends Rel<S>>(schema: S, relation: R): RowShape<R> {
|
|
47
|
+
return Object.freeze({ schema, relation })
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function invalid(operation: string): DbError {
|
|
51
|
+
return new DbError({ operation, reason: { _tag: "InvalidArgument" } })
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const DECIMAL = /^-?(?:0|[1-9][0-9]*)$/
|
|
55
|
+
const HEX16 = /^[0-9a-f]{16}$/
|
|
56
|
+
const LOWER_HEX = /^(?:[0-9a-f]{2})*$/
|
|
57
|
+
|
|
58
|
+
function hexOfBytes(bytes: Uint8Array): string {
|
|
59
|
+
let out = ""
|
|
60
|
+
for (const byte of bytes) {
|
|
61
|
+
out += byte.toString(16).padStart(2, "0")
|
|
62
|
+
}
|
|
63
|
+
return out
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function bytesOfHex(text: string): Uint8Array | undefined {
|
|
67
|
+
if (!LOWER_HEX.test(text)) {
|
|
68
|
+
return undefined
|
|
69
|
+
}
|
|
70
|
+
const out = new Uint8Array(text.length / 2)
|
|
71
|
+
for (let index = 0; index < out.length; index += 1) {
|
|
72
|
+
out[index] = Number.parseInt(text.slice(index * 2, index * 2 + 2), 16)
|
|
73
|
+
}
|
|
74
|
+
return out
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function f64OfHex(text: unknown): number | undefined {
|
|
78
|
+
if (typeof text !== "string" || !HEX16.test(text)) {
|
|
79
|
+
return undefined
|
|
80
|
+
}
|
|
81
|
+
const bits = BigInt(`0x${text}`)
|
|
82
|
+
const image = new DataView(new ArrayBuffer(8))
|
|
83
|
+
image.setBigUint64(0, bits)
|
|
84
|
+
const value = image.getFloat64(0)
|
|
85
|
+
// Canonical only: the one NaN bit pattern, and no negative zero image.
|
|
86
|
+
if (f64BitsHex(value) !== text) {
|
|
87
|
+
return undefined
|
|
88
|
+
}
|
|
89
|
+
return value
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** One host value to its schema-tagged JSON form (pure, bounded). */
|
|
93
|
+
function encodeBoundaryValue(field: AnyField, value: unknown): unknown {
|
|
94
|
+
const roster = rosterOf(field)
|
|
95
|
+
if (roster !== undefined) {
|
|
96
|
+
if (typeof value !== "string" || !roster.handles.includes(value)) {
|
|
97
|
+
throw invalid("encodeBoundaryRows")
|
|
98
|
+
}
|
|
99
|
+
return value
|
|
100
|
+
}
|
|
101
|
+
switch (field.kind) {
|
|
102
|
+
case "bool": {
|
|
103
|
+
if (typeof value !== "boolean") {
|
|
104
|
+
throw invalid("encodeBoundaryRows")
|
|
105
|
+
}
|
|
106
|
+
return value
|
|
107
|
+
}
|
|
108
|
+
case "u64":
|
|
109
|
+
case "i64": {
|
|
110
|
+
if (typeof value !== "bigint") {
|
|
111
|
+
throw invalid("encodeBoundaryRows")
|
|
112
|
+
}
|
|
113
|
+
return value.toString(10)
|
|
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
|
+
}
|
|
139
|
+
case "interval": {
|
|
140
|
+
if (field.element === "f64") {
|
|
141
|
+
if (!isFloatIntervalValue(value)) {
|
|
142
|
+
throw invalid("encodeBoundaryRows")
|
|
143
|
+
}
|
|
144
|
+
return { start: { $f64: f64BitsHex(value.start) }, end: { $f64: f64BitsHex(value.end) } }
|
|
145
|
+
}
|
|
146
|
+
if (!isIntervalValue(value)) {
|
|
147
|
+
throw invalid("encodeBoundaryRows")
|
|
148
|
+
}
|
|
149
|
+
return { start: value.start.toString(10), end: value.end.toString(10) }
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const U64_MAX = 0xffffffffffffffffn
|
|
155
|
+
const I64_MIN = -0x8000000000000000n
|
|
156
|
+
const I64_MAX = 0x7fffffffffffffffn
|
|
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
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function decodeTaggedF64(value: unknown): number | undefined {
|
|
174
|
+
if (typeof value !== "object" || value === null || !("$f64" in value)) {
|
|
175
|
+
return undefined
|
|
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)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** One schema-tagged JSON form back to the host value (strict, pure). */
|
|
185
|
+
function decodeBoundaryValue(field: AnyField, value: unknown): unknown | undefined {
|
|
186
|
+
const roster = rosterOf(field)
|
|
187
|
+
if (roster !== undefined) {
|
|
188
|
+
return typeof value === "string" && roster.handles.includes(value) ? value : undefined
|
|
189
|
+
}
|
|
190
|
+
switch (field.kind) {
|
|
191
|
+
case "bool":
|
|
192
|
+
return typeof value === "boolean" ? value : undefined
|
|
193
|
+
case "u64":
|
|
194
|
+
case "i64":
|
|
195
|
+
return decodeInteger(field.kind, value)
|
|
196
|
+
case "f64":
|
|
197
|
+
return decodeTaggedF64(value)
|
|
198
|
+
case "uuid":
|
|
199
|
+
return Uuid.isUuid(value) ? value : undefined
|
|
200
|
+
case "str":
|
|
201
|
+
return typeof value === "string" && value.isWellFormed() ? value : undefined
|
|
202
|
+
case "bytes": {
|
|
203
|
+
if (typeof value !== "string" || value.length !== field.width * 2) {
|
|
204
|
+
return undefined
|
|
205
|
+
}
|
|
206
|
+
return bytesOfHex(value)
|
|
207
|
+
}
|
|
208
|
+
case "interval": {
|
|
209
|
+
if (typeof value !== "object" || value === null || !("start" in value) || !("end" in value)) {
|
|
210
|
+
return undefined
|
|
211
|
+
}
|
|
212
|
+
const raw = value as { readonly start: unknown; readonly end: unknown }
|
|
213
|
+
if (field.element === "f64") {
|
|
214
|
+
const start = decodeTaggedF64(raw.start)
|
|
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 })
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Pure schema-tagged JSON encoding of owned rows (bounded per call). */
|
|
232
|
+
function encodeBoundaryRows<R extends AnyRelation>(
|
|
233
|
+
relation: R,
|
|
234
|
+
rows: Iterable<Fact<R>>
|
|
235
|
+
): Result.Result<ReadonlyArray<Readonly<Record<string, unknown>>>, DbError> {
|
|
236
|
+
return Result.try({
|
|
237
|
+
try: () => {
|
|
238
|
+
const out: Array<Readonly<Record<string, unknown>>> = []
|
|
239
|
+
for (const row of rows) {
|
|
240
|
+
const record: Record<string, unknown> = {}
|
|
241
|
+
for (const declared of relation.data.fields) {
|
|
242
|
+
record[declared.name] = encodeBoundaryValue(
|
|
243
|
+
declared.field,
|
|
244
|
+
(row as Readonly<Record<string, unknown>>)[declared.name]
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
out.push(Object.freeze(record))
|
|
248
|
+
}
|
|
249
|
+
return Object.freeze(out)
|
|
250
|
+
},
|
|
251
|
+
catch: (cause) => (cause instanceof DbError ? cause : invalid("encodeBoundaryRows"))
|
|
252
|
+
})
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Pure strict decoding of schema-tagged JSON rows; any refusal is typed. */
|
|
256
|
+
function decodeBoundaryRows<R extends AnyRelation>(
|
|
257
|
+
relation: R,
|
|
258
|
+
input: unknown
|
|
259
|
+
): Result.Result<ReadonlyArray<Fact<R>>, DbError> {
|
|
260
|
+
if (!Array.isArray(input)) {
|
|
261
|
+
return Result.fail(invalid("decodeBoundaryRows"))
|
|
262
|
+
}
|
|
263
|
+
const out: Array<Fact<R>> = []
|
|
264
|
+
for (const raw of input) {
|
|
265
|
+
if (typeof raw !== "object" || raw === null) {
|
|
266
|
+
return Result.fail(invalid("decodeBoundaryRows"))
|
|
267
|
+
}
|
|
268
|
+
const record: Record<string, unknown> = {}
|
|
269
|
+
for (const declared of relation.data.fields) {
|
|
270
|
+
const decoded = decodeBoundaryValue(declared.field, (raw as Readonly<Record<string, unknown>>)[declared.name])
|
|
271
|
+
if (decoded === undefined) {
|
|
272
|
+
return Result.fail(invalid("decodeBoundaryRows"))
|
|
273
|
+
}
|
|
274
|
+
record[declared.name] = decoded
|
|
275
|
+
}
|
|
276
|
+
// Unknown extra keys refuse: wrong-schema values never pass.
|
|
277
|
+
for (const key of Object.keys(raw)) {
|
|
278
|
+
if (!(key in record)) {
|
|
279
|
+
return Result.fail(invalid("decodeBoundaryRows"))
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
out.push(Object.freeze(record) as Fact<R>)
|
|
283
|
+
}
|
|
284
|
+
return Result.succeed(Object.freeze(out))
|
|
285
|
+
}
|
|
286
|
+
|
|
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
|
+
*/
|
|
293
|
+
function rowSchema<R extends AnyRelation>(relation: R) {
|
|
294
|
+
return EffectSchema.declare((value: unknown): value is Fact<R> => {
|
|
295
|
+
if (typeof value !== "object" || value === null) {
|
|
296
|
+
return false
|
|
297
|
+
}
|
|
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
|
+
})
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* The charged CANONICAL native row codec (chapter 35 roster): owned bytes
|
|
316
|
+
* out, owned typed rows back in — the same implementation log sealing and
|
|
317
|
+
* migrations use. Binding parameters is ingestion: input must stay stable
|
|
318
|
+
* through execution, and no native work starts before the checked owned
|
|
319
|
+
* chunk exists.
|
|
320
|
+
*/
|
|
321
|
+
const encodeRows = Effect.fn("encodeRows")(function* <R extends AnyRelation>(
|
|
322
|
+
shape: RowShape<R>,
|
|
323
|
+
rows: Iterable<Fact<R>>,
|
|
324
|
+
work: ExecutionPolicy
|
|
325
|
+
) {
|
|
326
|
+
const runtime = yield* runtimeHandle()
|
|
327
|
+
const tables = schemaTables(shape.schema)
|
|
328
|
+
const relationId = tables.relationIds.get(shape.relation.name)
|
|
329
|
+
if (relationId === undefined || shape.schema.relations[shape.relation.name] !== shape.relation) {
|
|
330
|
+
return yield* Effect.fail(invalid("encodeRows"))
|
|
331
|
+
}
|
|
332
|
+
const flat = yield* Effect.try({
|
|
333
|
+
try: () => flatRowsOf(shape.relation.data, rows as Iterable<object>),
|
|
334
|
+
catch: (cause) => (cause instanceof DbError ? cause : invalid("encodeRows"))
|
|
335
|
+
})
|
|
336
|
+
const spec = lower(shape.schema)
|
|
337
|
+
return yield* nativeOperationWith(
|
|
338
|
+
"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
|
+
),
|
|
349
|
+
dbNative.runtimeBytesTake,
|
|
350
|
+
(bytes) => bytes
|
|
351
|
+
)
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
const decodeRows = Effect.fn("decodeRows")(function* <R extends AnyRelation>(
|
|
355
|
+
shape: RowShape<R>,
|
|
356
|
+
input: Uint8Array,
|
|
357
|
+
work: ExecutionPolicy
|
|
358
|
+
) {
|
|
359
|
+
const runtime = yield* runtimeHandle()
|
|
360
|
+
const tables = schemaTables(shape.schema)
|
|
361
|
+
const relationId = tables.relationIds.get(shape.relation.name)
|
|
362
|
+
if (relationId === undefined || shape.schema.relations[shape.relation.name] !== shape.relation) {
|
|
363
|
+
return yield* Effect.fail(invalid("decodeRows"))
|
|
364
|
+
}
|
|
365
|
+
if (!(input instanceof Uint8Array) || !(input.buffer instanceof ArrayBuffer)) {
|
|
366
|
+
return yield* Effect.fail(invalid("decodeRows"))
|
|
367
|
+
}
|
|
368
|
+
const spec = lower(shape.schema)
|
|
369
|
+
return yield* nativeOperationWith(
|
|
370
|
+
"decodeRows",
|
|
371
|
+
(callback) =>
|
|
372
|
+
dbNative.runtimeDecodeRows(runtime, policyWire(work, "decodeRows"), spec, relationId, input, callback),
|
|
373
|
+
dbNative.runtimeRowsTake,
|
|
374
|
+
(rows) => Object.freeze(rows.map((row) => factOfCells(shape.relation, row as readonly CellValue[])))
|
|
375
|
+
)
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
export type { RowShape }
|
|
379
|
+
export { decodeBoundaryRows, decodeRows, encodeBoundaryRows, encodeRows, rowSchema, rowShape }
|
package/src/compile.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { Effect } from "effect"
|
|
2
|
+
import { isClosedMember } from "#closed.ts"
|
|
3
|
+
import { dbNative } from "#db-native.ts"
|
|
4
|
+
import { SdkInvariantError } from "#errors.ts"
|
|
5
|
+
import type { SchemaClasses } from "#law.ts"
|
|
6
|
+
import { lower } from "#lower.ts"
|
|
7
|
+
import type { SealedDescriptor } from "#native.ts"
|
|
8
|
+
import type { ExecutionPolicy } from "#runtime.ts"
|
|
9
|
+
import { nativeOperationWith, policyWire, runtimeHandle } from "#runtime.ts"
|
|
10
|
+
import type { AnySchema, Schema as SchemaDeclaration, SchemaRelations } from "#schema.ts"
|
|
11
|
+
import type { Statement } from "#statements.ts"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* `SchemaId` — the canonical schema/theory identity (chapter 30's value
|
|
15
|
+
* vocabulary): the engine's canonical schema fingerprint as lowercase hex.
|
|
16
|
+
* Independent of database identity; a branded string so arbitrary text does
|
|
17
|
+
* not typecheck where a schema identity is required.
|
|
18
|
+
*/
|
|
19
|
+
declare const schemaIdBrand: unique symbol
|
|
20
|
+
|
|
21
|
+
type SchemaId = string & { readonly [schemaIdBrand]: "bumbledb.SchemaId" }
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* `CompiledSchema<S>` — bounded detached immutable descriptor data plus the
|
|
25
|
+
* canonical `schemaId` (chapter 35). NOT a tenant handle or a second
|
|
26
|
+
* user-authored schema: it needs no native finalizer, holds no native
|
|
27
|
+
* resource, and open/create/build compile through the same implementation —
|
|
28
|
+
* prior compilation is optional, never a mandatory prepare ceremony.
|
|
29
|
+
*/
|
|
30
|
+
interface CompiledSchema<S extends AnySchema> {
|
|
31
|
+
readonly schema: S
|
|
32
|
+
readonly schemaId: SchemaId
|
|
33
|
+
readonly descriptor: SealedDescriptor
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Pure declaration-order tables shared by the whole core surface: relation
|
|
38
|
+
* ids (declaration order = ids), materialized statement ids (closed
|
|
39
|
+
* relations' auto-handle keys FIRST in relation-declaration order, then
|
|
40
|
+
* declared statements in declaration order with `mirrors` occupying two
|
|
41
|
+
* consecutive slots, source-first — the theory's `StatementId` law), and
|
|
42
|
+
* each relation's primary key (a closed relation's synthetic `(id)`,
|
|
43
|
+
* otherwise its FIRST declared `key` statement).
|
|
44
|
+
*/
|
|
45
|
+
interface PrimaryKey {
|
|
46
|
+
readonly statementId: number
|
|
47
|
+
readonly projection: readonly string[]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface SchemaTables {
|
|
51
|
+
readonly relationIds: ReadonlyMap<string, number>
|
|
52
|
+
readonly primaryKeys: ReadonlyMap<string, PrimaryKey>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function declaredWidth(statement: Statement): number {
|
|
56
|
+
return statement.data.kind === "mirrors" ? 2 : 1
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function tablesOf(theory: AnySchema): SchemaTables {
|
|
60
|
+
const relationIds = new Map<string, number>()
|
|
61
|
+
const primaryKeys = new Map<string, PrimaryKey>()
|
|
62
|
+
let autoKeys = 0
|
|
63
|
+
Object.entries(theory.relations).forEach(function assignRelation([name, member], ordinal) {
|
|
64
|
+
relationIds.set(name, ordinal)
|
|
65
|
+
if (isClosedMember(member)) {
|
|
66
|
+
primaryKeys.set(name, Object.freeze({ statementId: autoKeys, projection: Object.freeze(["id"]) }))
|
|
67
|
+
autoKeys += 1
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
let offset = autoKeys
|
|
71
|
+
for (const statement of theory.statements) {
|
|
72
|
+
const data = statement.data
|
|
73
|
+
if (data.kind === "key" && !primaryKeys.has(data.owner.name)) {
|
|
74
|
+
primaryKeys.set(data.owner.name, Object.freeze({ statementId: offset, projection: data.projection }))
|
|
75
|
+
}
|
|
76
|
+
offset += declaredWidth(statement)
|
|
77
|
+
}
|
|
78
|
+
return Object.freeze({ relationIds, primaryKeys })
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const compiledCache = new WeakMap<AnySchema, SchemaTables>()
|
|
82
|
+
|
|
83
|
+
/** Memoized pure tables (per schema value identity; no native work). */
|
|
84
|
+
function schemaTables(theory: AnySchema): SchemaTables {
|
|
85
|
+
const cached = compiledCache.get(theory)
|
|
86
|
+
if (cached !== undefined) {
|
|
87
|
+
return cached
|
|
88
|
+
}
|
|
89
|
+
const built = tablesOf(theory)
|
|
90
|
+
compiledCache.set(theory, built)
|
|
91
|
+
return built
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function admitSchemaId(fingerprint: string): SchemaId {
|
|
95
|
+
if (typeof fingerprint !== "string" || fingerprint.length === 0) {
|
|
96
|
+
throw new SdkInvariantError({ message: "Schema.compile: the engine returned no canonical fingerprint" })
|
|
97
|
+
}
|
|
98
|
+
return fingerprint as SchemaId
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Charged, effectful native schema admission/compilation (chapter 35): a
|
|
103
|
+
* schema declaration is pure metadata and never a claim its theory has been
|
|
104
|
+
* admitted. Runs on the one bounded executor under `work`, requires the
|
|
105
|
+
* acquired {@link NativeRuntime}, and yields detached immutable descriptor
|
|
106
|
+
* data plus the canonical schema identity. No database is opened and no
|
|
107
|
+
* native finalizer is created.
|
|
108
|
+
*/
|
|
109
|
+
const compile = Effect.fn("Schema.compile")(function* <S extends AnySchema>(schema: S, work: ExecutionPolicy) {
|
|
110
|
+
const handle = yield* runtimeHandle()
|
|
111
|
+
const spec = lower(schema)
|
|
112
|
+
const descriptor = yield* nativeOperationWith(
|
|
113
|
+
"Schema.compile",
|
|
114
|
+
(callback) => dbNative.runtimeSchemaCompile(handle, policyWire(work, "Schema.compile"), spec, callback),
|
|
115
|
+
dbNative.runtimeSchemaTake,
|
|
116
|
+
(value) => value
|
|
117
|
+
)
|
|
118
|
+
const compiled: CompiledSchema<S> = Object.freeze({
|
|
119
|
+
schema,
|
|
120
|
+
schemaId: admitSchemaId(descriptor.fingerprint),
|
|
121
|
+
descriptor
|
|
122
|
+
})
|
|
123
|
+
return compiled
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The declaration-tier `Schema<Rels, Classes>` TYPE (from `#schema.ts`),
|
|
128
|
+
* re-aliased LOCALLY so the one name carries both meanings through a
|
|
129
|
+
* SINGLE export specifier below — a local `type` + `const` merge is the
|
|
130
|
+
* plain TypeScript type/value merge, with no same-name pair of export
|
|
131
|
+
* declarations for any checker to refuse (the earlier
|
|
132
|
+
* `export type { Schema } from "#schema.ts"` beside `export { Schema }`
|
|
133
|
+
* spelled the type meaning twice at the barrel; this spelling cannot).
|
|
134
|
+
*/
|
|
135
|
+
type Schema<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses> = SchemaDeclaration<
|
|
136
|
+
Rels,
|
|
137
|
+
Classes
|
|
138
|
+
>
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The core `Schema` namespace (import as `BumbleSchema` when Effect Schema
|
|
142
|
+
* is also in scope — chapter 35). The `Schema<Rels>` TYPE remains the pure
|
|
143
|
+
* declaration from `#schema.ts` (the local alias above); this value owns
|
|
144
|
+
* the effectful half. One exported name, two meanings.
|
|
145
|
+
*/
|
|
146
|
+
const Schema = Object.freeze({ compile })
|
|
147
|
+
|
|
148
|
+
export type { CompiledSchema, PrimaryKey, SchemaId, SchemaTables }
|
|
149
|
+
export { Schema, schemaTables }
|