@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.
Files changed (192) hide show
  1. package/COOKBOOK.md +283 -1611
  2. package/README.md +156 -118
  3. package/dist/capacity.d.ts +16 -12
  4. package/dist/capacity.d.ts.map +1 -1
  5. package/dist/capacity.js +16 -48
  6. package/dist/capacity.js.map +1 -1
  7. package/dist/changes.d.ts +54 -0
  8. package/dist/changes.d.ts.map +1 -0
  9. package/dist/changes.js +245 -0
  10. package/dist/changes.js.map +1 -0
  11. package/dist/close.d.ts +24 -0
  12. package/dist/close.d.ts.map +1 -0
  13. package/dist/close.js +34 -0
  14. package/dist/close.js.map +1 -0
  15. package/dist/closed.d.ts +1 -3
  16. package/dist/closed.d.ts.map +1 -1
  17. package/dist/closed.js +24 -12
  18. package/dist/closed.js.map +1 -1
  19. package/dist/codec.d.ts +56 -0
  20. package/dist/codec.d.ts.map +1 -0
  21. package/dist/codec.js +289 -0
  22. package/dist/codec.js.map +1 -0
  23. package/dist/compile.d.ts +68 -0
  24. package/dist/compile.d.ts.map +1 -0
  25. package/dist/compile.js +75 -0
  26. package/dist/compile.js.map +1 -0
  27. package/dist/db-native.d.ts +170 -0
  28. package/dist/db-native.d.ts.map +1 -0
  29. package/dist/db-native.js +5 -0
  30. package/dist/db-native.js.map +1 -0
  31. package/dist/db.d.ts +101 -233
  32. package/dist/db.d.ts.map +1 -1
  33. package/dist/db.js +232 -1084
  34. package/dist/db.js.map +1 -1
  35. package/dist/errors.d.ts +57 -0
  36. package/dist/errors.d.ts.map +1 -0
  37. package/dist/errors.js +32 -0
  38. package/dist/errors.js.map +1 -0
  39. package/dist/face.d.ts.map +1 -1
  40. package/dist/face.js +2 -2
  41. package/dist/face.js.map +1 -1
  42. package/dist/fields.d.ts +65 -24
  43. package/dist/fields.d.ts.map +1 -1
  44. package/dist/fields.js +94 -24
  45. package/dist/fields.js.map +1 -1
  46. package/dist/index.d.ts +37 -32
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +17 -21
  49. package/dist/index.js.map +1 -1
  50. package/dist/internal/log.d.ts +24 -0
  51. package/dist/internal/log.d.ts.map +1 -0
  52. package/dist/internal/log.js +10 -0
  53. package/dist/internal/log.js.map +1 -0
  54. package/dist/law.d.ts +8 -13
  55. package/dist/law.d.ts.map +1 -1
  56. package/dist/law.js +6 -59
  57. package/dist/law.js.map +1 -1
  58. package/dist/lower.d.ts +0 -13
  59. package/dist/lower.d.ts.map +1 -1
  60. package/dist/lower.js +9 -17
  61. package/dist/lower.js.map +1 -1
  62. package/dist/migration.d.ts +33 -0
  63. package/dist/migration.d.ts.map +1 -0
  64. package/dist/migration.js +48 -0
  65. package/dist/migration.js.map +1 -0
  66. package/dist/native.d.ts +256 -301
  67. package/dist/native.d.ts.map +1 -1
  68. package/dist/native.js +65 -108
  69. package/dist/native.js.map +1 -1
  70. package/dist/query/atom.d.ts +24 -13
  71. package/dist/query/atom.d.ts.map +1 -1
  72. package/dist/query/atom.js +7 -3
  73. package/dist/query/atom.js.map +1 -1
  74. package/dist/query/compute.d.ts +68 -0
  75. package/dist/query/compute.d.ts.map +1 -0
  76. package/dist/query/compute.js +141 -0
  77. package/dist/query/compute.js.map +1 -0
  78. package/dist/query/find.d.ts +15 -8
  79. package/dist/query/find.d.ts.map +1 -1
  80. package/dist/query/find.js +6 -2
  81. package/dist/query/find.js.map +1 -1
  82. package/dist/query/lower.d.ts +23 -2
  83. package/dist/query/lower.d.ts.map +1 -1
  84. package/dist/query/lower.js +433 -80
  85. package/dist/query/lower.js.map +1 -1
  86. package/dist/query/parse-ir.d.ts.map +1 -1
  87. package/dist/query/parse-ir.js +17 -8
  88. package/dist/query/parse-ir.js.map +1 -1
  89. package/dist/query/run.d.ts +12 -2
  90. package/dist/query/run.d.ts.map +1 -1
  91. package/dist/query/run.js +26 -9
  92. package/dist/query/run.js.map +1 -1
  93. package/dist/query/scope.d.ts +47 -3
  94. package/dist/query/scope.d.ts.map +1 -1
  95. package/dist/query/scope.js +83 -19
  96. package/dist/query/scope.js.map +1 -1
  97. package/dist/relation.d.ts +3 -8
  98. package/dist/relation.d.ts.map +1 -1
  99. package/dist/relation.js +16 -10
  100. package/dist/relation.js.map +1 -1
  101. package/dist/result.d.ts +47 -0
  102. package/dist/result.d.ts.map +1 -0
  103. package/dist/result.js +50 -0
  104. package/dist/result.js.map +1 -0
  105. package/dist/rows.d.ts +69 -0
  106. package/dist/rows.d.ts.map +1 -0
  107. package/dist/rows.js +328 -0
  108. package/dist/rows.js.map +1 -0
  109. package/dist/runtime-codes.d.ts +2 -0
  110. package/dist/runtime-codes.d.ts.map +1 -0
  111. package/dist/runtime-codes.js +19 -0
  112. package/dist/runtime-codes.js.map +1 -0
  113. package/dist/runtime-errors.d.ts +106 -0
  114. package/dist/runtime-errors.d.ts.map +1 -0
  115. package/dist/runtime-errors.js +62 -0
  116. package/dist/runtime-errors.js.map +1 -0
  117. package/dist/runtime-native.d.ts +171 -0
  118. package/dist/runtime-native.d.ts.map +1 -0
  119. package/dist/runtime-native.js +5 -0
  120. package/dist/runtime-native.js.map +1 -0
  121. package/dist/runtime.d.ts +73 -0
  122. package/dist/runtime.d.ts.map +1 -0
  123. package/dist/runtime.js +293 -0
  124. package/dist/runtime.js.map +1 -0
  125. package/dist/scalar.d.ts +165 -0
  126. package/dist/scalar.d.ts.map +1 -0
  127. package/dist/scalar.js +344 -0
  128. package/dist/scalar.js.map +1 -0
  129. package/dist/schema.d.ts +1 -1
  130. package/dist/schema.d.ts.map +1 -1
  131. package/dist/schema.js +34 -37
  132. package/dist/schema.js.map +1 -1
  133. package/dist/shape.d.ts +28 -0
  134. package/dist/shape.d.ts.map +1 -0
  135. package/dist/shape.js +2 -0
  136. package/dist/shape.js.map +1 -0
  137. package/dist/spec.d.ts +27 -3
  138. package/dist/spec.d.ts.map +1 -1
  139. package/dist/spec.js +22 -1
  140. package/dist/spec.js.map +1 -1
  141. package/dist/statements.d.ts +16 -3
  142. package/dist/statements.d.ts.map +1 -1
  143. package/dist/statements.js +54 -28
  144. package/dist/statements.js.map +1 -1
  145. package/dist/uuid.d.ts +23 -0
  146. package/dist/uuid.d.ts.map +1 -0
  147. package/dist/uuid.js +54 -0
  148. package/dist/uuid.js.map +1 -0
  149. package/pack-provenance.json +7 -0
  150. package/package.json +12 -16
  151. package/src/capacity.ts +34 -54
  152. package/src/changes.ts +361 -0
  153. package/src/close.ts +53 -0
  154. package/src/closed.ts +25 -19
  155. package/src/codec.ts +379 -0
  156. package/src/compile.ts +149 -0
  157. package/src/db-native.ts +247 -0
  158. package/src/db.ts +461 -1632
  159. package/src/errors.ts +50 -0
  160. package/src/face.ts +2 -2
  161. package/src/fields.ts +182 -55
  162. package/src/index.ts +62 -126
  163. package/src/internal/log.ts +51 -0
  164. package/src/law.ts +13 -28
  165. package/src/lower.ts +9 -4
  166. package/src/migration.ts +69 -0
  167. package/src/native.ts +326 -487
  168. package/src/query/atom.ts +61 -25
  169. package/src/query/compute.ts +237 -0
  170. package/src/query/find.ts +31 -12
  171. package/src/query/lower.ts +531 -143
  172. package/src/query/parse-ir.ts +18 -13
  173. package/src/query/run.ts +32 -16
  174. package/src/query/scope.ts +140 -8
  175. package/src/relation.ts +17 -21
  176. package/src/result.ts +130 -0
  177. package/src/rows.ts +404 -0
  178. package/src/runtime-codes.ts +18 -0
  179. package/src/runtime-errors.ts +69 -0
  180. package/src/runtime-native.ts +242 -0
  181. package/src/runtime.ts +440 -0
  182. package/src/scalar.ts +560 -0
  183. package/src/schema.ts +35 -37
  184. package/src/shape.ts +31 -0
  185. package/src/spec.ts +33 -3
  186. package/src/statements.ts +75 -68
  187. package/src/uuid.ts +69 -0
  188. package/dist/marshal.d.ts +0 -44
  189. package/dist/marshal.d.ts.map +0 -1
  190. package/dist/marshal.js +0 -165
  191. package/dist/marshal.js.map +0 -1
  192. package/src/marshal.ts +0 -200
