@bjornpagen/bumbledb 1.1.0 → 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 +114 -11
- package/README.md +52 -9
- 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 +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 +14 -10
- 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 +10 -10
- 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/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/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 +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 +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 +34 -28
- package/src/internal/log.ts +2 -0
- package/src/law.ts +13 -13
- 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/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/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 +9 -9
- package/src/statements.ts +138 -189
- package/src/values.ts +223 -0
package/src/codec.ts
CHANGED
|
@@ -1,18 +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
11
|
import { nativeOperationWith, runtimeHandle } from "#runtime.ts"
|
|
11
|
-
import { DbError } from "#runtime-errors.ts"
|
|
12
|
+
import { argumentError, DbError } from "#runtime-errors.ts"
|
|
12
13
|
import type { AnySchema } from "#schema.ts"
|
|
14
|
+
import { schemaDescriptor } from "#schema.ts"
|
|
13
15
|
import type { Rel } from "#shape.ts"
|
|
14
16
|
import { f64BitsHex } from "#spec.ts"
|
|
15
|
-
import {
|
|
17
|
+
import { fieldValue, recordValue } from "#values.ts"
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* Boundary row codecs, derived from the core relation descriptors — never a
|
|
@@ -43,14 +45,27 @@ interface RowShape<R extends AnyRelation> {
|
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
function rowShape<S extends AnySchema, R extends Rel<S>>(schema: S, relation: R): RowShape<R> {
|
|
46
|
-
|
|
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) }
|
|
47
62
|
}
|
|
48
63
|
|
|
49
64
|
function invalid(operation: string): DbError {
|
|
50
65
|
return new DbError({ operation, reason: { _tag: "InvalidArgument" } })
|
|
51
66
|
}
|
|
52
67
|
|
|
53
|
-
const DECIMAL =
|
|
68
|
+
const DECIMAL = /^(?:0|-?[1-9][0-9]*)$/
|
|
54
69
|
const HEX16 = /^[0-9a-f]{16}$/
|
|
55
70
|
const LOWER_HEX = /^(?:[0-9a-f]{2})*$/
|
|
56
71
|
|
|
@@ -88,146 +103,71 @@ function f64OfHex(text: unknown): number | undefined {
|
|
|
88
103
|
return value
|
|
89
104
|
}
|
|
90
105
|
|
|
91
|
-
/** One host value to its
|
|
92
|
-
function encodeBoundaryValue(field: AnyField,
|
|
93
|
-
|
|
94
|
-
if (roster !== undefined) {
|
|
95
|
-
if (typeof value !== "string" || !roster.handles.includes(value)) {
|
|
96
|
-
throw invalid("encodeBoundaryRows")
|
|
97
|
-
}
|
|
98
|
-
return value
|
|
99
|
-
}
|
|
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)
|
|
100
109
|
switch (field.kind) {
|
|
101
|
-
case "bool":
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return value
|
|
106
|
-
}
|
|
110
|
+
case "bool":
|
|
111
|
+
case "str":
|
|
112
|
+
case "uuid":
|
|
113
|
+
return fieldValue(context, field, input)
|
|
107
114
|
case "u64":
|
|
108
|
-
case "i64":
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
case "f64": {
|
|
115
|
-
if (typeof value !== "number") {
|
|
116
|
-
throw invalid("encodeBoundaryRows")
|
|
117
|
-
}
|
|
118
|
-
return { $f64: f64BitsHex(value) }
|
|
119
|
-
}
|
|
120
|
-
case "uuid": {
|
|
121
|
-
if (!Uuid.isUuid(value)) {
|
|
122
|
-
throw invalid("encodeBoundaryRows")
|
|
123
|
-
}
|
|
124
|
-
return value
|
|
125
|
-
}
|
|
126
|
-
case "str": {
|
|
127
|
-
if (typeof value !== "string" || !value.isWellFormed()) {
|
|
128
|
-
throw invalid("encodeBoundaryRows")
|
|
129
|
-
}
|
|
130
|
-
return value
|
|
131
|
-
}
|
|
132
|
-
case "bytes": {
|
|
133
|
-
if (!(value instanceof Uint8Array) || value.byteLength !== field.width) {
|
|
134
|
-
throw invalid("encodeBoundaryRows")
|
|
135
|
-
}
|
|
136
|
-
return hexOfBytes(value)
|
|
137
|
-
}
|
|
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))
|
|
138
121
|
case "interval": {
|
|
139
122
|
if (field.element === "f64") {
|
|
140
|
-
|
|
141
|
-
throw invalid("encodeBoundaryRows")
|
|
142
|
-
}
|
|
123
|
+
const value = fieldValue(context, { ...field, element: "f64" }, input)
|
|
143
124
|
return { start: { $f64: f64BitsHex(value.start) }, end: { $f64: f64BitsHex(value.end) } }
|
|
144
125
|
}
|
|
145
|
-
|
|
146
|
-
throw invalid("encodeBoundaryRows")
|
|
147
|
-
}
|
|
126
|
+
const value = fieldValue(context, { ...field, element: field.element }, input)
|
|
148
127
|
return { start: value.start.toString(10), end: value.end.toString(10) }
|
|
149
128
|
}
|
|
150
129
|
}
|
|
151
130
|
}
|
|
152
131
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
function decodeInteger(kind: "u64" | "i64", value: unknown): bigint | undefined {
|
|
158
|
-
if (typeof value !== "string" || !DECIMAL.test(value)) {
|
|
159
|
-
return undefined
|
|
160
|
-
}
|
|
161
|
-
const parsed = BigInt(value)
|
|
162
|
-
if (kind === "u64" && (parsed < 0n || parsed > U64_MAX)) {
|
|
163
|
-
return undefined
|
|
164
|
-
}
|
|
165
|
-
if (kind === "i64" && (parsed < I64_MIN || parsed > I64_MAX)) {
|
|
166
|
-
return undefined
|
|
167
|
-
}
|
|
168
|
-
// Canonical decimal only (no leading zeros, no "-0") — DECIMAL enforces.
|
|
169
|
-
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)
|
|
170
136
|
}
|
|
171
137
|
|
|
172
138
|
function decodeTaggedF64(value: unknown): number | undefined {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
const keys = Object.keys(value)
|
|
177
|
-
if (keys.length !== 1) {
|
|
178
|
-
return undefined
|
|
179
|
-
}
|
|
180
|
-
return f64OfHex((value as { readonly $f64: unknown }).$f64)
|
|
139
|
+
const record = recordValue("binary64 image", value, ["$f64"])
|
|
140
|
+
return f64OfHex(record.$f64)
|
|
181
141
|
}
|
|
182
142
|
|
|
183
|
-
/**
|
|
184
|
-
function decodeBoundaryValue(field: AnyField,
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
return typeof value === "string" && roster.handles.includes(value) ? value : undefined
|
|
188
|
-
}
|
|
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
|
|
189
147
|
switch (field.kind) {
|
|
190
|
-
case "bool":
|
|
191
|
-
return typeof value === "boolean" ? value : undefined
|
|
192
148
|
case "u64":
|
|
193
149
|
case "i64":
|
|
194
|
-
|
|
150
|
+
decoded = decodeInteger(input)
|
|
151
|
+
break
|
|
195
152
|
case "f64":
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
case "bytes": {
|
|
202
|
-
if (typeof value !== "string" || value.length !== field.width * 2) {
|
|
203
|
-
return undefined
|
|
204
|
-
}
|
|
205
|
-
return bytesOfHex(value)
|
|
206
|
-
}
|
|
153
|
+
decoded = decodeTaggedF64(input)
|
|
154
|
+
break
|
|
155
|
+
case "bytes":
|
|
156
|
+
decoded = typeof input === "string" && input.length === field.width * 2 ? bytesOfHex(input) : undefined
|
|
157
|
+
break
|
|
207
158
|
case "interval": {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const end = decodeTaggedF64(raw.end)
|
|
215
|
-
if (start === undefined || end === undefined || Number.isNaN(start) || Number.isNaN(end) || !(start < end)) {
|
|
216
|
-
return undefined
|
|
217
|
-
}
|
|
218
|
-
return Object.freeze({ start, end })
|
|
219
|
-
}
|
|
220
|
-
const start = decodeInteger(field.element, raw.start)
|
|
221
|
-
const end = decodeInteger(field.element, raw.end)
|
|
222
|
-
if (start === undefined || end === undefined || start >= end) {
|
|
223
|
-
return undefined
|
|
224
|
-
}
|
|
225
|
-
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
|
|
226
165
|
}
|
|
227
166
|
}
|
|
167
|
+
return fieldValue(context, field, decoded)
|
|
228
168
|
}
|
|
229
169
|
|
|
230
|
-
/** Pure schema-tagged JSON encoding of
|
|
170
|
+
/** Pure schema-tagged JSON encoding of complete rows. */
|
|
231
171
|
function encodeBoundaryRows<R extends AnyRelation>(
|
|
232
172
|
relation: R,
|
|
233
173
|
rows: Iterable<Fact<R>>
|
|
@@ -235,78 +175,68 @@ function encodeBoundaryRows<R extends AnyRelation>(
|
|
|
235
175
|
return Result.try({
|
|
236
176
|
try: () => {
|
|
237
177
|
const out: Array<Readonly<Record<string, unknown>>> = []
|
|
178
|
+
const fields = relationFields(relation)
|
|
179
|
+
const names = fields.map((declared) => declared.name)
|
|
238
180
|
for (const row of rows) {
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
+
)
|
|
244
190
|
)
|
|
245
|
-
|
|
246
|
-
out.push(Object.freeze(record))
|
|
191
|
+
)
|
|
247
192
|
}
|
|
248
193
|
return Object.freeze(out)
|
|
249
194
|
},
|
|
250
|
-
catch: (cause) => (
|
|
195
|
+
catch: (cause) => argumentError("encodeBoundaryRows", cause)
|
|
251
196
|
})
|
|
252
197
|
}
|
|
253
198
|
|
|
254
|
-
/** Pure strict decoding
|
|
199
|
+
/** Pure strict decoding; malformed records, getters and values fail as data. */
|
|
255
200
|
function decodeBoundaryRows<R extends AnyRelation>(
|
|
256
201
|
relation: R,
|
|
257
202
|
input: unknown
|
|
258
203
|
): Result.Result<ReadonlyArray<Fact<R>>, DbError> {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
out.push(Object.freeze(record) as Fact<R>)
|
|
282
|
-
}
|
|
283
|
-
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
|
+
})
|
|
284
225
|
}
|
|
285
226
|
|
|
286
|
-
/**
|
|
287
|
-
* An Effect Schema for one relation's typed row, DERIVED from the core
|
|
288
|
-
* descriptors (a validation-only declaration; the canonical `$f64`/decimal
|
|
289
|
-
* wire form stays this module's explicit codec — Effect Schema's generic
|
|
290
|
-
* JSON number encoding is not Bumbledb's `$f64` codec).
|
|
291
|
-
*/
|
|
227
|
+
/** An Effect Schema derived from the same complete-row value interpreter. */
|
|
292
228
|
function rowSchema<R extends AnyRelation>(relation: R) {
|
|
229
|
+
const fields = relationFields(relation)
|
|
230
|
+
const names = fields.map((declared) => declared.name)
|
|
293
231
|
return EffectSchema.declare((value: unknown): value is Fact<R> => {
|
|
294
|
-
|
|
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 {
|
|
295
238
|
return false
|
|
296
239
|
}
|
|
297
|
-
const record = value as Readonly<Record<string, unknown>>
|
|
298
|
-
return relation.data.fields.every(function checkField(declared) {
|
|
299
|
-
const cell = record[declared.name]
|
|
300
|
-
if (cell === undefined) {
|
|
301
|
-
return false
|
|
302
|
-
}
|
|
303
|
-
return Result.isSuccess(
|
|
304
|
-
Result.try({
|
|
305
|
-
try: () => encodeBoundaryValue(declared.field, cell),
|
|
306
|
-
catch: () => invalid("rowSchema")
|
|
307
|
-
})
|
|
308
|
-
)
|
|
309
|
-
})
|
|
310
240
|
})
|
|
311
241
|
}
|
|
312
242
|
|
|
@@ -322,16 +252,14 @@ const encodeRows = Effect.fn("encodeRows")(function* <R extends AnyRelation>(
|
|
|
322
252
|
rows: Iterable<Fact<R>>
|
|
323
253
|
) {
|
|
324
254
|
const runtime = yield* runtimeHandle()
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
255
|
+
const { relation, relationId, spec } = yield* Effect.try({
|
|
256
|
+
try: () => checkedRowShape("encodeRows", shape),
|
|
257
|
+
catch: (cause) => argumentError("encodeRows", cause)
|
|
258
|
+
})
|
|
330
259
|
const flat = yield* Effect.try({
|
|
331
|
-
try: () => flatRowsOf(
|
|
332
|
-
catch: (cause) => (
|
|
260
|
+
try: () => flatRowsOf(relation, rows as Iterable<object>),
|
|
261
|
+
catch: (cause) => argumentError("encodeRows", cause)
|
|
333
262
|
})
|
|
334
|
-
const spec = lower(shape.schema)
|
|
335
263
|
return yield* nativeOperationWith(
|
|
336
264
|
"encodeRows",
|
|
337
265
|
(callback) => dbNative.runtimeEncodeRows(runtime, spec, relationId, flat.rows, flat.cells, callback),
|
|
@@ -342,20 +270,18 @@ const encodeRows = Effect.fn("encodeRows")(function* <R extends AnyRelation>(
|
|
|
342
270
|
|
|
343
271
|
const decodeRows = Effect.fn("decodeRows")(function* <R extends AnyRelation>(shape: RowShape<R>, input: Uint8Array) {
|
|
344
272
|
const runtime = yield* runtimeHandle()
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}
|
|
273
|
+
const { relation, relationId, spec } = yield* Effect.try({
|
|
274
|
+
try: () => checkedRowShape("decodeRows", shape),
|
|
275
|
+
catch: (cause) => argumentError("decodeRows", cause)
|
|
276
|
+
})
|
|
350
277
|
if (!(input instanceof Uint8Array) || !(input.buffer instanceof ArrayBuffer)) {
|
|
351
278
|
return yield* Effect.fail(invalid("decodeRows"))
|
|
352
279
|
}
|
|
353
|
-
const spec = lower(shape.schema)
|
|
354
280
|
return yield* nativeOperationWith(
|
|
355
281
|
"decodeRows",
|
|
356
282
|
(callback) => dbNative.runtimeDecodeRows(runtime, spec, relationId, input, callback),
|
|
357
283
|
dbNative.runtimeRowsTake,
|
|
358
|
-
(rows) => Object.freeze(rows.map((row) => factOfCells(
|
|
284
|
+
(rows) => Object.freeze(rows.map((row) => factOfCells(relation, row as readonly CellValue[])))
|
|
359
285
|
)
|
|
360
286
|
})
|
|
361
287
|
|
package/src/compile.ts
CHANGED
|
@@ -1,22 +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"
|
|
9
|
+
import type { AnyRelation } from "#relation.ts"
|
|
8
10
|
import { nativeOperationWith, runtimeHandle } from "#runtime.ts"
|
|
11
|
+
import { argumentError } from "#runtime-errors.ts"
|
|
9
12
|
import type { AnySchema, Schema as SchemaDeclaration, SchemaRelations } from "#schema.ts"
|
|
10
|
-
import
|
|
13
|
+
import { schemaDescriptor } from "#schema.ts"
|
|
14
|
+
import { type KeyStatement, type Statement, statementDescriptor } from "#statements.ts"
|
|
11
15
|
|
|
12
16
|
/**
|
|
13
17
|
* `SchemaId` — the engine's canonical schema fingerprint as lowercase hex.
|
|
14
|
-
* Independent of database identity;
|
|
15
|
-
*
|
|
18
|
+
* Independent of database identity; ordinary structural text. Native
|
|
19
|
+
* boundaries verify the fingerprint wherever it grants admission.
|
|
16
20
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
type SchemaId = string & { readonly [schemaIdBrand]: "bumbledb.SchemaId" }
|
|
21
|
+
type SchemaId = string
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* `CompiledSchema<S>` — bounded detached immutable descriptor data plus the
|
|
@@ -36,44 +38,35 @@ interface CompiledSchema<S extends AnySchema> {
|
|
|
36
38
|
* ids (declaration order = ids), materialized statement ids (closed
|
|
37
39
|
* relations' auto-handle keys FIRST in relation-declaration order, then
|
|
38
40
|
* declared statements in declaration order with `mirrors` occupying two
|
|
39
|
-
* consecutive slots, source-first — the theory's `StatementId` law)
|
|
40
|
-
*
|
|
41
|
-
*
|
|
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.
|
|
42
44
|
*/
|
|
43
|
-
interface PrimaryKey {
|
|
44
|
-
readonly statementId: number
|
|
45
|
-
readonly projection: readonly string[]
|
|
46
|
-
}
|
|
47
|
-
|
|
48
45
|
interface SchemaTables {
|
|
49
46
|
readonly relationIds: ReadonlyMap<string, number>
|
|
50
|
-
readonly
|
|
47
|
+
readonly statementIds: ReadonlyMap<Statement, number>
|
|
51
48
|
}
|
|
52
49
|
|
|
53
50
|
function declaredWidth(statement: Statement): number {
|
|
54
|
-
return statement.
|
|
51
|
+
return statement.kind === "mirrors" ? 2 : 1
|
|
55
52
|
}
|
|
56
53
|
|
|
57
54
|
function tablesOf(theory: AnySchema): SchemaTables {
|
|
58
55
|
const relationIds = new Map<string, number>()
|
|
59
|
-
const
|
|
56
|
+
const statementIds = new Map<Statement, number>()
|
|
60
57
|
let autoKeys = 0
|
|
61
58
|
Object.entries(theory.relations).forEach(function assignRelation([name, member], ordinal) {
|
|
62
59
|
relationIds.set(name, ordinal)
|
|
63
60
|
if (isClosedMember(member)) {
|
|
64
|
-
primaryKeys.set(name, Object.freeze({ statementId: autoKeys, projection: Object.freeze(["id"]) }))
|
|
65
61
|
autoKeys += 1
|
|
66
62
|
}
|
|
67
63
|
})
|
|
68
64
|
let offset = autoKeys
|
|
69
65
|
for (const statement of theory.statements) {
|
|
70
|
-
|
|
71
|
-
if (data.kind === "key" && !primaryKeys.has(data.owner.name)) {
|
|
72
|
-
primaryKeys.set(data.owner.name, Object.freeze({ statementId: offset, projection: data.projection }))
|
|
73
|
-
}
|
|
66
|
+
statementIds.set(statement, offset)
|
|
74
67
|
offset += declaredWidth(statement)
|
|
75
68
|
}
|
|
76
|
-
return Object.freeze({ relationIds,
|
|
69
|
+
return Object.freeze({ relationIds, statementIds })
|
|
77
70
|
}
|
|
78
71
|
|
|
79
72
|
const compiledCache = new WeakMap<AnySchema, SchemaTables>()
|
|
@@ -85,15 +78,37 @@ function schemaTables(theory: AnySchema): SchemaTables {
|
|
|
85
78
|
return cached
|
|
86
79
|
}
|
|
87
80
|
const built = tablesOf(theory)
|
|
88
|
-
compiledCache.set(theory, built)
|
|
81
|
+
if (isImmutable(theory)) compiledCache.set(theory, built)
|
|
89
82
|
return built
|
|
90
83
|
}
|
|
91
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
|
+
|
|
92
107
|
function admitSchemaId(fingerprint: string): SchemaId {
|
|
93
|
-
if (typeof fingerprint !== "string" || fingerprint
|
|
108
|
+
if (typeof fingerprint !== "string" || !/^[0-9a-f]{64}$/.test(fingerprint)) {
|
|
94
109
|
throw new SdkInvariantError({ message: "Schema.compile: the engine returned no canonical fingerprint" })
|
|
95
110
|
}
|
|
96
|
-
return fingerprint
|
|
111
|
+
return fingerprint
|
|
97
112
|
}
|
|
98
113
|
|
|
99
114
|
/**
|
|
@@ -105,18 +120,27 @@ function admitSchemaId(fingerprint: string): SchemaId {
|
|
|
105
120
|
* native finalizer is created.
|
|
106
121
|
*/
|
|
107
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) })
|
|
108
128
|
const handle = yield* runtimeHandle()
|
|
109
|
-
const spec = lower(schema)
|
|
110
129
|
const descriptor = yield* nativeOperationWith(
|
|
111
130
|
"Schema.compile",
|
|
112
131
|
(callback) => dbNative.runtimeSchemaCompile(handle, spec, callback),
|
|
113
132
|
dbNative.runtimeSchemaTake,
|
|
114
133
|
(value) => value
|
|
115
134
|
)
|
|
135
|
+
const ownedDescriptor = snapshotData(descriptor)
|
|
116
136
|
const compiled: CompiledSchema<S> = Object.freeze({
|
|
117
|
-
schema
|
|
137
|
+
get schema() {
|
|
138
|
+
return snapshotData(owned)
|
|
139
|
+
},
|
|
118
140
|
schemaId: admitSchemaId(descriptor.fingerprint),
|
|
119
|
-
descriptor
|
|
141
|
+
get descriptor() {
|
|
142
|
+
return snapshotData(ownedDescriptor)
|
|
143
|
+
}
|
|
120
144
|
})
|
|
121
145
|
return compiled
|
|
122
146
|
})
|
|
@@ -143,5 +167,5 @@ type Schema<Rels extends SchemaRelations, Classes extends SchemaClasses = Schema
|
|
|
143
167
|
*/
|
|
144
168
|
const Schema = Object.freeze({ compile })
|
|
145
169
|
|
|
146
|
-
export type { CompiledSchema,
|
|
147
|
-
export { Schema, schemaTables }
|
|
170
|
+
export type { CompiledSchema, SchemaId, SchemaTables }
|
|
171
|
+
export { declaredKey, Schema, schemaTables }
|
package/src/db-native.ts
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* and joins any of them. Close verbs report the real drain outcome through
|
|
7
7
|
* `CloseWire`.
|
|
8
8
|
*/
|
|
9
|
+
|
|
10
|
+
import type { ChangeCounts } from "#changes.ts"
|
|
9
11
|
import type { DbHandle, ParsedQuery, QueryParam, SealedDescriptor, Violation } from "#native.ts"
|
|
10
12
|
import { native } from "#native.ts"
|
|
11
13
|
import type { CellValue } from "#rows.ts"
|
|
@@ -30,6 +32,9 @@ export interface DraftHandle {
|
|
|
30
32
|
export interface ChangesHandle {
|
|
31
33
|
readonly __changes: unique symbol
|
|
32
34
|
}
|
|
35
|
+
export interface ChangesCursorHandle {
|
|
36
|
+
readonly __changesCursor: unique symbol
|
|
37
|
+
}
|
|
33
38
|
|
|
34
39
|
/** The core-local witness: catalog/store identity plus generation, never a StateStamp. */
|
|
35
40
|
export interface WitnessWire {
|
|
@@ -47,6 +52,16 @@ export type ApplyOutcomeWire =
|
|
|
47
52
|
| { readonly tag: "invariant-rejected"; readonly violations: readonly Violation[] }
|
|
48
53
|
| { readonly tag: "moved"; readonly witnessed: WitnessWire; readonly current: WitnessWire }
|
|
49
54
|
|
|
55
|
+
export type JudgeOutcomeWire =
|
|
56
|
+
| { readonly tag: "admitted"; readonly base: WitnessWire; readonly changes: ChangeCounts }
|
|
57
|
+
| {
|
|
58
|
+
readonly tag: "invariant-rejected"
|
|
59
|
+
readonly base: WitnessWire
|
|
60
|
+
readonly changes: ChangeCounts
|
|
61
|
+
readonly violations: readonly Violation[]
|
|
62
|
+
}
|
|
63
|
+
| { readonly tag: "moved"; readonly witnessed: WitnessWire; readonly current: WitnessWire }
|
|
64
|
+
|
|
50
65
|
/** Bounded database diagnostics: measurements, never retained row payloads. */
|
|
51
66
|
export interface DbInspectionWire {
|
|
52
67
|
readonly generation: bigint
|
|
@@ -62,6 +77,14 @@ export interface SnapshotWire {
|
|
|
62
77
|
export interface ChangesWire {
|
|
63
78
|
readonly changes: ChangesHandle
|
|
64
79
|
readonly fingerprint: string
|
|
80
|
+
readonly counts: ChangeCounts
|
|
81
|
+
readonly byteLength: bigint
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface ChangeRecordWire {
|
|
85
|
+
readonly relation: number
|
|
86
|
+
readonly kind: "add" | "remove"
|
|
87
|
+
readonly values: readonly CellValue[]
|
|
65
88
|
}
|
|
66
89
|
|
|
67
90
|
export interface MutationReportWire {
|
|
@@ -158,10 +181,29 @@ interface DbBridge {
|
|
|
158
181
|
runtimeChangesTake(operation: OperationHandle): ChangesWire
|
|
159
182
|
runtimeDraftClose(draft: DraftHandle, callback: (report: CloseWire) => void): void
|
|
160
183
|
runtimeChangesClose(changes: ChangesHandle, callback: (report: CloseWire) => void): void
|
|
184
|
+
/** Owned immutable bytes; parsing checks canonical order and the supplied schema. */
|
|
185
|
+
runtimeChangesBytes(changes: ChangesHandle, callback: () => void): OperationHandle
|
|
186
|
+
runtimeChangesParse(
|
|
187
|
+
runtime: RuntimeHandle,
|
|
188
|
+
spec: SchemaSpec,
|
|
189
|
+
bytes: Uint8Array,
|
|
190
|
+
callback: () => void
|
|
191
|
+
): OperationHandle
|
|
192
|
+
/** One command's add-wins composition, not sequential application. */
|
|
193
|
+
runtimeChangesCompose(left: ChangesHandle, right: ChangesHandle, callback: () => void): OperationHandle
|
|
194
|
+
/** Independent bounded traversal; never spends the immutable source. */
|
|
195
|
+
runtimeChangesCursor(changes: ChangesHandle, callback: () => void): OperationHandle
|
|
196
|
+
runtimeChangesCursorTake(operation: OperationHandle): ChangesCursorHandle
|
|
197
|
+
runtimeChangesCursorNext(cursor: ChangesCursorHandle, callback: () => void): OperationHandle
|
|
198
|
+
runtimeChangePageTake(operation: OperationHandle): readonly ChangeRecordWire[] | null
|
|
199
|
+
runtimeChangesCursorClose(cursor: ChangesCursorHandle, callback: (report: CloseWire) => void): void
|
|
161
200
|
|
|
162
201
|
/** One immutable final-state admission/commit under the managed owner. */
|
|
163
202
|
runtimeDbApply(db: DbHandle, changes: ChangesHandle, expected: ExpectedWire, callback: () => void): OperationHandle
|
|
164
203
|
runtimeApplyTake(operation: OperationHandle): ApplyOutcomeWire
|
|
204
|
+
/** Same candidate admission as apply, aborted on its worker before returning. */
|
|
205
|
+
runtimeDbJudge(db: DbHandle, changes: ChangesHandle, expected: ExpectedWire, callback: () => void): OperationHandle
|
|
206
|
+
runtimeJudgeTake(operation: OperationHandle): JudgeOutcomeWire
|
|
165
207
|
|
|
166
208
|
runtimeDbInspect(db: DbHandle, callback: () => void): OperationHandle
|
|
167
209
|
runtimeDbClearCache(db: DbHandle, callback: () => void): OperationHandle
|