@bjornpagen/bumbledb 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COOKBOOK.md +72 -19
- package/README.md +9 -6
- package/dist/changes.d.ts +0 -1
- package/dist/changes.d.ts.map +1 -1
- package/dist/changes.js +2 -8
- package/dist/changes.js.map +1 -1
- package/dist/codec.d.ts +8 -1
- package/dist/codec.d.ts.map +1 -1
- package/dist/codec.js +29 -2
- package/dist/codec.js.map +1 -1
- package/dist/db-native.d.ts +2 -9
- package/dist/db-native.d.ts.map +1 -1
- package/dist/db-native.js.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/internal/log.d.ts +4 -4
- package/dist/internal/log.d.ts.map +1 -1
- package/dist/internal/log.js +2 -2
- package/dist/internal/log.js.map +1 -1
- package/dist/native.d.ts +9 -9
- package/dist/native.d.ts.map +1 -1
- package/dist/query/compute.d.ts +3 -3
- package/dist/query/compute.d.ts.map +1 -1
- package/dist/query/compute.js +3 -12
- package/dist/query/compute.js.map +1 -1
- package/dist/query/description.js +4 -4
- package/dist/query/description.js.map +1 -1
- package/dist/query/lower.d.ts +11 -2
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +31 -26
- package/dist/query/lower.js.map +1 -1
- package/dist/query/parse-ir.d.ts.map +1 -1
- package/dist/query/parse-ir.js +4 -4
- package/dist/query/parse-ir.js.map +1 -1
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +6 -1
- package/dist/query/scope.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 -0
- package/dist/runtime-codes.js.map +1 -1
- package/dist/runtime-errors.d.ts +23 -3
- package/dist/runtime-errors.d.ts.map +1 -1
- package/dist/runtime-errors.js +16 -3
- package/dist/runtime-errors.js.map +1 -1
- package/dist/runtime-native.d.ts +3 -8
- package/dist/runtime-native.d.ts.map +1 -1
- package/dist/runtime-native.js.map +1 -1
- package/dist/runtime.d.ts +0 -23
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +0 -36
- package/dist/runtime.js.map +1 -1
- package/dist/scalar.d.ts +34 -104
- package/dist/scalar.d.ts.map +1 -1
- package/dist/scalar.js +12 -177
- package/dist/scalar.js.map +1 -1
- package/dist/schema-file.d.ts +7 -0
- package/dist/schema-file.d.ts.map +1 -0
- package/dist/schema-file.js +24 -0
- package/dist/schema-file.js.map +1 -0
- package/pack-provenance.json +4 -4
- package/package.json +4 -4
- package/src/changes.ts +2 -8
- package/src/codec.ts +43 -3
- package/src/db-native.ts +2 -10
- package/src/index.ts +11 -5
- package/src/internal/log.ts +2 -4
- package/src/native.ts +7 -7
- package/src/query/compute.ts +6 -15
- package/src/query/description.ts +4 -4
- package/src/query/lower.ts +56 -25
- package/src/query/parse-ir.ts +6 -4
- package/src/query/scope.ts +8 -1
- package/src/runtime-codes.ts +1 -0
- package/src/runtime-errors.ts +16 -3
- package/src/runtime-native.ts +1 -9
- package/src/runtime.ts +1 -70
- package/src/scalar.ts +49 -329
- package/src/schema-file.ts +38 -0
- package/dist/migration.d.ts +0 -31
- package/dist/migration.d.ts.map +0 -1
- package/dist/migration.js +0 -47
- package/dist/migration.js.map +0 -1
- package/src/migration.ts +0 -61
package/src/native.ts
CHANGED
|
@@ -82,7 +82,7 @@ type HeadOpIr = "sum" | "mean" | "min" | "max" | "count" | "pack"
|
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
84
|
* The computed-find scalar expression (C05 `FindTerm::Compute(ScalarExpr)`):
|
|
85
|
-
* exactly the frozen core roster, spelled as the
|
|
85
|
+
* exactly the frozen core roster, spelled as the canonical scalar JSON spells
|
|
86
86
|
* the same expressions (one grammar, no second evaluator). `var` binds a
|
|
87
87
|
* rule variable ordinal on this lane.
|
|
88
88
|
*/
|
|
@@ -294,7 +294,7 @@ type LogAccess =
|
|
|
294
294
|
readonly operation: string
|
|
295
295
|
readonly intent:
|
|
296
296
|
| { readonly kind: "erasure" }
|
|
297
|
-
| { readonly kind: "
|
|
297
|
+
| { readonly kind: "transition"; readonly contractDigest: Uint8Array; readonly target: string }
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
type LogLifecycle =
|
|
@@ -311,10 +311,10 @@ type LogLifecycle =
|
|
|
311
311
|
readonly reason:
|
|
312
312
|
| { readonly kind: "erasure" }
|
|
313
313
|
| {
|
|
314
|
-
readonly kind: "
|
|
314
|
+
readonly kind: "transitionAborted"
|
|
315
315
|
readonly sourceDatabase: string
|
|
316
316
|
readonly sourceIncarnation: string
|
|
317
|
-
readonly
|
|
317
|
+
readonly contractDigest: Uint8Array
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
320
|
|
|
@@ -327,7 +327,7 @@ type LogActivation =
|
|
|
327
327
|
readonly cause:
|
|
328
328
|
| { readonly kind: "create" }
|
|
329
329
|
| { readonly kind: "restore" }
|
|
330
|
-
| { readonly kind: "
|
|
330
|
+
| { readonly kind: "transition"; readonly contractDigest: Uint8Array }
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
interface LogAuthority {
|
|
@@ -341,10 +341,10 @@ type LogGenesisProvenance =
|
|
|
341
341
|
| { readonly kind: "create" }
|
|
342
342
|
| { readonly kind: "restore"; readonly sourceEvidence: Uint8Array }
|
|
343
343
|
| {
|
|
344
|
-
readonly kind: "
|
|
344
|
+
readonly kind: "transition"
|
|
345
345
|
readonly sourceDatabase: string
|
|
346
346
|
readonly sourceIncarnation: string
|
|
347
|
-
readonly
|
|
347
|
+
readonly contractDigest: Uint8Array
|
|
348
348
|
}
|
|
349
349
|
|
|
350
350
|
interface LogGenesis {
|
package/src/query/compute.ts
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
* variable-addressed leaf scope over the ONE shared operator roster in
|
|
4
|
-
* `#scalar.ts`. Leaves are bound query variables that lower to `VarId`s.
|
|
5
|
-
* Operators are the shared constructors — this module only admits known
|
|
6
|
-
* schema kinds at query-var leaves.
|
|
7
|
-
*
|
|
8
|
-
* `ComputeExpr<K>` is a `ScalarNode<"query-var", K>`. Distinct I64 and U64
|
|
9
|
-
* survive host inference; they do not collapse to plain `bigint`.
|
|
10
|
-
*/
|
|
1
|
+
/** Query scalar expressions use the same nodes that native preparation reads.
|
|
2
|
+
* Distinct i64 and u64 kinds survive TypeScript's shared bigint carrier. */
|
|
11
3
|
import { AuthoringError } from "#errors.ts"
|
|
12
4
|
import type { AnyField } from "#fields.ts"
|
|
13
5
|
import { bool as boolField, f64 as f64Field, i64 as i64Field, rosterOf, u64 as u64Field } from "#fields.ts"
|
|
@@ -32,13 +24,13 @@ import {
|
|
|
32
24
|
scalarNegate
|
|
33
25
|
} from "#scalar.ts"
|
|
34
26
|
|
|
35
|
-
/** Query
|
|
36
|
-
type QueryNode = ScalarNode
|
|
27
|
+
/** Query expression, including interval leaves consumed by measure. */
|
|
28
|
+
type QueryNode = ScalarNode
|
|
37
29
|
|
|
38
30
|
/** Host value for a derived query compute kind. */
|
|
39
31
|
type ComputeValue<K extends ScalarKind> = K extends "f64" ? number : K extends "bool" ? boolean : bigint
|
|
40
32
|
|
|
41
|
-
type ComputeExpr<K extends ScalarKind> = ScalarNode<
|
|
33
|
+
type ComputeExpr<K extends ScalarKind> = ScalarNode<K>
|
|
42
34
|
|
|
43
35
|
type AnyComputeExpr = ComputeExpr<"u64"> | ComputeExpr<"i64"> | ComputeExpr<"f64"> | ComputeExpr<"bool">
|
|
44
36
|
|
|
@@ -65,7 +57,6 @@ type OperandKind<O> =
|
|
|
65
57
|
function isComputeExpr(value: unknown): value is AnyComputeExpr {
|
|
66
58
|
return (
|
|
67
59
|
isScalarNode(value) &&
|
|
68
|
-
value.scope === "query-var" &&
|
|
69
60
|
(value.result === "u64" || value.result === "i64" || value.result === "f64" || value.result === "bool")
|
|
70
61
|
)
|
|
71
62
|
}
|
|
@@ -99,7 +90,7 @@ function asQueryNode(where: string, operand: Operand): QueryNode {
|
|
|
99
90
|
}
|
|
100
91
|
|
|
101
92
|
function literal<K extends ScalarKind>(value: ScalarLiteral): ComputeExpr<K> {
|
|
102
|
-
return scalarLiteral(
|
|
93
|
+
return scalarLiteral(value) as ComputeExpr<K>
|
|
103
94
|
}
|
|
104
95
|
|
|
105
96
|
function u64(value: bigint): ComputeExpr<"u64"> {
|
package/src/query/description.ts
CHANGED
|
@@ -269,13 +269,13 @@ function replayRule(
|
|
|
269
269
|
const value = input.value
|
|
270
270
|
switch (value.kind) {
|
|
271
271
|
case "u64":
|
|
272
|
-
return scalarLiteral(
|
|
272
|
+
return scalarLiteral({ u64: value.value })
|
|
273
273
|
case "i64":
|
|
274
|
-
return scalarLiteral(
|
|
274
|
+
return scalarLiteral({ i64: value.value })
|
|
275
275
|
case "f64":
|
|
276
|
-
return scalarLiteral(
|
|
276
|
+
return scalarLiteral({ f64: value.value })
|
|
277
277
|
case "bool":
|
|
278
|
-
return scalarLiteral(
|
|
278
|
+
return scalarLiteral({ bool: value.value })
|
|
279
279
|
default:
|
|
280
280
|
return refused("query compute", "requires a scalar literal")
|
|
281
281
|
}
|
package/src/query/lower.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
u64 as u64Field
|
|
14
14
|
} from "#fields.ts"
|
|
15
15
|
import { snapshotData } from "#immutable.ts"
|
|
16
|
+
import type { Same } from "#judgment.ts"
|
|
16
17
|
import type { ClassRecordOf, SchemaClasses } from "#law.ts"
|
|
17
18
|
import type {
|
|
18
19
|
AtomIr,
|
|
@@ -103,6 +104,25 @@ type HeadShape = Readonly<Record<string, ClassedField>> | undefined
|
|
|
103
104
|
|
|
104
105
|
type HeadOf<T> = InferredOf<T> extends { readonly head: infer H extends HeadShape } ? H : HeadShape
|
|
105
106
|
|
|
107
|
+
type HeadFieldMeet<A extends AnyField, B extends AnyField> = A extends IntervalField
|
|
108
|
+
? B extends IntervalField
|
|
109
|
+
? IntervalField<A["element"], Same<A["width"], B["width"]> extends true ? A["width"] : undefined>
|
|
110
|
+
: A
|
|
111
|
+
: A
|
|
112
|
+
|
|
113
|
+
/** The common refinements of the same output column across union arms. */
|
|
114
|
+
type HeadMeet<A extends HeadShape, B extends HeadShape> =
|
|
115
|
+
A extends Readonly<Record<string, ClassedField>>
|
|
116
|
+
? B extends Readonly<Record<string, ClassedField>>
|
|
117
|
+
? {
|
|
118
|
+
readonly [K in keyof A & keyof B]: {
|
|
119
|
+
readonly field: HeadFieldMeet<A[K]["field"], B[K]["field"]>
|
|
120
|
+
readonly class: Same<A[K]["class"], B[K]["class"]> extends true ? A[K]["class"] : undefined
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
: HeadShape
|
|
124
|
+
: HeadShape
|
|
125
|
+
|
|
106
126
|
interface RuleValue<Row, P extends ParamsRecord, Head extends HeadShape = undefined> {
|
|
107
127
|
readonly rule: RuleData
|
|
108
128
|
readonly [inferred]?: { readonly row: Row; readonly params: P; readonly head: Head }
|
|
@@ -412,7 +432,7 @@ interface Query<
|
|
|
412
432
|
|
|
413
433
|
rule<RV extends AnyRuleValue>(
|
|
414
434
|
build: (r: QueryRuleScope<Rels, Classes>) => RV
|
|
415
|
-
): Query<Rels, Row | RowOf<RV>, Flatten<Params & ParamsOf<RV>>, Classes, Head
|
|
435
|
+
): Query<Rels, Row | RowOf<RV>, Flatten<Params & ParamsOf<RV>>, Classes, HeadMeet<Head, HeadOf<RV>>>
|
|
416
436
|
|
|
417
437
|
/** A diagnostic name for composition/tracing — never a schema relation. */
|
|
418
438
|
named(label: string): Query<Rels, Row, Params, Classes, Head>
|
|
@@ -1542,20 +1562,8 @@ function renderClosedSlice(closed: AnyClosedRoster | undefined): string {
|
|
|
1542
1562
|
return closed === undefined ? "a bare value" : `a ${closed.name} reference`
|
|
1543
1563
|
}
|
|
1544
1564
|
|
|
1545
|
-
function
|
|
1546
|
-
|
|
1547
|
-
if (entry.kind === "segments") return `${column.name}:compute`
|
|
1548
|
-
if (entry.kind === "var") {
|
|
1549
|
-
return `${column.name}:var`
|
|
1550
|
-
}
|
|
1551
|
-
if (entry.kind === "compute") {
|
|
1552
|
-
return `${column.name}:compute`
|
|
1553
|
-
}
|
|
1554
|
-
const agg = entry.agg
|
|
1555
|
-
if (agg.op === "fold") {
|
|
1556
|
-
return `${column.name}:${agg.fold}`
|
|
1557
|
-
}
|
|
1558
|
-
return `${column.name}:${agg.op}`
|
|
1565
|
+
function headOperation(column: FindColumn): HeadOpIr | undefined {
|
|
1566
|
+
return column.entry.kind === "aggregate" ? headOpOf(column.entry.agg) : undefined
|
|
1559
1567
|
}
|
|
1560
1568
|
|
|
1561
1569
|
function renderParamAnchor(roster: AnyClosedRoster | undefined): string {
|
|
@@ -1692,12 +1700,22 @@ function alignedHeadOf(label: string, rules: readonly RuleData[]): readonly Find
|
|
|
1692
1700
|
if (first === undefined) {
|
|
1693
1701
|
throw new AuthoringError({ message: `${label} needs at least one rule` })
|
|
1694
1702
|
}
|
|
1695
|
-
|
|
1703
|
+
// Bare u64 admits either carrier, but does not make two different
|
|
1704
|
+
// carriers compatible. Retain one original classed slot per column;
|
|
1705
|
+
// checking only the first arm (or its weakened meet) loses that proof.
|
|
1706
|
+
const classed = new Map<number, { readonly slot: ClassedField; readonly rule: number }>()
|
|
1696
1707
|
rules.forEach(function verifyHead(rule, index) {
|
|
1697
|
-
|
|
1698
|
-
|
|
1708
|
+
if (
|
|
1709
|
+
rule.finds.length !== first.finds.length ||
|
|
1710
|
+
rule.finds.some((column, position) => {
|
|
1711
|
+
const lead = first.finds[position]
|
|
1712
|
+
return lead === undefined || lead.name !== column.name || headOperation(lead) !== headOperation(column)
|
|
1713
|
+
})
|
|
1714
|
+
) {
|
|
1715
|
+
const render = (columns: readonly FindColumn[]) =>
|
|
1716
|
+
columns.map((column) => `${column.name}:${headOperation(column) ?? "project"}`).join(", ")
|
|
1699
1717
|
throw new AuthoringError({
|
|
1700
|
-
message: `every rule of ${label} derives the same head — rule 0 finds (${
|
|
1718
|
+
message: `every rule of ${label} derives the same head — rule 0 finds (${render(first.finds)}), rule ${index} finds (${render(rule.finds)})`
|
|
1701
1719
|
})
|
|
1702
1720
|
}
|
|
1703
1721
|
rule.finds.forEach(function verifyClosedSlice(column, position) {
|
|
@@ -1710,30 +1728,44 @@ function alignedHeadOf(label: string, rules: readonly RuleData[]): readonly Find
|
|
|
1710
1728
|
if (lead === undefined) {
|
|
1711
1729
|
return
|
|
1712
1730
|
}
|
|
1713
|
-
|
|
1731
|
+
const prior = classed.get(position) ?? (lead.slot === undefined ? undefined : { slot: lead.slot, rule: 0 })
|
|
1732
|
+
if (prior !== undefined && column.slot !== undefined && !headFieldJoins(prior.slot, column.slot)) {
|
|
1714
1733
|
throw new AuthoringError({
|
|
1715
|
-
message: `every rule of ${label} derives the same head — the head column ${lead.name} is bound at ${renderFieldKind(
|
|
1734
|
+
message: `every rule of ${label} derives the same head — the head column ${lead.name} is bound at ${renderFieldKind(prior.slot)} in rule ${prior.rule} but at ${renderFieldKind(column.slot)} in rule ${index} (a head column joins class-equal slots; same-wire u64 admits a bare side, and the merged head is bare)`
|
|
1716
1735
|
})
|
|
1717
1736
|
}
|
|
1737
|
+
if (column.slot?.class !== undefined && !classed.has(position)) {
|
|
1738
|
+
classed.set(position, { slot: column.slot, rule: index })
|
|
1739
|
+
}
|
|
1718
1740
|
})
|
|
1719
1741
|
})
|
|
1720
1742
|
const merged = first.finds.map(function meetColumn(lead, position) {
|
|
1721
1743
|
const slot = lead.slot
|
|
1722
|
-
if (slot === undefined
|
|
1744
|
+
if (slot === undefined) {
|
|
1723
1745
|
return lead
|
|
1724
1746
|
}
|
|
1725
1747
|
const demoted = rules.some(function bareElsewhere(rule) {
|
|
1726
1748
|
const column = rule.finds[position]
|
|
1727
1749
|
return column !== undefined && column.slot !== undefined && column.slot.class !== slot.class
|
|
1728
1750
|
})
|
|
1729
|
-
|
|
1751
|
+
const field = slot.field
|
|
1752
|
+
const widened =
|
|
1753
|
+
field.kind === "interval" &&
|
|
1754
|
+
rules.some((rule) => {
|
|
1755
|
+
const other = rule.finds[position]?.slot?.field
|
|
1756
|
+
return other?.kind === "interval" && other.width !== field.width
|
|
1757
|
+
})
|
|
1758
|
+
if (!demoted && !widened) {
|
|
1730
1759
|
return lead
|
|
1731
1760
|
}
|
|
1732
1761
|
return Object.freeze({
|
|
1733
1762
|
name: lead.name,
|
|
1734
1763
|
entry: lead.entry,
|
|
1735
1764
|
closed: lead.closed,
|
|
1736
|
-
slot: Object.freeze({
|
|
1765
|
+
slot: Object.freeze({
|
|
1766
|
+
field: widened ? Object.freeze({ ...field, width: undefined }) : field,
|
|
1767
|
+
class: demoted ? undefined : slot.class
|
|
1768
|
+
})
|
|
1737
1769
|
})
|
|
1738
1770
|
})
|
|
1739
1771
|
return Object.freeze(merged)
|
|
@@ -2211,7 +2243,6 @@ function lowerComputeNode(node: QueryNode, ids: VarIds): ScalarExprIr {
|
|
|
2211
2243
|
|
|
2212
2244
|
function lowerComputeGrammar(node: QueryNode, ids: VarIds): ScalarExprIr {
|
|
2213
2245
|
return scalarWire(node, (leaf) => {
|
|
2214
|
-
if (leaf.kind !== "var") throw new AuthoringError({ message: "query expressions require bound variables" })
|
|
2215
2246
|
return ids.of(leaf.ref as AnyVar)
|
|
2216
2247
|
})
|
|
2217
2248
|
}
|
package/src/query/parse-ir.ts
CHANGED
|
@@ -231,9 +231,8 @@ function align(context: string, head: readonly HeadTermIr[], rules: readonly Rul
|
|
|
231
231
|
if (rule.finds.length !== head.length) fail(`${context}.rules[${index}]`, "finds width does not match head width")
|
|
232
232
|
for (const [position, find] of rule.finds.entries()) {
|
|
233
233
|
const term = head[position]
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
if (term?.kind !== family)
|
|
234
|
+
const projects = find.kind === "var" || find.kind === "compute" || find.kind === "segments"
|
|
235
|
+
if (term === undefined || (term.kind === "aggregate") === projects)
|
|
237
236
|
fail(`${context}.rules[${index}].finds[${position}]`, "find family does not match head")
|
|
238
237
|
if (term.kind === "aggregate") {
|
|
239
238
|
const op = find.kind === "aggregate" ? find.op.kind : find.kind
|
|
@@ -280,7 +279,10 @@ function parseQueryIr(input: unknown): ParsedQuery {
|
|
|
280
279
|
if (rec.length === 0) return fail("parseQueryIr", "rec step is empty")
|
|
281
280
|
align("rec base", headRec, base)
|
|
282
281
|
align("rec step", headRec, rec)
|
|
283
|
-
if (
|
|
282
|
+
if (
|
|
283
|
+
headRec.some((term) => term.kind !== "var") ||
|
|
284
|
+
[...base, ...rec].some((rule) => rule.negated.length !== 0 || rule.finds.some((find) => find.kind !== "var"))
|
|
285
|
+
)
|
|
284
286
|
return fail("query.rec", "recursion requires projection-only heads and no negation")
|
|
285
287
|
owned = { kind: raw.kind, interiors, head, rules, rec: Object.freeze({ head: headRec, base, rec }) }
|
|
286
288
|
}
|
package/src/query/scope.ts
CHANGED
|
@@ -369,7 +369,14 @@ function fieldAntiJoins(a: ClassedField, b: ClassedField): boolean {
|
|
|
369
369
|
* joins cannot inherit provenance a rule never proved.
|
|
370
370
|
*/
|
|
371
371
|
function headFieldJoins(a: ClassedField, b: ClassedField): boolean {
|
|
372
|
-
return
|
|
372
|
+
return (
|
|
373
|
+
fieldJoins(a, b) ||
|
|
374
|
+
u64BareWirePair(a, b) ||
|
|
375
|
+
(a.class === b.class &&
|
|
376
|
+
a.field.kind === "interval" &&
|
|
377
|
+
b.field.kind === "interval" &&
|
|
378
|
+
a.field.element === b.field.element)
|
|
379
|
+
)
|
|
373
380
|
}
|
|
374
381
|
|
|
375
382
|
function renderFieldKind(slot: ClassedField): string {
|
package/src/runtime-codes.ts
CHANGED
package/src/runtime-errors.ts
CHANGED
|
@@ -32,11 +32,18 @@ const Io = Schema.Struct({
|
|
|
32
32
|
kind: Schema.String,
|
|
33
33
|
osCode: Schema.optional(Schema.Number)
|
|
34
34
|
})
|
|
35
|
-
|
|
35
|
+
const StatementDiagnostic = Schema.Struct({ id: Schema.Number, descriptor: Schema.String })
|
|
36
|
+
const SchemaDiagnostic = Schema.Struct({
|
|
37
|
+
statement: StatementDiagnostic,
|
|
38
|
+
conflict: Schema.optional(StatementDiagnostic)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
/** A typed engine refusal, including native statement coordinates when known. */
|
|
36
42
|
const Engine = Schema.Struct({
|
|
37
43
|
_tag: Schema.Literal("Engine"),
|
|
38
44
|
kind: Schema.String,
|
|
39
|
-
message: Schema.String
|
|
45
|
+
message: Schema.String,
|
|
46
|
+
diagnostic: Schema.optional(SchemaDiagnostic)
|
|
40
47
|
})
|
|
41
48
|
export const DbReason = Schema.Union([ResourceLimit, Io, Engine, InvalidArgument, PlainReason])
|
|
42
49
|
export class DbError extends Schema.TaggedError<DbError>()("DbError", {
|
|
@@ -68,7 +75,13 @@ export function dbError(operation: string, cause: unknown): DbError {
|
|
|
68
75
|
"message" in cause &&
|
|
69
76
|
typeof cause.message === "string"
|
|
70
77
|
) {
|
|
71
|
-
|
|
78
|
+
const reason = decodeReason({
|
|
79
|
+
_tag: "Engine",
|
|
80
|
+
kind: cause.kind,
|
|
81
|
+
message: cause.message,
|
|
82
|
+
diagnostic: "diagnostic" in cause ? cause.diagnostic : undefined
|
|
83
|
+
})
|
|
84
|
+
if (reason._tag === "Some") return new DbError({ operation, reason: reason.value })
|
|
72
85
|
}
|
|
73
86
|
return new DbError({ operation, reason: { _tag: "Internal" } })
|
|
74
87
|
}
|
package/src/runtime-native.ts
CHANGED
|
@@ -28,11 +28,6 @@ export interface DirectoryHandle {
|
|
|
28
28
|
readonly __directory: unique symbol
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
/** Stamped `NativeKind::RepositoryLock` mint — not a directory-owner twin. */
|
|
32
|
-
export interface RepositoryLockHandle {
|
|
33
|
-
readonly __repositoryLock: unique symbol
|
|
34
|
-
}
|
|
35
|
-
|
|
36
31
|
/** Resource kinds installed in native worker tables. */
|
|
37
32
|
export type NativeKind = "snapshot" | "prepared" | "result" | "cursor" | "draft" | "changes" | "repository-lock"
|
|
38
33
|
|
|
@@ -55,6 +50,7 @@ export type ManagedDbOutcome =
|
|
|
55
50
|
readonly tag: "refused"
|
|
56
51
|
readonly kind: "schemaError" | "newtypeMismatch" | "fingerprintMismatch" | "destinationExists"
|
|
57
52
|
readonly message: string
|
|
53
|
+
readonly diagnostic?: Extract<import("#runtime-errors.ts").DbError["reason"], { _tag: "Engine" }>["diagnostic"]
|
|
58
54
|
}
|
|
59
55
|
|
|
60
56
|
export type LogTakeWire =
|
|
@@ -179,10 +175,6 @@ interface RuntimeNative {
|
|
|
179
175
|
callback: () => void
|
|
180
176
|
): OperationHandle
|
|
181
177
|
runtimeLogTake(operation: OperationHandle): LogTakeWire
|
|
182
|
-
/** L14 mint: `Runtime::mint_repository_lock` stamps `NativeKind::RepositoryLock` at take. */
|
|
183
|
-
logRepositoryLockAcquire(runtime: RuntimeHandle, directory: string, callback: () => void): OperationHandle
|
|
184
|
-
logRepositoryLockTake(operation: OperationHandle): RepositoryLockHandle
|
|
185
|
-
logRepositoryLockRelease(owner: RepositoryLockHandle, callback: (report: CloseWire) => void): void
|
|
186
178
|
}
|
|
187
179
|
|
|
188
180
|
// The checked source/fresh-addon roster test pins this private declaration.
|
package/src/runtime.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Context, Duration, Effect, Exit, Layer } from "effect"
|
|
2
2
|
import type { CloseReport, OutstandingWork } from "#runtime-errors.ts"
|
|
3
3
|
import { CloseFailure, DbError, dbError } from "#runtime-errors.ts"
|
|
4
|
-
import type { CloseWire, OperationHandle, OptionsWire,
|
|
4
|
+
import type { CloseWire, OperationHandle, OptionsWire, RuntimeHandle } from "#runtime-native.ts"
|
|
5
5
|
import { runtimeNative } from "#runtime-native.ts"
|
|
6
6
|
|
|
7
7
|
export interface NativeRuntimeOptions {
|
|
@@ -283,75 +283,6 @@ export const runtimeHandle = Effect.fn("NativeRuntime.handle")(function* () {
|
|
|
283
283
|
return handle
|
|
284
284
|
})
|
|
285
285
|
|
|
286
|
-
/**
|
|
287
|
-
* Opaque kernel fence (C8). Backed by L14 `log_repository_lock_*` and
|
|
288
|
-
* L12 `mint_repository_lock` (`NativeKind::RepositoryLock`). Native
|
|
289
|
-
* exclusion is the owner — there is no JS occupancy table. `release`
|
|
290
|
-
* is the one close: it joins `logRepositoryLockRelease` then clears
|
|
291
|
-
* `slot.owner` so the Scope finalizer is a no-op. Callers join host
|
|
292
|
-
* I/O first (`joinPendingIo.pipe(Effect.andThen(lock.release))`).
|
|
293
|
-
*/
|
|
294
|
-
export interface RepositoryLock {
|
|
295
|
-
readonly directory: string
|
|
296
|
-
readonly release: Effect.Effect<void>
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function joinLockRelease(operation: string, owner: RepositoryLockHandle): Effect.Effect<void> {
|
|
300
|
-
return drain(operation, (callback) => runtimeNative.logRepositoryLockRelease(owner, callback)).pipe(
|
|
301
|
-
Effect.flatMap((report) => finalizeClose(operation, report))
|
|
302
|
-
)
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Internal log seam: stamped mint only. Cleanup is registered before
|
|
307
|
-
* the interruptible acquire: `acquireRelease({ interruptible })`
|
|
308
|
-
* installs the finalizer only after acquire succeeds. Interrupt, defect,
|
|
309
|
-
* and acquire failure all run the same slot finalizer — empty slot is
|
|
310
|
-
* a no-op. No `runtimeDirectoryAcquire`, no core `Db`, no JS bookkeeping.
|
|
311
|
-
*/
|
|
312
|
-
export const internalAcquireRepositoryLock = Effect.fn("internalAcquireRepositoryLock")(function* (
|
|
313
|
-
operation: string,
|
|
314
|
-
directory: string
|
|
315
|
-
) {
|
|
316
|
-
if (directory.length === 0) {
|
|
317
|
-
return yield* Effect.fail(invalid(operation))
|
|
318
|
-
}
|
|
319
|
-
const runtime = yield* runtimeHandle()
|
|
320
|
-
const closeOp = `${operation}.repositoryLock`
|
|
321
|
-
return yield* Effect.uninterruptibleMask((restore) =>
|
|
322
|
-
Effect.gen(function* () {
|
|
323
|
-
const slot: { owner: RepositoryLockHandle | undefined } = { owner: undefined }
|
|
324
|
-
const release = Effect.suspend(() => {
|
|
325
|
-
const held = slot.owner
|
|
326
|
-
if (held === undefined) {
|
|
327
|
-
return Effect.void
|
|
328
|
-
}
|
|
329
|
-
return joinLockRelease(closeOp, held).pipe(
|
|
330
|
-
Effect.ensuring(
|
|
331
|
-
Effect.sync(() => {
|
|
332
|
-
slot.owner = undefined
|
|
333
|
-
})
|
|
334
|
-
)
|
|
335
|
-
)
|
|
336
|
-
})
|
|
337
|
-
yield* Effect.addFinalizer(() => release)
|
|
338
|
-
const owner = yield* restore(
|
|
339
|
-
nativeOperationWith(
|
|
340
|
-
operation,
|
|
341
|
-
(callback) => runtimeNative.logRepositoryLockAcquire(runtime, directory, callback),
|
|
342
|
-
runtimeNative.logRepositoryLockTake,
|
|
343
|
-
(value) => value
|
|
344
|
-
)
|
|
345
|
-
)
|
|
346
|
-
slot.owner = owner
|
|
347
|
-
return Object.freeze({
|
|
348
|
-
directory,
|
|
349
|
-
release
|
|
350
|
-
})
|
|
351
|
-
})
|
|
352
|
-
)
|
|
353
|
-
})
|
|
354
|
-
|
|
355
286
|
/** Internal first executor consumer; not a replacement row codec or public hash API. */
|
|
356
287
|
export const hashChunk = Effect.fn("bumbledb.hashChunk")(function* (input: Uint8Array) {
|
|
357
288
|
const runtime = yield* NativeRuntime
|