@@ -1,9 +1,9 @@
1
- import * as errors from "@superbuilders/errors"
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 errors.new("parseQueryIr: main rules are empty")
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 errors.new("parseQueryIr: rec base is empty")
14
+ throw new AuthoringError({ message: "parseQueryIr: rec base is empty" })
15
15
  }
16
16
  if (ir.rec.rec.length === 0) {
17
- throw errors.new("parseQueryIr: rec step is empty")
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 errors.new(
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 errors.new(
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 errors.new(`${context}: Count carries no over`)
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 errors.new(`${context}: ${find.kind} requires over`)
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 * as errors from "@superbuilders/errors"
2
- import { handleOf } from "#marshal.ts"
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 errors.new(
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 errors.new(`execute params object is missing param ${entry.name}`)
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 errors.new(`param ${entry.name}: a set param binds a readonly array of values`)
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, FactValue>>
44
- ): decoded is Readonly<Record<string, FactValue>> & Row {
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
- function decodeAnswers<Row>(finds: readonly FindColumn[], rows: FactValue[][]): Row[] {
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 errors.new(`query answer arity ${row.length} does not match the ${finds.length} find columns`)
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, FactValue> = {}
56
- finds.forEach(function decodeCell(column, ordinal) {
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 errors.new(`query answer cell ${ordinal} (${column.name}) is absent`)
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 errors.new("query answer row is not a complete find record")
82
+ throw new SdkInvariantError({ message: "query answer row is not a complete find record" })
67
83
  }
68
84
  return decoded
69
85
  })
@@ -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
- function v<R extends MatchOwner>(owner: R): VarsOf<R> {
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(owner)) {
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: `${owner.name}.${declared.name}`
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(owner, record)) {
111
- throw errors.new(`v(${owner.name}): variable-record minting incomplete`)
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
- type JoinOk<A extends ClassedField, B extends ClassedField> = Same<SlotSignature<A>, SlotSignature<B>>
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 (no brands): every field is present,
12
- * including fresh cells. Mint with `tx.reserve` before insert.
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 errors.new(
23
- entry.length === 0
24
- ? `${context}: an empty literal set selects nothing — write the selection you mean`
25
- : `${context}: a one-element literal set is the bare literal respelled — write the literal (the canonical-utterance law: one meaning, one spelling)`
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 errors.new(
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 errors.new(`relation ${name} has no field ${fieldName}`)
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 errors.new(
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 errors.new(`relation ${name}: self-reference read before construction completed`)
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 }