@bjornpagen/bumbledb 0.20.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COOKBOOK.md +283 -1611
- package/README.md +156 -118
- package/dist/capacity.d.ts +16 -12
- package/dist/capacity.d.ts.map +1 -1
- package/dist/capacity.js +16 -48
- package/dist/capacity.js.map +1 -1
- package/dist/changes.d.ts +54 -0
- package/dist/changes.d.ts.map +1 -0
- package/dist/changes.js +245 -0
- package/dist/changes.js.map +1 -0
- package/dist/close.d.ts +24 -0
- package/dist/close.d.ts.map +1 -0
- package/dist/close.js +34 -0
- package/dist/close.js.map +1 -0
- package/dist/closed.d.ts +1 -3
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +24 -12
- package/dist/closed.js.map +1 -1
- package/dist/codec.d.ts +56 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +289 -0
- package/dist/codec.js.map +1 -0
- package/dist/compile.d.ts +68 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +75 -0
- package/dist/compile.js.map +1 -0
- package/dist/db-native.d.ts +170 -0
- package/dist/db-native.d.ts.map +1 -0
- package/dist/db-native.js +5 -0
- package/dist/db-native.js.map +1 -0
- package/dist/db.d.ts +101 -233
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +232 -1084
- package/dist/db.js.map +1 -1
- package/dist/errors.d.ts +57 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +32 -0
- package/dist/errors.js.map +1 -0
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +2 -2
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +65 -24
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +94 -24
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +37 -32
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -21
- package/dist/index.js.map +1 -1
- package/dist/internal/log.d.ts +24 -0
- package/dist/internal/log.d.ts.map +1 -0
- package/dist/internal/log.js +10 -0
- package/dist/internal/log.js.map +1 -0
- package/dist/law.d.ts +8 -13
- package/dist/law.d.ts.map +1 -1
- package/dist/law.js +6 -59
- package/dist/law.js.map +1 -1
- package/dist/lower.d.ts +0 -13
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +9 -17
- package/dist/lower.js.map +1 -1
- package/dist/migration.d.ts +33 -0
- package/dist/migration.d.ts.map +1 -0
- package/dist/migration.js +48 -0
- package/dist/migration.js.map +1 -0
- package/dist/native.d.ts +256 -301
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +65 -108
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +24 -13
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +7 -3
- package/dist/query/atom.js.map +1 -1
- package/dist/query/compute.d.ts +68 -0
- package/dist/query/compute.d.ts.map +1 -0
- package/dist/query/compute.js +141 -0
- package/dist/query/compute.js.map +1 -0
- package/dist/query/find.d.ts +15 -8
- package/dist/query/find.d.ts.map +1 -1
- package/dist/query/find.js +6 -2
- package/dist/query/find.js.map +1 -1
- package/dist/query/lower.d.ts +23 -2
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +433 -80
- package/dist/query/lower.js.map +1 -1
- package/dist/query/parse-ir.d.ts.map +1 -1
- package/dist/query/parse-ir.js +17 -8
- package/dist/query/parse-ir.js.map +1 -1
- package/dist/query/run.d.ts +12 -2
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +26 -9
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +47 -3
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +83 -19
- package/dist/query/scope.js.map +1 -1
- package/dist/relation.d.ts +3 -8
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +16 -10
- package/dist/relation.js.map +1 -1
- package/dist/result.d.ts +47 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +50 -0
- package/dist/result.js.map +1 -0
- package/dist/rows.d.ts +69 -0
- package/dist/rows.d.ts.map +1 -0
- package/dist/rows.js +328 -0
- package/dist/rows.js.map +1 -0
- package/dist/runtime-codes.d.ts +2 -0
- package/dist/runtime-codes.d.ts.map +1 -0
- package/dist/runtime-codes.js +19 -0
- package/dist/runtime-codes.js.map +1 -0
- package/dist/runtime-errors.d.ts +106 -0
- package/dist/runtime-errors.d.ts.map +1 -0
- package/dist/runtime-errors.js +62 -0
- package/dist/runtime-errors.js.map +1 -0
- package/dist/runtime-native.d.ts +171 -0
- package/dist/runtime-native.d.ts.map +1 -0
- package/dist/runtime-native.js +5 -0
- package/dist/runtime-native.js.map +1 -0
- package/dist/runtime.d.ts +73 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +293 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scalar.d.ts +165 -0
- package/dist/scalar.d.ts.map +1 -0
- package/dist/scalar.js +344 -0
- package/dist/scalar.js.map +1 -0
- package/dist/schema.d.ts +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +34 -37
- package/dist/schema.js.map +1 -1
- package/dist/shape.d.ts +28 -0
- package/dist/shape.d.ts.map +1 -0
- package/dist/shape.js +2 -0
- package/dist/shape.js.map +1 -0
- package/dist/spec.d.ts +27 -3
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js +22 -1
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +16 -3
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +54 -28
- package/dist/statements.js.map +1 -1
- package/dist/uuid.d.ts +23 -0
- package/dist/uuid.d.ts.map +1 -0
- package/dist/uuid.js +54 -0
- package/dist/uuid.js.map +1 -0
- package/pack-provenance.json +7 -0
- package/package.json +12 -16
- package/src/capacity.ts +34 -54
- package/src/changes.ts +361 -0
- package/src/close.ts +53 -0
- package/src/closed.ts +25 -19
- package/src/codec.ts +379 -0
- package/src/compile.ts +149 -0
- package/src/db-native.ts +247 -0
- package/src/db.ts +461 -1632
- package/src/errors.ts +50 -0
- package/src/face.ts +2 -2
- package/src/fields.ts +182 -55
- package/src/index.ts +62 -126
- package/src/internal/log.ts +51 -0
- package/src/law.ts +13 -28
- package/src/lower.ts +9 -4
- package/src/migration.ts +69 -0
- package/src/native.ts +326 -487
- package/src/query/atom.ts +61 -25
- package/src/query/compute.ts +237 -0
- package/src/query/find.ts +31 -12
- package/src/query/lower.ts +531 -143
- package/src/query/parse-ir.ts +18 -13
- package/src/query/run.ts +32 -16
- package/src/query/scope.ts +140 -8
- package/src/relation.ts +17 -21
- package/src/result.ts +130 -0
- package/src/rows.ts +404 -0
- package/src/runtime-codes.ts +18 -0
- package/src/runtime-errors.ts +69 -0
- package/src/runtime-native.ts +242 -0
- package/src/runtime.ts +440 -0
- package/src/scalar.ts +560 -0
- package/src/schema.ts +35 -37
- package/src/shape.ts +31 -0
- package/src/spec.ts +33 -3
- package/src/statements.ts +75 -68
- package/src/uuid.ts +69 -0
- package/dist/marshal.d.ts +0 -44
- package/dist/marshal.d.ts.map +0 -1
- package/dist/marshal.js +0 -165
- package/dist/marshal.js.map +0 -1
- package/src/marshal.ts +0 -200
package/src/query/parse-ir.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { AuthoringError } from "#errors.ts"
|
|
2
2
|
import type { FindTermIr, HeadTermIr, ParsedQuery, QueryIr, RuleIr } from "#native.ts"
|
|
3
3
|
|
|
4
4
|
function parseQueryIr(ir: QueryIr): ParsedQuery {
|
|
5
5
|
if (ir.rules.length === 0) {
|
|
6
|
-
throw
|
|
6
|
+
throw new AuthoringError({ message: "parseQueryIr: main rules are empty" })
|
|
7
7
|
}
|
|
8
8
|
align("query", ir.head, ir.rules)
|
|
9
9
|
ir.interiors.forEach(function checkInterior(interior, index) {
|
|
@@ -11,10 +11,10 @@ function parseQueryIr(ir: QueryIr): ParsedQuery {
|
|
|
11
11
|
})
|
|
12
12
|
if (ir.kind === "reach") {
|
|
13
13
|
if (ir.rec.base.length === 0) {
|
|
14
|
-
throw
|
|
14
|
+
throw new AuthoringError({ message: "parseQueryIr: rec base is empty" })
|
|
15
15
|
}
|
|
16
16
|
if (ir.rec.rec.length === 0) {
|
|
17
|
-
throw
|
|
17
|
+
throw new AuthoringError({ message: "parseQueryIr: rec step is empty" })
|
|
18
18
|
}
|
|
19
19
|
align("rec base", ir.rec.head, ir.rec.base)
|
|
20
20
|
align("rec step", ir.rec.head, ir.rec.rec)
|
|
@@ -25,17 +25,17 @@ function parseQueryIr(ir: QueryIr): ParsedQuery {
|
|
|
25
25
|
function align(context: string, head: readonly HeadTermIr[], rules: readonly RuleIr[]): void {
|
|
26
26
|
for (const [ruleIndex, rule] of rules.entries()) {
|
|
27
27
|
if (rule.finds.length !== head.length) {
|
|
28
|
-
throw
|
|
29
|
-
`${context}: rule ${ruleIndex} finds width ${rule.finds.length} does not match head width ${head.length}`
|
|
30
|
-
)
|
|
28
|
+
throw new AuthoringError({
|
|
29
|
+
message: `${context}: rule ${ruleIndex} finds width ${rule.finds.length} does not match head width ${head.length}`
|
|
30
|
+
})
|
|
31
31
|
}
|
|
32
32
|
rule.finds.forEach(function checkFind(find, position) {
|
|
33
33
|
parseFind(`${context} rule ${ruleIndex} find ${position}`, find)
|
|
34
34
|
const family = findFamily(find)
|
|
35
35
|
if (family !== head[position]?.kind) {
|
|
36
|
-
throw
|
|
37
|
-
`${context}: rule ${ruleIndex} find ${position} is ${family}, not head ${head[position]?.kind}`
|
|
38
|
-
)
|
|
36
|
+
throw new AuthoringError({
|
|
37
|
+
message: `${context}: rule ${ruleIndex} find ${position} is ${family}, not head ${head[position]?.kind}`
|
|
38
|
+
})
|
|
39
39
|
}
|
|
40
40
|
})
|
|
41
41
|
}
|
|
@@ -45,21 +45,26 @@ function parseFind(context: string, find: FindTermIr): void {
|
|
|
45
45
|
const raw = find as Record<string, unknown>
|
|
46
46
|
if (find.kind === "count") {
|
|
47
47
|
if ("over" in raw) {
|
|
48
|
-
throw
|
|
48
|
+
throw new AuthoringError({ message: `${context}: Count carries no over` })
|
|
49
49
|
}
|
|
50
50
|
return
|
|
51
51
|
}
|
|
52
52
|
if (find.kind === "pack" || find.kind === "aggregate") {
|
|
53
53
|
if (!("over" in raw)) {
|
|
54
|
-
throw
|
|
54
|
+
throw new AuthoringError({ message: `${context}: ${find.kind} requires over` })
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
+
if (find.kind === "compute" && !("expr" in raw)) {
|
|
58
|
+
throw new AuthoringError({ message: `${context}: compute requires expr` })
|
|
59
|
+
}
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
function findFamily(find: FindTermIr): "var" | "aggregate" {
|
|
62
|
+
function findFamily(find: FindTermIr): "var" | "aggregate" | "compute" {
|
|
60
63
|
switch (find.kind) {
|
|
61
64
|
case "var":
|
|
62
65
|
return "var"
|
|
66
|
+
case "compute":
|
|
67
|
+
return "compute"
|
|
63
68
|
case "count":
|
|
64
69
|
case "pack":
|
|
65
70
|
case "aggregate":
|
package/src/query/run.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import type { FactValue, QueryParam, TaggedValue } from "#native.ts"
|
|
1
|
+
import { AuthoringError, SdkInvariantError } from "#errors.ts"
|
|
2
|
+
import type { QueryParam, TaggedValue } from "#native.ts"
|
|
4
3
|
import type { FindColumn } from "#query/atom.ts"
|
|
5
4
|
import { taggedCmpLiteral } from "#query/lower.ts"
|
|
6
5
|
import type { ParamEntry } from "#query/scope.ts"
|
|
6
|
+
import type { CellValue } from "#rows.ts"
|
|
7
|
+
import { decodeCell, handleOf } from "#rows.ts"
|
|
7
8
|
|
|
8
9
|
function wireValue(entry: ParamEntry, context: string, value: unknown): TaggedValue {
|
|
9
10
|
if (entry.anchor === undefined) {
|
|
10
|
-
throw
|
|
11
|
-
`param ${entry.name} has no field-anchored use — bind it in an atom or compare it against a bound variable`
|
|
12
|
-
)
|
|
11
|
+
throw new AuthoringError({
|
|
12
|
+
message: `param ${entry.name} has no field-anchored use — bind it in an atom or compare it against a bound variable`
|
|
13
|
+
})
|
|
13
14
|
}
|
|
14
15
|
return taggedCmpLiteral(context, entry.anchor, value, entry.op)
|
|
15
16
|
}
|
|
@@ -21,11 +22,11 @@ function wireParams(entries: readonly ParamEntry[], supplied: Readonly<Record<st
|
|
|
21
22
|
}
|
|
22
23
|
const value = supplied[entry.name]
|
|
23
24
|
if (value === undefined) {
|
|
24
|
-
throw
|
|
25
|
+
throw new AuthoringError({ message: `execute params object is missing param ${entry.name}` })
|
|
25
26
|
}
|
|
26
27
|
if (entry.shape === "set") {
|
|
27
28
|
if (!Array.isArray(value)) {
|
|
28
|
-
throw
|
|
29
|
+
throw new AuthoringError({ message: `param ${entry.name}: a set param binds a readonly array of values` })
|
|
29
30
|
}
|
|
30
31
|
return {
|
|
31
32
|
kind: "set",
|
|
@@ -40,30 +41,45 @@ function wireParams(entries: readonly ParamEntry[], supplied: Readonly<Record<st
|
|
|
40
41
|
|
|
41
42
|
function isAnswerRow<Row>(
|
|
42
43
|
finds: readonly FindColumn[],
|
|
43
|
-
decoded: Readonly<Record<string,
|
|
44
|
-
): decoded is Readonly<Record<string,
|
|
44
|
+
decoded: Readonly<Record<string, unknown>>
|
|
45
|
+
): decoded is Readonly<Record<string, unknown>> & Row {
|
|
45
46
|
return finds.every(function present(column) {
|
|
46
47
|
return decoded[column.name] !== undefined
|
|
47
48
|
})
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
/**
|
|
52
|
+
* The one answer-row decoder: owned positional cells into a plain frozen
|
|
53
|
+
* record keyed by the find columns — the SAME fields and shapes across
|
|
54
|
+
* every row and page (stable row shape). A column carrying its mint slot
|
|
55
|
+
* decodes through the full value roster (uuid bytes lift to canonical
|
|
56
|
+
* hex, closed ids lift to handle names, float intervals stay owned plain
|
|
57
|
+
* objects); an aggregate column without a slot passes the engine's owned
|
|
58
|
+
* scalar through, with the closed lift when the column is closed-typed.
|
|
59
|
+
*/
|
|
60
|
+
function decodeAnswers<Row>(finds: readonly FindColumn[], rows: readonly (readonly CellValue[])[]): Row[] {
|
|
51
61
|
return rows.map(function decodeRow(row) {
|
|
52
62
|
if (row.length !== finds.length) {
|
|
53
|
-
throw
|
|
63
|
+
throw new SdkInvariantError({
|
|
64
|
+
message: `query answer arity ${row.length} does not match the ${finds.length} find columns`
|
|
65
|
+
})
|
|
54
66
|
}
|
|
55
|
-
const decoded: Record<string,
|
|
56
|
-
finds.forEach(function
|
|
67
|
+
const decoded: Record<string, unknown> = {}
|
|
68
|
+
finds.forEach(function decodeColumn(column, ordinal) {
|
|
57
69
|
const cell = row[ordinal]
|
|
58
70
|
if (cell === undefined) {
|
|
59
|
-
throw
|
|
71
|
+
throw new SdkInvariantError({ message: `query answer cell ${ordinal} (${column.name}) is absent` })
|
|
72
|
+
}
|
|
73
|
+
if (column.slot !== undefined) {
|
|
74
|
+
decoded[column.name] = decodeCell(`query answer column ${column.name}`, column.slot.field, cell)
|
|
75
|
+
return
|
|
60
76
|
}
|
|
61
77
|
decoded[column.name] =
|
|
62
78
|
column.closed === undefined ? cell : handleOf(`query answer column ${column.name}`, column.closed, cell)
|
|
63
79
|
})
|
|
64
80
|
Object.freeze(decoded)
|
|
65
81
|
if (!isAnswerRow<Row>(finds, decoded)) {
|
|
66
|
-
throw
|
|
82
|
+
throw new SdkInvariantError({ message: "query answer row is not a complete find record" })
|
|
67
83
|
}
|
|
68
84
|
return decoded
|
|
69
85
|
})
|
package/src/query/scope.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import * as errors from "@superbuilders/errors"
|
|
2
1
|
import type { AnyClosed } from "#closed.ts"
|
|
3
2
|
import { sealedFieldsOf } from "#closed.ts"
|
|
3
|
+
import { AuthoringError, SdkInvariantError } from "#errors.ts"
|
|
4
4
|
import type { AnyField, Infer, SignatureOf } from "#fields.ts"
|
|
5
5
|
import { rosterOf, signaturesAgree } from "#fields.ts"
|
|
6
6
|
import type { Same, SameLen } from "#judgment.ts"
|
|
7
7
|
import type { ClassLookup, ClassRecordOf, SchemaClasses } from "#law.ts"
|
|
8
8
|
import type { QueryParam } from "#native.ts"
|
|
9
|
+
import type { FindColumn } from "#query/atom.ts"
|
|
9
10
|
import type { AnyRelation, RelationFields } from "#relation.ts"
|
|
10
11
|
|
|
11
12
|
const term: unique symbol = Symbol("bumbledb.query.term")
|
|
@@ -94,21 +95,136 @@ function varsMinted<R extends MatchOwner>(owner: R, record: Readonly<Record<stri
|
|
|
94
95
|
* what you need (`const { id, holder } = v(Account)`) and reuse a value
|
|
95
96
|
* across binding positions to join.
|
|
96
97
|
*/
|
|
97
|
-
|
|
98
|
+
/**
|
|
99
|
+
* A schema-bound query template used as a relation-expression source
|
|
100
|
+
* (chapter 34's nonrecursive composition): structurally, the query value's
|
|
101
|
+
* own frozen data. Detection is structural so `scope.ts` needs no runtime
|
|
102
|
+
* import of the query module.
|
|
103
|
+
*/
|
|
104
|
+
interface ImportedSource {
|
|
105
|
+
readonly schema: object
|
|
106
|
+
readonly data: {
|
|
107
|
+
readonly kind: "cq" | "reach"
|
|
108
|
+
readonly finds: readonly FindColumn[]
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isImportedSource(value: unknown): value is ImportedSource {
|
|
113
|
+
if (typeof value !== "object" || value === null || !("data" in value) || !("schema" in value)) {
|
|
114
|
+
return false
|
|
115
|
+
}
|
|
116
|
+
const data = (value as { readonly data: unknown }).data
|
|
117
|
+
return (
|
|
118
|
+
typeof data === "object" &&
|
|
119
|
+
data !== null &&
|
|
120
|
+
"kind" in data &&
|
|
121
|
+
((data as { readonly kind: unknown }).kind === "cq" || (data as { readonly kind: unknown }).kind === "reach") &&
|
|
122
|
+
"finds" in data
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
type RowOfImport<Q> = InferredOf<Q> extends { readonly row: infer R } ? R : never
|
|
127
|
+
|
|
128
|
+
type ImportVars<Q> = [RowOfImport<Q>] extends [never]
|
|
129
|
+
? Readonly<Record<string, Var<AnyField, string, string>>>
|
|
130
|
+
: { readonly [K in keyof RowOfImport<Q> & string]: Var<AnyField, string, K> }
|
|
131
|
+
|
|
132
|
+
interface ImportFacade {
|
|
133
|
+
/** The pseudo-owner every import var carries (structurally an owner). */
|
|
134
|
+
readonly owner: MatchOwner & { readonly name: string }
|
|
135
|
+
readonly source: ImportedSource
|
|
136
|
+
/** Column name → the imported head column (field + carrier class). */
|
|
137
|
+
readonly columns: ReadonlyMap<string, FindColumn>
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const facadeBySource = new WeakMap<object, ImportFacade>()
|
|
141
|
+
const facadeByOwner = new WeakMap<object, ImportFacade>()
|
|
142
|
+
|
|
143
|
+
let importOrdinal = 0
|
|
144
|
+
|
|
145
|
+
const importLabels = new WeakMap<object, string>()
|
|
146
|
+
|
|
147
|
+
/** `.named(label)` registration — a diagnostic name, never a schema relation. */
|
|
148
|
+
function labelImport(source: object, label: string): void {
|
|
149
|
+
importLabels.set(source, label)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function importFacadeOf(source: ImportedSource): ImportFacade {
|
|
153
|
+
const cached = facadeBySource.get(source)
|
|
154
|
+
if (cached !== undefined) {
|
|
155
|
+
return cached
|
|
156
|
+
}
|
|
157
|
+
importOrdinal += 1
|
|
158
|
+
const name = importLabels.get(source) ?? `\u0000import:${importOrdinal}`
|
|
159
|
+
const columns = new Map<string, FindColumn>()
|
|
160
|
+
const fields: Array<{ readonly name: string; readonly field: AnyField }> = []
|
|
161
|
+
for (const column of source.data.finds) {
|
|
162
|
+
if (column.slot === undefined) {
|
|
163
|
+
throw new AuthoringError({
|
|
164
|
+
message: `v(query): the imported head column ${column.name} carries no descriptor — the imported query is not a mintable relation expression`
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
columns.set(column.name, column)
|
|
168
|
+
fields.push(Object.freeze({ name: column.name, field: column.slot.field }))
|
|
169
|
+
}
|
|
170
|
+
const data = Object.freeze({ name, fields: Object.freeze(fields) })
|
|
171
|
+
const owner = Object.freeze({
|
|
172
|
+
name,
|
|
173
|
+
data,
|
|
174
|
+
where(): never {
|
|
175
|
+
throw new AuthoringError({
|
|
176
|
+
message: `imported query ${name}: an imported relation expression takes no selection — filter through where() in the consuming rule`
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
}) as unknown as MatchOwner & { readonly name: string }
|
|
180
|
+
const facade: ImportFacade = Object.freeze({ owner, source, columns })
|
|
181
|
+
facadeBySource.set(source, facade)
|
|
182
|
+
facadeByOwner.set(owner, facade)
|
|
183
|
+
return facade
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** The import awareness the lowering's mint judgment reads. */
|
|
187
|
+
function importFacadeOfOwner(owner: object): ImportFacade | undefined {
|
|
188
|
+
return facadeByOwner.get(owner)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function v<R extends MatchOwner>(owner: R): VarsOf<R>
|
|
192
|
+
function v<Q extends ImportedSource>(imported: Q): ImportVars<Q>
|
|
193
|
+
function v(owner: MatchOwner | ImportedSource): Readonly<Record<string, AnyVar>> {
|
|
194
|
+
if (isImportedSource(owner) && !("name" in owner)) {
|
|
195
|
+
const facade = importFacadeOf(owner)
|
|
196
|
+
const record: Record<string, AnyVar> = {}
|
|
197
|
+
for (const [columnName, column] of facade.columns) {
|
|
198
|
+
const slot = column.slot
|
|
199
|
+
if (slot === undefined) {
|
|
200
|
+
throw new SdkInvariantError({ message: `v(query): facade column ${columnName} lost its slot` })
|
|
201
|
+
}
|
|
202
|
+
const variable: AnyVar = Object.freeze({
|
|
203
|
+
[term]: "var" as const,
|
|
204
|
+
owner: facade.owner,
|
|
205
|
+
column: columnName,
|
|
206
|
+
field: slot.field,
|
|
207
|
+
label: `${facade.owner.name}.${columnName}`
|
|
208
|
+
})
|
|
209
|
+
Object.defineProperty(record, columnName, { value: variable, enumerable: true })
|
|
210
|
+
}
|
|
211
|
+
return Object.freeze(record)
|
|
212
|
+
}
|
|
213
|
+
const member = owner as MatchOwner
|
|
98
214
|
const record: Record<string, AnyVar> = {}
|
|
99
|
-
for (const declared of sealedFieldsOf(
|
|
215
|
+
for (const declared of sealedFieldsOf(member)) {
|
|
100
216
|
const variable: AnyVar = Object.freeze({
|
|
101
217
|
[term]: "var" as const,
|
|
102
|
-
owner,
|
|
218
|
+
owner: member,
|
|
103
219
|
column: declared.name,
|
|
104
220
|
field: declared.field,
|
|
105
|
-
label: `${
|
|
221
|
+
label: `${member.name}.${declared.name}`
|
|
106
222
|
})
|
|
107
223
|
Object.defineProperty(record, declared.name, { value: variable, enumerable: true })
|
|
108
224
|
}
|
|
109
225
|
Object.freeze(record)
|
|
110
|
-
if (!varsMinted(
|
|
111
|
-
throw
|
|
226
|
+
if (!varsMinted(member, record)) {
|
|
227
|
+
throw new SdkInvariantError({ message: `v(${member.name}): variable-record minting incomplete` })
|
|
112
228
|
}
|
|
113
229
|
return record
|
|
114
230
|
}
|
|
@@ -155,7 +271,17 @@ type ShapeOf<U> = [U] extends [never] ? Record<never, never> : Flatten<UnionToIn
|
|
|
155
271
|
*/
|
|
156
272
|
type SlotSignature<S extends ClassedField> = readonly [SignatureOf<S["field"]>, S["class"]]
|
|
157
273
|
|
|
158
|
-
|
|
274
|
+
/**
|
|
275
|
+
* A widened slot — a variable minted from an imported query template (its
|
|
276
|
+
* facade columns carry `AnyField`) or fully generic code — cannot be judged
|
|
277
|
+
* at the type tier. The degradation law applies: best effort degrades to
|
|
278
|
+
* SILENT, never to a wrong refusal; the runtime join judgment (real
|
|
279
|
+
* descriptors off the imported head's slots) stays authoritative.
|
|
280
|
+
*/
|
|
281
|
+
type WidenedSlot<S extends ClassedField> = [AnyField] extends [S["field"]] ? true : false
|
|
282
|
+
|
|
283
|
+
type JoinOk<A extends ClassedField, B extends ClassedField> =
|
|
284
|
+
WidenedSlot<A> extends true ? true : WidenedSlot<B> extends true ? true : Same<SlotSignature<A>, SlotSignature<B>>
|
|
159
285
|
|
|
160
286
|
type U64Wire<F extends AnyField> = F extends { readonly kind: "u64" }
|
|
161
287
|
? F extends { readonly closed: unknown }
|
|
@@ -272,6 +398,8 @@ export type {
|
|
|
272
398
|
ClassedField,
|
|
273
399
|
ExactVars,
|
|
274
400
|
Flatten,
|
|
401
|
+
ImportedSource,
|
|
402
|
+
ImportVars,
|
|
275
403
|
InferredOf,
|
|
276
404
|
JoinOk,
|
|
277
405
|
MatchFields,
|
|
@@ -290,8 +418,12 @@ export {
|
|
|
290
418
|
fieldAntiJoins,
|
|
291
419
|
fieldJoins,
|
|
292
420
|
headFieldJoins,
|
|
421
|
+
importFacadeOf,
|
|
422
|
+
importFacadeOfOwner,
|
|
293
423
|
inferred,
|
|
424
|
+
isImportedSource,
|
|
294
425
|
isTerm,
|
|
426
|
+
labelImport,
|
|
295
427
|
makeParam,
|
|
296
428
|
makeSetParam,
|
|
297
429
|
renderFieldKind,
|
package/src/relation.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AuthoringError } from "#errors.ts"
|
|
1
2
|
/**
|
|
2
3
|
* `relation` — the ordinary-relation half of the theory's signature. A
|
|
3
4
|
* relation value is a frozen plain object carrying its name, its ordered
|
|
@@ -8,31 +9,31 @@
|
|
|
8
9
|
* NAME everywhere — statements (`on(R, "holder")`), selections, and match
|
|
9
10
|
* records all spell the field's own name, checked by type
|
|
10
11
|
* (`FaceFields`/`MatchShape`). `Fact<>` is the inferred row object type
|
|
11
|
-
* at BARE structural value types
|
|
12
|
-
*
|
|
12
|
+
* at BARE structural value types: every field is present. Identities are
|
|
13
|
+
* ordinary application-owned values (`Uuid`); the database mints none.
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
|
-
import * as errors from "@superbuilders/errors"
|
|
16
16
|
import { type AnyField, assertDeclarationOrderKey, assertDeclarationRecord, type Infer, literalOf } from "#fields.ts"
|
|
17
17
|
import { type LiteralSetSpec, type LiteralSpec, renderLiteral } from "#spec.ts"
|
|
18
18
|
|
|
19
19
|
function resolveEntry(context: string, field: AnyField, entry: unknown): LiteralSetSpec {
|
|
20
20
|
if (Array.isArray(entry)) {
|
|
21
21
|
if (entry.length < 2) {
|
|
22
|
-
throw
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
throw new AuthoringError({
|
|
23
|
+
message:
|
|
24
|
+
entry.length === 0
|
|
25
|
+
? `${context}: an empty literal set selects nothing — write the selection you mean`
|
|
26
|
+
: `${context}: a one-element literal set is the bare literal respelled — write the literal (the canonical-utterance law: one meaning, one spelling)`
|
|
27
|
+
})
|
|
27
28
|
}
|
|
28
29
|
const seen = new Set<string>()
|
|
29
30
|
const literals: LiteralSpec[] = entry.map(function lowerSetLiteral(literal: unknown) {
|
|
30
31
|
const lowered = Object.freeze(literalOf(field, literal))
|
|
31
32
|
const rendered = renderLiteral(lowered)
|
|
32
33
|
if (seen.has(rendered)) {
|
|
33
|
-
throw
|
|
34
|
-
`${context}: the literal set spells ${rendered} twice — write it once (the canonical-utterance law: one meaning, one spelling)`
|
|
35
|
-
)
|
|
34
|
+
throw new AuthoringError({
|
|
35
|
+
message: `${context}: the literal set spells ${rendered} twice — write it once (the canonical-utterance law: one meaning, one spelling)`
|
|
36
|
+
})
|
|
36
37
|
}
|
|
37
38
|
seen.add(rendered)
|
|
38
39
|
return lowered
|
|
@@ -56,16 +57,16 @@ function resolveSelection(
|
|
|
56
57
|
return candidate.name === fieldName
|
|
57
58
|
})
|
|
58
59
|
if (declared === undefined) {
|
|
59
|
-
throw
|
|
60
|
+
throw new AuthoringError({ message: `relation ${name} has no field ${fieldName}` })
|
|
60
61
|
}
|
|
61
62
|
bindings.push(
|
|
62
63
|
Object.freeze({ field: fieldName, set: resolveEntry(`relation ${name}.${fieldName}`, declared.field, entry) })
|
|
63
64
|
)
|
|
64
65
|
}
|
|
65
66
|
if (bindings.length === 0) {
|
|
66
|
-
throw
|
|
67
|
-
`relation ${name}: an empty selection is the bare relation respelled — pass the relation itself (the canonical-utterance law: one meaning, one spelling)`
|
|
68
|
-
)
|
|
67
|
+
throw new AuthoringError({
|
|
68
|
+
message: `relation ${name}: an empty selection is the bare relation respelled — pass the relation itself (the canonical-utterance law: one meaning, one spelling)`
|
|
69
|
+
})
|
|
69
70
|
}
|
|
70
71
|
return Object.freeze(bindings)
|
|
71
72
|
}
|
|
@@ -115,10 +116,6 @@ type Fact<R extends AnyRelation> = {
|
|
|
115
116
|
[K in keyof RelationFields<R>]: Infer<RelationFields<R>[K]>
|
|
116
117
|
}
|
|
117
118
|
|
|
118
|
-
type FreshKeys<R extends AnyRelation> = {
|
|
119
|
-
[K in keyof RelationFields<R>]: RelationFields<R>[K] extends { readonly fresh: true } ? K : never
|
|
120
|
-
}[keyof RelationFields<R>]
|
|
121
|
-
|
|
122
119
|
function relation<const Name extends string, Fields extends FieldsShape>(
|
|
123
120
|
name: Name,
|
|
124
121
|
fields: Fields
|
|
@@ -135,7 +132,7 @@ function relation<const Name extends string, Fields extends FieldsShape>(
|
|
|
135
132
|
function where(selection: SelectionInput<Fields>): Selected<Name, Fields> {
|
|
136
133
|
const owner = holder.value
|
|
137
134
|
if (owner === undefined) {
|
|
138
|
-
throw
|
|
135
|
+
throw new AuthoringError({ message: `relation ${name}: self-reference read before construction completed` })
|
|
139
136
|
}
|
|
140
137
|
return Object.freeze({
|
|
141
138
|
relation: owner,
|
|
@@ -152,7 +149,6 @@ export type {
|
|
|
152
149
|
AnySelected,
|
|
153
150
|
Fact,
|
|
154
151
|
FieldsShape,
|
|
155
|
-
FreshKeys,
|
|
156
152
|
Relation,
|
|
157
153
|
RelationData,
|
|
158
154
|
RelationField,
|
package/src/result.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { Effect, Option, Stream } from "effect"
|
|
2
|
+
import { drainClose, releaseOwner } from "#close.ts"
|
|
3
|
+
import type { CursorHandle, ResultHandle } from "#db-native.ts"
|
|
4
|
+
import { dbNative } from "#db-native.ts"
|
|
5
|
+
import type { FindColumn } from "#query/atom.ts"
|
|
6
|
+
import { decodeAnswers } from "#query/run.ts"
|
|
7
|
+
import type { CellValue } from "#rows.ts"
|
|
8
|
+
import type { ExecutionPolicy } from "#runtime.ts"
|
|
9
|
+
import { deliveryResultBytes, nativeOperationWith, policyWire } from "#runtime.ts"
|
|
10
|
+
import type { CloseReport, DbError } from "#runtime-errors.ts"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* `CompleteResult<A>` — the sealed owner of one COMPLETED query answer
|
|
14
|
+
* (C05): published only after all evaluation/finalization succeeded,
|
|
15
|
+
* possibly backed by temporary LMDB scratch. Owned and independent of its
|
|
16
|
+
* source snapshot/session.
|
|
17
|
+
*
|
|
18
|
+
* `collect` materializes a bounded owned array and leaves the result
|
|
19
|
+
* available; it refuses (`ResourceLimit`) before allocating past
|
|
20
|
+
* `maxBytes`, and a cap failure leaves the sealed backing available for
|
|
21
|
+
* `pages`. `pages` is chapter 35's 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.
|
|
29
|
+
*/
|
|
30
|
+
interface CompleteResult<A> {
|
|
31
|
+
collect(options: { readonly maxBytes: bigint }, work: ExecutionPolicy): Effect.Effect<ReadonlyArray<A>, DbError>
|
|
32
|
+
pages(options: { readonly pageBytes: bigint }, work: ExecutionPolicy): Stream.Stream<ReadonlyArray<A>, DbError>
|
|
33
|
+
close(): Effect.Effect<CloseReport>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface ResultInternal {
|
|
37
|
+
readonly handle: ResultHandle
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const resultInternals = new WeakMap<object, ResultInternal>()
|
|
41
|
+
|
|
42
|
+
/** Private cross-module accessor (scope finalizers in db.ts reach the handle). */
|
|
43
|
+
function internalResult(value: object): ResultInternal | undefined {
|
|
44
|
+
return resultInternals.get(value)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function decodePage<A>(finds: readonly FindColumn[], rows: readonly (readonly CellValue[])[]): ReadonlyArray<A> {
|
|
48
|
+
// Owned ordinary records in declared column order — the same fields and
|
|
49
|
+
// shapes on every page (stable row shape; no Proxy, no per-row fiber).
|
|
50
|
+
return Object.freeze(decodeAnswers<A>(finds, rows))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Internal constructor: `db.ts` publishes results through this after
|
|
55
|
+
* execution completes. Delivery (`collect`/`pages`) starts a fresh bounded
|
|
56
|
+
* operation under the caller's delivery policy — never the completed query's
|
|
57
|
+
* expired execution deadline.
|
|
58
|
+
*/
|
|
59
|
+
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
|
+
const value: CompleteResult<A> = {
|
|
64
|
+
collect(options, work) {
|
|
65
|
+
return Effect.suspend(() =>
|
|
66
|
+
nativeOperationWith(
|
|
67
|
+
"CompleteResult.collect",
|
|
68
|
+
(callback) =>
|
|
69
|
+
dbNative.runtimeResultCollect(
|
|
70
|
+
handle,
|
|
71
|
+
deliveryWire("CompleteResult.collect", work, options.maxBytes),
|
|
72
|
+
callback
|
|
73
|
+
),
|
|
74
|
+
dbNative.runtimeRowsTake,
|
|
75
|
+
(rows) => decodePage<A>(finds, rows)
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
},
|
|
79
|
+
pages(options, work) {
|
|
80
|
+
return Stream.unwrap(
|
|
81
|
+
Effect.gen(function* () {
|
|
82
|
+
const cursor: CursorHandle = yield* Effect.acquireRelease(
|
|
83
|
+
nativeOperationWith(
|
|
84
|
+
"CompleteResult.pages",
|
|
85
|
+
(callback) =>
|
|
86
|
+
dbNative.runtimeResultCursor(
|
|
87
|
+
handle,
|
|
88
|
+
deliveryWire("CompleteResult.pages", work, options.pageBytes),
|
|
89
|
+
callback
|
|
90
|
+
),
|
|
91
|
+
dbNative.runtimeCursorTake,
|
|
92
|
+
(taken) => taken
|
|
93
|
+
),
|
|
94
|
+
(taken) => releaseOwner("ResultCursor.close", (callback) => dbNative.runtimeCursorClose(taken, callback)),
|
|
95
|
+
{ interruptible: true }
|
|
96
|
+
)
|
|
97
|
+
return Stream.paginate(undefined, () =>
|
|
98
|
+
nativeOperationWith(
|
|
99
|
+
"CompleteResult.page",
|
|
100
|
+
(callback) =>
|
|
101
|
+
dbNative.runtimeCursorNext(
|
|
102
|
+
cursor,
|
|
103
|
+
deliveryWire("CompleteResult.page", work, options.pageBytes),
|
|
104
|
+
callback
|
|
105
|
+
),
|
|
106
|
+
dbNative.runtimePageTake,
|
|
107
|
+
(page) => page
|
|
108
|
+
).pipe(
|
|
109
|
+
Effect.map((page) => {
|
|
110
|
+
if (page === null) {
|
|
111
|
+
return [[], Option.none<undefined>()] as const
|
|
112
|
+
}
|
|
113
|
+
return [[decodePage<A>(finds, page)], Option.some(undefined)] as const
|
|
114
|
+
})
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
})
|
|
118
|
+
)
|
|
119
|
+
},
|
|
120
|
+
close() {
|
|
121
|
+
return drainClose("CompleteResult.close", (callback) => dbNative.runtimeResultClose(handle, callback))
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
Object.freeze(value)
|
|
125
|
+
resultInternals.set(value, { handle })
|
|
126
|
+
return value
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export type { CompleteResult }
|
|
130
|
+
export { internalResult, makeCompleteResult }
|