@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/result.ts
CHANGED
|
@@ -5,31 +5,22 @@ import { dbNative } from "#db-native.ts"
|
|
|
5
5
|
import type { FindColumn } from "#query/atom.ts"
|
|
6
6
|
import { decodeAnswers } from "#query/run.ts"
|
|
7
7
|
import type { CellValue } from "#rows.ts"
|
|
8
|
-
import
|
|
9
|
-
import { deliveryResultBytes, nativeOperationWith, policyWire } from "#runtime.ts"
|
|
8
|
+
import { nativeOperationWith } from "#runtime.ts"
|
|
10
9
|
import type { CloseReport, DbError } from "#runtime-errors.ts"
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* One complete answer set, sealed after evaluation and independent of its
|
|
13
|
+
* source snapshot. collect() explicitly materializes all rows into JS and
|
|
14
|
+
* leaves the result available. pages() consumes it once through bounded
|
|
15
|
+
* delivery batches; it does not stream query execution.
|
|
17
16
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* `pages`. `pages` is a one-shot consuming stream over the
|
|
22
|
-
* completed result: its first execution atomically spends the result and
|
|
23
|
-
* moves the backing storage into a private cursor owned by the stream's
|
|
24
|
-
* scope — construction alone spends nothing, a second run fails
|
|
25
|
-
* `SpentHandle`, and a run after the result's scope closed fails
|
|
26
|
-
* `ClosedHandle`. Early take, downstream failure and interruption all
|
|
27
|
-
* close/drain the private cursor; EOF cleanup is identical. There is no
|
|
28
|
-
* public cursor, `next`, AsyncIterable, clone or second streaming API.
|
|
17
|
+
* The stream owns its cursor through Effect scope. Early termination,
|
|
18
|
+
* failure, interruption and EOF close/drain it. Creating the stream does
|
|
19
|
+
* not spend the result; running it twice fails with SpentHandle.
|
|
29
20
|
*/
|
|
30
21
|
interface CompleteResult<A> {
|
|
31
|
-
collect(
|
|
32
|
-
pages(
|
|
22
|
+
collect(): Effect.Effect<ReadonlyArray<A>, DbError>
|
|
23
|
+
pages(): Stream.Stream<ReadonlyArray<A>, DbError>
|
|
33
24
|
close(): Effect.Effect<CloseReport>
|
|
34
25
|
}
|
|
35
26
|
|
|
@@ -52,42 +43,27 @@ function decodePage<A>(finds: readonly FindColumn[], rows: readonly (readonly Ce
|
|
|
52
43
|
|
|
53
44
|
/**
|
|
54
45
|
* Internal constructor: `db.ts` publishes results through this after
|
|
55
|
-
* execution completes.
|
|
56
|
-
* operation under the caller's delivery policy — never the completed query's
|
|
57
|
-
* expired execution deadline.
|
|
46
|
+
* execution completes. Each delivery has independent cooperative cancellation.
|
|
58
47
|
*/
|
|
59
48
|
function makeCompleteResult<A>(handle: ResultHandle, finds: readonly FindColumn[]): CompleteResult<A> {
|
|
60
|
-
function deliveryWire(operation: string, delivery: ExecutionPolicy, requested: bigint) {
|
|
61
|
-
return policyWire({ ...delivery, resultBytes: deliveryResultBytes(requested, delivery) }, operation)
|
|
62
|
-
}
|
|
63
49
|
const value: CompleteResult<A> = {
|
|
64
|
-
collect(
|
|
50
|
+
collect() {
|
|
65
51
|
return Effect.suspend(() =>
|
|
66
52
|
nativeOperationWith(
|
|
67
53
|
"CompleteResult.collect",
|
|
68
|
-
(callback) =>
|
|
69
|
-
dbNative.runtimeResultCollect(
|
|
70
|
-
handle,
|
|
71
|
-
deliveryWire("CompleteResult.collect", work, options.maxBytes),
|
|
72
|
-
callback
|
|
73
|
-
),
|
|
54
|
+
(callback) => dbNative.runtimeResultCollect(handle, callback),
|
|
74
55
|
dbNative.runtimeRowsTake,
|
|
75
56
|
(rows) => decodePage<A>(finds, rows)
|
|
76
57
|
)
|
|
77
58
|
)
|
|
78
59
|
},
|
|
79
|
-
pages(
|
|
60
|
+
pages() {
|
|
80
61
|
return Stream.unwrap(
|
|
81
62
|
Effect.gen(function* () {
|
|
82
63
|
const cursor: CursorHandle = yield* Effect.acquireRelease(
|
|
83
64
|
nativeOperationWith(
|
|
84
65
|
"CompleteResult.pages",
|
|
85
|
-
(callback) =>
|
|
86
|
-
dbNative.runtimeResultCursor(
|
|
87
|
-
handle,
|
|
88
|
-
deliveryWire("CompleteResult.pages", work, options.pageBytes),
|
|
89
|
-
callback
|
|
90
|
-
),
|
|
66
|
+
(callback) => dbNative.runtimeResultCursor(handle, callback),
|
|
91
67
|
dbNative.runtimeCursorTake,
|
|
92
68
|
(taken) => taken
|
|
93
69
|
),
|
|
@@ -97,12 +73,7 @@ function makeCompleteResult<A>(handle: ResultHandle, finds: readonly FindColumn[
|
|
|
97
73
|
return Stream.paginate(undefined, () =>
|
|
98
74
|
nativeOperationWith(
|
|
99
75
|
"CompleteResult.page",
|
|
100
|
-
(callback) =>
|
|
101
|
-
dbNative.runtimeCursorNext(
|
|
102
|
-
cursor,
|
|
103
|
-
deliveryWire("CompleteResult.page", work, options.pageBytes),
|
|
104
|
-
callback
|
|
105
|
-
),
|
|
76
|
+
(callback) => dbNative.runtimeCursorNext(cursor, callback),
|
|
106
77
|
dbNative.runtimePageTake,
|
|
107
78
|
(page) => page
|
|
108
79
|
).pipe(
|
package/src/rows.ts
CHANGED
|
@@ -17,12 +17,9 @@ import { AuthoringError, SdkInvariantError } from "#errors.ts"
|
|
|
17
17
|
* scans and byte copies.
|
|
18
18
|
*/
|
|
19
19
|
import type { AnyClosedRoster, AnyField } from "#fields.ts"
|
|
20
|
-
import {
|
|
21
|
-
import type
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
/** Converter-granularity host cell wall (64 KiB). One oversize cell refuses. */
|
|
25
|
-
const HOST_CELL_MAX = 65536n
|
|
20
|
+
import { literalShapeError, rosterOf } from "#fields.ts"
|
|
21
|
+
import { type AnyRelation, type Fact, relationFields } from "#relation.ts"
|
|
22
|
+
import { fieldValue, recordValue } from "#values.ts"
|
|
26
23
|
|
|
27
24
|
/**
|
|
28
25
|
* One owned cell at the private bridge boundary. The declared sealed field
|
|
@@ -44,29 +41,18 @@ type CellValue =
|
|
|
44
41
|
interface FlatRows {
|
|
45
42
|
readonly rows: bigint
|
|
46
43
|
readonly cells: readonly CellValue[]
|
|
47
|
-
/** Conservative charged byte size of the owned cells (input accounting). */
|
|
48
|
-
readonly bytes: bigint
|
|
49
44
|
}
|
|
50
45
|
|
|
51
46
|
function recordOf(fact: object): Readonly<Record<string, unknown>> {
|
|
52
|
-
if (typeof fact !== "object"
|
|
53
|
-
throw new AuthoringError({ message: "
|
|
47
|
+
if (typeof fact !== "object" || fact === null) {
|
|
48
|
+
throw new AuthoringError({ message: "expected a fact record" })
|
|
54
49
|
}
|
|
55
50
|
return fact as Readonly<Record<string, unknown>>
|
|
56
51
|
}
|
|
57
52
|
|
|
58
|
-
function refuseShared(context: string, value: Uint8Array): void {
|
|
59
|
-
if (!(value.buffer instanceof ArrayBuffer)) {
|
|
60
|
-
throw new AuthoringError({
|
|
61
|
-
message: `${context}: SharedArrayBuffer-backed views are refused — make a synchronized stable copy into ordinary unshared input first`
|
|
62
|
-
})
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
53
|
/**
|
|
67
|
-
*
|
|
68
|
-
* two bytes per UTF-16 code unit, not their UTF-8 wire size.
|
|
69
|
-
* measures and bounds UTF-8 separately; byte views charge their length.
|
|
54
|
+
* Approximate payload size for host batching, not a memory allowance.
|
|
55
|
+
* Strings use two bytes per UTF-16 code unit, not their UTF-8 wire size.
|
|
70
56
|
*/
|
|
71
57
|
function hostCellCharge(value: unknown): bigint {
|
|
72
58
|
if (typeof value === "string") {
|
|
@@ -87,36 +73,11 @@ function hostCellCharge(value: unknown): bigint {
|
|
|
87
73
|
return 0n
|
|
88
74
|
}
|
|
89
75
|
|
|
90
|
-
function assertHostCellFits(context: string, value: unknown, limit: bigint): bigint {
|
|
91
|
-
const charge = hostCellCharge(value)
|
|
92
|
-
if (charge > limit) {
|
|
93
|
-
throw new AuthoringError({
|
|
94
|
-
message: `${context}: host cell of ${charge} bytes exceeds the ${limit}-byte converter bound — refuse before copy or scan`
|
|
95
|
-
})
|
|
96
|
-
}
|
|
97
|
-
return charge
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* The write half of the closed bijection: one handle NAME to its u64 row
|
|
102
|
-
* id (declaration order = row ids). An unknown name is a pointed refusal
|
|
103
|
-
* naming the vocabulary and its roster.
|
|
104
|
-
*/
|
|
105
|
-
function closedCellOf(context: string, closed: AnyClosedRoster, name: string): CellValue {
|
|
106
|
-
const id = closed.handles.indexOf(name)
|
|
107
|
-
if (id === -1) {
|
|
108
|
-
throw new AuthoringError({
|
|
109
|
-
message: `${context}: "${name}" is not a handle of ${closed.name} — the roster is ${closed.handles.join(", ")}`
|
|
110
|
-
})
|
|
111
|
-
}
|
|
112
|
-
return BigInt(id)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
76
|
function handleOf(context: string, closed: AnyClosedRoster, cell: unknown): string {
|
|
116
77
|
if (typeof cell !== "bigint") {
|
|
117
78
|
throw literalShapeError(context, `a ${closed.name} handle id (bigint)`, cell)
|
|
118
79
|
}
|
|
119
|
-
const handle = closed.handles[Number(cell)]
|
|
80
|
+
const handle = cell < 0n || cell >= BigInt(closed.handles.length) ? undefined : closed.handles[Number(cell)]
|
|
120
81
|
if (handle === undefined) {
|
|
121
82
|
throw new AuthoringError({
|
|
122
83
|
message: `${context}: id ${cell} is outside the ${closed.name} roster (${closed.handles.join(", ")})`
|
|
@@ -125,227 +86,63 @@ function handleOf(context: string, closed: AnyClosedRoster, cell: unknown): stri
|
|
|
125
86
|
return handle
|
|
126
87
|
}
|
|
127
88
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return 1n
|
|
132
|
-
}
|
|
133
|
-
if (typeof cell === "bigint" || typeof cell === "number") {
|
|
134
|
-
return 8n
|
|
135
|
-
}
|
|
136
|
-
if (typeof cell === "string") {
|
|
137
|
-
// Account for the host string here; native admission separately
|
|
138
|
-
// checks and charges its exact UTF-8 encoding.
|
|
139
|
-
return BigInt(cell.length) * 2n
|
|
140
|
-
}
|
|
141
|
-
if (cell instanceof Uint8Array) {
|
|
142
|
-
return BigInt(cell.byteLength)
|
|
89
|
+
function cellOf(context: string, field: AnyField, value: unknown): CellValue {
|
|
90
|
+
if ("closed" in field) {
|
|
91
|
+
return BigInt(field.closed.handles.indexOf(fieldValue(context, field, value)))
|
|
143
92
|
}
|
|
144
|
-
return
|
|
93
|
+
return fieldValue(context, field, value)
|
|
145
94
|
}
|
|
146
95
|
|
|
147
|
-
function
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
case "bool": {
|
|
157
|
-
if (typeof value !== "boolean") {
|
|
158
|
-
throw literalShapeError(context, "boolean", value)
|
|
159
|
-
}
|
|
160
|
-
return value
|
|
161
|
-
}
|
|
162
|
-
case "u64":
|
|
163
|
-
case "i64": {
|
|
164
|
-
if (typeof value !== "bigint") {
|
|
165
|
-
throw literalShapeError(context, "bigint", value)
|
|
166
|
-
}
|
|
167
|
-
return value
|
|
168
|
-
}
|
|
169
|
-
case "str": {
|
|
170
|
-
if (typeof value !== "string") {
|
|
171
|
-
throw literalShapeError(context, "string", value)
|
|
172
|
-
}
|
|
173
|
-
assertHostCellFits(context, value, HOST_CELL_MAX)
|
|
174
|
-
if (!value.isWellFormed()) {
|
|
175
|
-
throw literalShapeError(context, "well-formed string", value)
|
|
176
|
-
}
|
|
177
|
-
return value
|
|
178
|
-
}
|
|
179
|
-
case "f64": {
|
|
180
|
-
if (typeof value !== "number") {
|
|
181
|
-
throw literalShapeError(context, "number", value)
|
|
182
|
-
}
|
|
183
|
-
return value
|
|
184
|
-
}
|
|
185
|
-
case "uuid": {
|
|
186
|
-
if (!Uuid.isUuid(value)) {
|
|
187
|
-
throw literalShapeError(context, "a UUID (canonical UUID text)", value)
|
|
188
|
-
}
|
|
189
|
-
// UUIDs cross as canonical hyphenated text, not raw bytes.
|
|
190
|
-
return value
|
|
191
|
-
}
|
|
192
|
-
case "bytes": {
|
|
193
|
-
if (!(value instanceof Uint8Array)) {
|
|
194
|
-
throw literalShapeError(context, "Uint8Array", value)
|
|
195
|
-
}
|
|
196
|
-
refuseShared(context, value)
|
|
197
|
-
assertHostCellFits(context, value, HOST_CELL_MAX)
|
|
198
|
-
if (value.byteLength !== field.width) {
|
|
199
|
-
throw new AuthoringError({
|
|
200
|
-
message: `${context}: bytes<${field.width}> takes exactly ${field.width} bytes (got ${value.byteLength})`
|
|
201
|
-
})
|
|
202
|
-
}
|
|
203
|
-
// Owned copy at the ownership boundary: later caller mutation
|
|
204
|
-
// cannot change an accepted cell.
|
|
205
|
-
return Uint8Array.from(value)
|
|
206
|
-
}
|
|
207
|
-
case "interval": {
|
|
208
|
-
if (field.element === "f64") {
|
|
209
|
-
if (!isFloatIntervalValue(value)) {
|
|
210
|
-
throw literalShapeError(context, "float interval ({ start, end } numbers)", value)
|
|
211
|
-
}
|
|
212
|
-
if (Number.isNaN(value.start) || Number.isNaN(value.end)) {
|
|
213
|
-
throw new AuthoringError({ message: `${context}: a float interval endpoint cannot be NaN` })
|
|
214
|
-
}
|
|
215
|
-
const start = Object.is(value.start, -0) ? 0 : value.start
|
|
216
|
-
const end = Object.is(value.end, -0) ? 0 : value.end
|
|
217
|
-
if (!(start < end)) {
|
|
218
|
-
throw new AuthoringError({
|
|
219
|
-
message: `${context}: a float interval is half-open and nonempty (start < end strictly)`
|
|
220
|
-
})
|
|
221
|
-
}
|
|
222
|
-
return { start, end }
|
|
223
|
-
}
|
|
224
|
-
if (!isIntervalValue(value)) {
|
|
225
|
-
throw literalShapeError(context, "interval ({ start, end } bigints)", value)
|
|
226
|
-
}
|
|
227
|
-
return { start: value.start, end: value.end }
|
|
228
|
-
}
|
|
229
|
-
}
|
|
96
|
+
function factCellsOf(data: AnyRelation, fact: unknown): CellValue[] {
|
|
97
|
+
const record = recordValue(
|
|
98
|
+
`relation ${data.name}`,
|
|
99
|
+
fact,
|
|
100
|
+
relationFields(data).map((declared) => declared.name)
|
|
101
|
+
)
|
|
102
|
+
return relationFields(data).map((declared) =>
|
|
103
|
+
cellOf(`relation ${data.name} field ${declared.name}`, declared.field, record[declared.name])
|
|
104
|
+
)
|
|
230
105
|
}
|
|
231
106
|
|
|
232
107
|
/**
|
|
233
108
|
* The flat projector: every fact's cells land in ONE row-major cell array
|
|
234
|
-
* (length rows × arity)
|
|
235
|
-
*
|
|
109
|
+
* (length rows × arity), counting rows while projecting.
|
|
110
|
+
* Missing-field refusal and per-cell judgment are
|
|
236
111
|
* {@link cellOf}'s, byte for byte.
|
|
237
112
|
*/
|
|
238
|
-
function flatRowsOf(data:
|
|
113
|
+
function flatRowsOf(data: AnyRelation, facts: Iterable<object>): FlatRows {
|
|
239
114
|
const cells: CellValue[] = []
|
|
240
115
|
let rows = 0n
|
|
241
|
-
let bytes = 0n
|
|
242
116
|
for (const fact of facts) {
|
|
117
|
+
for (const cell of factCellsOf(data, fact)) cells.push(cell)
|
|
243
118
|
rows += 1n
|
|
244
|
-
const record = recordOf(fact)
|
|
245
|
-
for (const declared of data.fields) {
|
|
246
|
-
const value = record[declared.name]
|
|
247
|
-
if (value === undefined) {
|
|
248
|
-
throw new AuthoringError({ message: `relation ${data.name}: fact is missing field ${declared.name}` })
|
|
249
|
-
}
|
|
250
|
-
const cell = cellOf(`relation ${data.name} field ${declared.name}`, declared.field, value)
|
|
251
|
-
bytes += cellBytes(cell)
|
|
252
|
-
cells.push(cell)
|
|
253
|
-
}
|
|
254
119
|
}
|
|
255
|
-
return { rows, cells
|
|
120
|
+
return { rows, cells }
|
|
256
121
|
}
|
|
257
122
|
|
|
258
123
|
function keyCellsOf(
|
|
259
|
-
data:
|
|
124
|
+
data: AnyRelation,
|
|
260
125
|
projection: readonly string[],
|
|
261
|
-
|
|
126
|
+
input: Readonly<Record<string, unknown>>
|
|
262
127
|
): CellValue[] {
|
|
263
|
-
|
|
264
|
-
if (key[supplied] !== undefined && !projection.includes(supplied)) {
|
|
265
|
-
throw new AuthoringError({
|
|
266
|
-
message: `relation ${data.name}: key object carries field ${supplied} outside the primary key projection (${projection.join(", ")})`
|
|
267
|
-
})
|
|
268
|
-
}
|
|
269
|
-
}
|
|
128
|
+
const key = recordValue(`relation ${data.name} key`, input, projection)
|
|
270
129
|
return projection.map(function marshalKeyCell(fieldName) {
|
|
271
|
-
const declared = data.
|
|
272
|
-
return candidate.name === fieldName
|
|
273
|
-
})
|
|
130
|
+
const declared = relationFields(data).find((candidate) => candidate.name === fieldName)
|
|
274
131
|
if (declared === undefined) {
|
|
275
|
-
throw new AuthoringError({
|
|
276
|
-
message: `relation ${data.name}: key projection cites unknown field ${fieldName}`
|
|
277
|
-
})
|
|
132
|
+
throw new AuthoringError({ message: `relation ${data.name}: key projection cites unknown field ${fieldName}` })
|
|
278
133
|
}
|
|
279
|
-
|
|
280
|
-
if (value === undefined) {
|
|
281
|
-
throw new AuthoringError({
|
|
282
|
-
message: `relation ${data.name}: key object is missing field ${fieldName} — get reads through the primary (first-declared) key, whose projection is (${projection.join(", ")})`
|
|
283
|
-
})
|
|
284
|
-
}
|
|
285
|
-
return cellOf(`relation ${data.name} key field ${fieldName}`, declared.field, value)
|
|
134
|
+
return cellOf(`relation ${data.name} key field ${fieldName}`, declared.field, key[fieldName])
|
|
286
135
|
})
|
|
287
136
|
}
|
|
288
137
|
|
|
289
138
|
function decodeCell(context: string, field: AnyField, cell: unknown): unknown {
|
|
290
139
|
const roster = rosterOf(field)
|
|
291
|
-
if (roster !== undefined)
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
throw new SdkInvariantError({ message: `${context}: expected boolean cell` })
|
|
298
|
-
}
|
|
299
|
-
return cell
|
|
300
|
-
}
|
|
301
|
-
case "u64":
|
|
302
|
-
case "i64": {
|
|
303
|
-
if (typeof cell !== "bigint") {
|
|
304
|
-
throw new SdkInvariantError({ message: `${context}: expected bigint cell` })
|
|
305
|
-
}
|
|
306
|
-
return cell
|
|
307
|
-
}
|
|
308
|
-
case "f64": {
|
|
309
|
-
if (typeof cell !== "number") {
|
|
310
|
-
throw new SdkInvariantError({ message: `${context}: expected number cell` })
|
|
311
|
-
}
|
|
312
|
-
return cell
|
|
313
|
-
}
|
|
314
|
-
case "str": {
|
|
315
|
-
if (typeof cell !== "string") {
|
|
316
|
-
throw new SdkInvariantError({ message: `${context}: expected string cell` })
|
|
317
|
-
}
|
|
318
|
-
return cell
|
|
319
|
-
}
|
|
320
|
-
case "uuid": {
|
|
321
|
-
// The native renderer spells uuid as the canonical
|
|
322
|
-
// canonical hyphenated UUID string (marshal.rs `uuid_hex`); the read
|
|
323
|
-
// side revalidates the spelling and keeps the string value.
|
|
324
|
-
if (!Uuid.isUuid(cell)) {
|
|
325
|
-
throw new SdkInvariantError({
|
|
326
|
-
message: `${context}: expected an uuid cell (canonical UUID text)`
|
|
327
|
-
})
|
|
328
|
-
}
|
|
329
|
-
return cell
|
|
330
|
-
}
|
|
331
|
-
case "bytes": {
|
|
332
|
-
if (!(cell instanceof Uint8Array)) {
|
|
333
|
-
throw new SdkInvariantError({ message: `${context}: expected owned bytes` })
|
|
334
|
-
}
|
|
335
|
-
return cell
|
|
336
|
-
}
|
|
337
|
-
case "interval": {
|
|
338
|
-
if (field.element === "f64") {
|
|
339
|
-
if (!isFloatIntervalValue(cell)) {
|
|
340
|
-
throw new SdkInvariantError({ message: `${context}: expected a float interval cell` })
|
|
341
|
-
}
|
|
342
|
-
return Object.freeze({ start: cell.start, end: cell.end })
|
|
343
|
-
}
|
|
344
|
-
if (!isIntervalValue(cell)) {
|
|
345
|
-
throw new SdkInvariantError({ message: `${context}: expected an integer interval cell` })
|
|
346
|
-
}
|
|
347
|
-
return Object.freeze({ start: cell.start, end: cell.end })
|
|
348
|
-
}
|
|
140
|
+
if (roster !== undefined) return handleOf(context, roster, cell)
|
|
141
|
+
try {
|
|
142
|
+
return fieldValue(context, field, cell)
|
|
143
|
+
} catch (cause) {
|
|
144
|
+
if (cause instanceof AuthoringError) throw new SdkInvariantError({ message: cause.message })
|
|
145
|
+
throw cause
|
|
349
146
|
}
|
|
350
147
|
}
|
|
351
148
|
|
|
@@ -353,7 +150,7 @@ function isCompleteFact<R extends AnyRelation>(
|
|
|
353
150
|
relation: R,
|
|
354
151
|
decoded: Readonly<Record<string, unknown>>
|
|
355
152
|
): decoded is Readonly<Record<string, unknown>> & Fact<R> {
|
|
356
|
-
return relation.
|
|
153
|
+
return relationFields(relation).every(function present(declared) {
|
|
357
154
|
return decoded[declared.name] !== undefined
|
|
358
155
|
})
|
|
359
156
|
}
|
|
@@ -363,21 +160,22 @@ function isCompleteFact<R extends AnyRelation>(
|
|
|
363
160
|
* declared field order: stable field names and shapes, no Proxy or per-cell closures.
|
|
364
161
|
*/
|
|
365
162
|
function factOfCells<R extends AnyRelation>(relation: R, row: readonly unknown[]): Fact<R> {
|
|
366
|
-
const data = relation
|
|
367
|
-
if (row.length !== data.
|
|
163
|
+
const data = relation
|
|
164
|
+
if (row.length !== relationFields(data).length) {
|
|
368
165
|
throw new SdkInvariantError({
|
|
369
|
-
message: `relation ${data.name}: row arity ${row.length} does not match the ${data.
|
|
166
|
+
message: `relation ${data.name}: row arity ${row.length} does not match the ${relationFields(data).length} declared fields`
|
|
370
167
|
})
|
|
371
168
|
}
|
|
372
169
|
const decoded: Record<string, unknown> = {}
|
|
373
|
-
data.
|
|
170
|
+
relationFields(data).forEach(function decodeOne(declared, ordinal) {
|
|
374
171
|
const cell = row[ordinal]
|
|
375
172
|
if (cell === undefined) {
|
|
376
173
|
throw new SdkInvariantError({
|
|
377
174
|
message: `relation ${data.name}: row cell ${ordinal} (${declared.name}) is absent`
|
|
378
175
|
})
|
|
379
176
|
}
|
|
380
|
-
|
|
177
|
+
const value = decodeCell(`relation ${data.name} field ${declared.name}`, declared.field, cell)
|
|
178
|
+
setOwnField(decoded, declared.name, value)
|
|
381
179
|
})
|
|
382
180
|
Object.freeze(decoded)
|
|
383
181
|
if (!isCompleteFact(relation, decoded)) {
|
|
@@ -386,16 +184,25 @@ function factOfCells<R extends AnyRelation>(relation: R, row: readonly unknown[]
|
|
|
386
184
|
return decoded
|
|
387
185
|
}
|
|
388
186
|
|
|
187
|
+
/** Preserve every declared name as data, including JavaScript's prototype setter. */
|
|
188
|
+
function setOwnField(record: Record<string, unknown>, name: string, value: unknown): void {
|
|
189
|
+
if (name === "__proto__") {
|
|
190
|
+
Object.defineProperty(record, name, { value, enumerable: true, writable: true, configurable: true })
|
|
191
|
+
} else {
|
|
192
|
+
record[name] = value
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
389
196
|
export type { CellValue, FlatRows }
|
|
390
197
|
export {
|
|
391
|
-
assertHostCellFits,
|
|
392
|
-
cellBytes,
|
|
393
198
|
cellOf,
|
|
394
199
|
decodeCell,
|
|
200
|
+
factCellsOf,
|
|
395
201
|
factOfCells,
|
|
396
202
|
flatRowsOf,
|
|
397
203
|
handleOf,
|
|
398
204
|
hostCellCharge,
|
|
399
205
|
keyCellsOf,
|
|
400
|
-
recordOf
|
|
206
|
+
recordOf,
|
|
207
|
+
setOwnField
|
|
401
208
|
}
|
package/src/runtime-codes.ts
CHANGED
package/src/runtime-errors.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Schema } from "effect"
|
|
2
|
+
import { AuthoringDiagnostic, AuthoringError } from "#errors.ts"
|
|
2
3
|
import { runtimeErrorCodes } from "#runtime-codes.ts"
|
|
3
4
|
|
|
4
5
|
export { runtimeErrorCodes } from "#runtime-codes.ts"
|
|
@@ -12,7 +13,18 @@ const ResourceLimit = Schema.Struct({
|
|
|
12
13
|
})
|
|
13
14
|
const PlainReason = Schema.Struct({
|
|
14
15
|
_tag: Schema.Literals(
|
|
15
|
-
runtimeErrorCodes.filter(
|
|
16
|
+
runtimeErrorCodes.filter(
|
|
17
|
+
(code) => code !== "ResourceLimit" && code !== "Io" && code !== "Engine" && code !== "InvalidArgument"
|
|
18
|
+
)
|
|
19
|
+
)
|
|
20
|
+
})
|
|
21
|
+
const InvalidArgument = Schema.Struct({
|
|
22
|
+
_tag: Schema.Literal("InvalidArgument"),
|
|
23
|
+
detail: Schema.optional(
|
|
24
|
+
Schema.Struct({
|
|
25
|
+
message: Schema.String,
|
|
26
|
+
diagnostic: Schema.optional(AuthoringDiagnostic)
|
|
27
|
+
})
|
|
16
28
|
)
|
|
17
29
|
})
|
|
18
30
|
const Io = Schema.Struct({
|
|
@@ -26,7 +38,7 @@ const Engine = Schema.Struct({
|
|
|
26
38
|
kind: Schema.String,
|
|
27
39
|
message: Schema.String
|
|
28
40
|
})
|
|
29
|
-
export const DbReason = Schema.Union([ResourceLimit, Io, Engine, PlainReason])
|
|
41
|
+
export const DbReason = Schema.Union([ResourceLimit, Io, Engine, InvalidArgument, PlainReason])
|
|
30
42
|
export class DbError extends Schema.TaggedError<DbError>()("DbError", {
|
|
31
43
|
operation: Schema.String,
|
|
32
44
|
reason: DbReason
|
|
@@ -34,12 +46,44 @@ export class DbError extends Schema.TaggedError<DbError>()("DbError", {
|
|
|
34
46
|
get code() {
|
|
35
47
|
return this.reason._tag
|
|
36
48
|
}
|
|
49
|
+
/** Safe default display; callers explicitly inspect structured detail. */
|
|
50
|
+
override get message(): string {
|
|
51
|
+
return `${this.operation}: ${this.code}`
|
|
52
|
+
}
|
|
37
53
|
}
|
|
38
54
|
|
|
39
55
|
const decodeReason = Schema.decodeUnknownOption(DbReason)
|
|
40
56
|
export function dbError(operation: string, cause: unknown): DbError {
|
|
57
|
+
if (cause instanceof DbError) return cause
|
|
58
|
+
if (cause instanceof AuthoringError) return argumentError(operation, cause)
|
|
41
59
|
const decoded = decodeReason(cause)
|
|
42
|
-
|
|
60
|
+
if (decoded._tag === "Some") return new DbError({ operation, reason: decoded.value })
|
|
61
|
+
// Synchronous native schema/marshalling refusals precede the executor's
|
|
62
|
+
// tagged error wire. Preserve their engine family instead of losing it.
|
|
63
|
+
if (
|
|
64
|
+
typeof cause === "object" &&
|
|
65
|
+
cause !== null &&
|
|
66
|
+
"kind" in cause &&
|
|
67
|
+
typeof cause.kind === "string" &&
|
|
68
|
+
"message" in cause &&
|
|
69
|
+
typeof cause.message === "string"
|
|
70
|
+
) {
|
|
71
|
+
return new DbError({ operation, reason: { _tag: "Engine", kind: cause.kind, message: cause.message } })
|
|
72
|
+
}
|
|
73
|
+
return new DbError({ operation, reason: { _tag: "Internal" } })
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Host input failures retain authoring details without displaying values. */
|
|
77
|
+
export function argumentError(operation: string, cause: unknown): DbError {
|
|
78
|
+
if (cause instanceof DbError) return cause
|
|
79
|
+
if (cause instanceof AuthoringError) {
|
|
80
|
+
const detail =
|
|
81
|
+
cause.diagnostic === undefined
|
|
82
|
+
? { message: cause.message }
|
|
83
|
+
: { message: cause.message, diagnostic: cause.diagnostic }
|
|
84
|
+
return new DbError({ operation, reason: { _tag: "InvalidArgument", detail } })
|
|
85
|
+
}
|
|
86
|
+
return new DbError({ operation, reason: { _tag: "InvalidArgument" } })
|
|
43
87
|
}
|
|
44
88
|
|
|
45
89
|
const Outstanding = Schema.Struct({
|
|
@@ -49,11 +93,7 @@ const Outstanding = Schema.Struct({
|
|
|
49
93
|
retained: Schema.BigInt,
|
|
50
94
|
owners: Schema.BigInt,
|
|
51
95
|
databases: Schema.BigInt,
|
|
52
|
-
natives: Schema.BigInt
|
|
53
|
-
inputBytes: Schema.BigInt,
|
|
54
|
-
workingBytes: Schema.BigInt,
|
|
55
|
-
scratchBytes: Schema.BigInt,
|
|
56
|
-
resultBytes: Schema.BigInt
|
|
96
|
+
natives: Schema.BigInt
|
|
57
97
|
})
|
|
58
98
|
export type OutstandingWork = typeof Outstanding.Type
|
|
59
99
|
const Close = Schema.Union([
|