@bjornpagen/bumbledb 1.2.0 → 1.3.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 (72) hide show
  1. package/COOKBOOK.md +178 -0
  2. package/README.md +2 -2
  3. package/dist/alternatives.d.ts +25 -0
  4. package/dist/alternatives.d.ts.map +1 -0
  5. package/dist/alternatives.js +59 -0
  6. package/dist/alternatives.js.map +1 -0
  7. package/dist/capacity.d.ts +7 -4
  8. package/dist/capacity.d.ts.map +1 -1
  9. package/dist/capacity.js +3 -1
  10. package/dist/capacity.js.map +1 -1
  11. package/dist/index.d.ts +6 -4
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/law.d.ts +3 -1
  16. package/dist/law.d.ts.map +1 -1
  17. package/dist/law.js.map +1 -1
  18. package/dist/native.d.ts +14 -0
  19. package/dist/native.d.ts.map +1 -1
  20. package/dist/native.js.map +1 -1
  21. package/dist/query/atom.d.ts +2 -1
  22. package/dist/query/atom.d.ts.map +1 -1
  23. package/dist/query/atom.js.map +1 -1
  24. package/dist/query/compute.d.ts +16 -1
  25. package/dist/query/compute.d.ts.map +1 -1
  26. package/dist/query/compute.js +15 -2
  27. package/dist/query/compute.js.map +1 -1
  28. package/dist/query/description.d.ts +10 -2
  29. package/dist/query/description.d.ts.map +1 -1
  30. package/dist/query/description.js +10 -1
  31. package/dist/query/description.js.map +1 -1
  32. package/dist/query/find.d.ts +32 -10
  33. package/dist/query/find.d.ts.map +1 -1
  34. package/dist/query/find.js +1 -1
  35. package/dist/query/find.js.map +1 -1
  36. package/dist/query/lower.d.ts +18 -6
  37. package/dist/query/lower.d.ts.map +1 -1
  38. package/dist/query/lower.js +43 -30
  39. package/dist/query/lower.js.map +1 -1
  40. package/dist/query/parse-ir.d.ts.map +1 -1
  41. package/dist/query/parse-ir.js +24 -1
  42. package/dist/query/parse-ir.js.map +1 -1
  43. package/dist/query/scope.d.ts +21 -12
  44. package/dist/query/scope.d.ts.map +1 -1
  45. package/dist/query/scope.js.map +1 -1
  46. package/dist/query/segments.d.ts +20 -0
  47. package/dist/query/segments.d.ts.map +1 -0
  48. package/dist/query/segments.js +35 -0
  49. package/dist/query/segments.js.map +1 -0
  50. package/dist/scalar.d.ts +30 -8
  51. package/dist/scalar.d.ts.map +1 -1
  52. package/dist/scalar.js +80 -2
  53. package/dist/scalar.js.map +1 -1
  54. package/dist/shape.d.ts +6 -3
  55. package/dist/shape.d.ts.map +1 -1
  56. package/pack-provenance.json +3 -3
  57. package/package.json +4 -4
  58. package/src/alternatives.ts +95 -0
  59. package/src/capacity.ts +7 -4
  60. package/src/index.ts +14 -2
  61. package/src/law.ts +8 -5
  62. package/src/native.ts +14 -0
  63. package/src/query/atom.ts +2 -0
  64. package/src/query/compute.ts +41 -2
  65. package/src/query/description.ts +34 -4
  66. package/src/query/find.ts +47 -23
  67. package/src/query/lower.ts +73 -43
  68. package/src/query/parse-ir.ts +23 -1
  69. package/src/query/scope.ts +32 -15
  70. package/src/query/segments.ts +61 -0
  71. package/src/scalar.ts +140 -7
  72. package/src/shape.ts +9 -3
@@ -122,9 +122,23 @@ function isImportedSource(value: unknown): value is ImportedSource {
122
122
 
123
123
  type RowOfImport<Q> = InferredOf<Q> extends { readonly row: infer R } ? R : never
124
124
 
125
+ type HeadOfImport<Q> = InferredOf<Q> extends { readonly head: infer H } ? H : never
126
+
127
+ /** Named imported slot so downstream declarations need not spell its private marker. */
128
+ interface ImportedFieldVar<S extends ClassedField, K extends string> extends Var<S["field"], string, K> {
129
+ readonly [inferred]?: { readonly slot: S }
130
+ }
131
+
132
+ // Retain the same descriptor and carrier class as the runtime import facade.
133
+ // A host bigint alone cannot distinguish i64 from u64 or a closed identifier.
134
+ type ImportedVar<Q, K extends string> =
135
+ HeadOfImport<Q> extends Readonly<Record<K, infer S extends ClassedField>>
136
+ ? ImportedFieldVar<S, K>
137
+ : Var<AnyField, string, K>
138
+
125
139
  type ImportVars<Q> = [RowOfImport<Q>] extends [never]
126
140
  ? Readonly<Record<string, Var<AnyField, string, string>>>
127
- : { readonly [K in keyof RowOfImport<Q> & string]: Var<AnyField, string, K> }
141
+ : { readonly [K in keyof RowOfImport<Q> & string]: ImportedVar<Q, K> }
128
142
 
129
143
  interface ImportFacade {
130
144
  /** The pseudo-owner every import var carries (structurally an owner). */
@@ -241,10 +255,11 @@ type MintClassOf<Classes extends SchemaClasses, V> =
241
255
  ? ClassLookup<ClassRecordOf<Classes, RN>, K>
242
256
  : never
243
257
 
244
- type MintSlotOf<Classes extends SchemaClasses, V extends AnyVar> = {
245
- readonly field: V["field"]
246
- readonly class: MintClassOf<Classes, V>
247
- }
258
+ type MintSlotOf<Classes extends SchemaClasses, V extends AnyVar> = [InferredOf<V>] extends [never]
259
+ ? { readonly field: V["field"]; readonly class: MintClassOf<Classes, V> }
260
+ : InferredOf<V> extends { readonly slot: infer S extends ClassedField }
261
+ ? S
262
+ : { readonly field: V["field"]; readonly class: MintClassOf<Classes, V> }
248
263
 
249
264
  type ParamsRecord = Readonly<Record<string, unknown>>
250
265
 
@@ -257,15 +272,8 @@ type UnionToIntersection<U> = (U extends unknown ? (member: U) => void : never)
257
272
  type ShapeOf<U> = [U] extends [never] ? Record<never, never> : Flatten<UnionToIntersection<U>>
258
273
 
259
274
  /**
260
- * A slot's identity for the positive-join judgment: the field's structural
261
- * signature ({@link SignatureOf} the ONE interpreter, shared with the
262
- * face pairing wall) plus the slot's law class.
263
- */
264
- type SlotSignature<S extends ClassedField> = readonly [SignatureOf<S["field"]>, S["class"]]
265
-
266
- /**
267
- * A widened slot — a variable minted from an imported query template (its
268
- * facade columns carry `AnyField`) or fully generic code — cannot be judged
275
+ * A widened slot an explicitly erased query template or fully generic
276
+ * code with no retained descriptor cannot be judged
269
277
  * at the type tier. The degradation law applies: best effort degrades to
270
278
  * SILENT, never to a wrong refusal; the runtime join judgment (real
271
279
  * descriptors off the imported head's slots) stays authoritative.
@@ -273,7 +281,15 @@ type SlotSignature<S extends ClassedField> = readonly [SignatureOf<S["field"]>,
273
281
  type WidenedSlot<S extends ClassedField> = [AnyField] extends [S["field"]] ? true : false
274
282
 
275
283
  type JoinOk<A extends ClassedField, B extends ClassedField> =
276
- WidenedSlot<A> extends true ? true : WidenedSlot<B> extends true ? true : Same<SlotSignature<A>, SlotSignature<B>>
284
+ WidenedSlot<A> extends true
285
+ ? true
286
+ : WidenedSlot<B> extends true
287
+ ? true
288
+ : Same<SignatureOf<A["field"]>, SignatureOf<B["field"]>> extends true
289
+ ? string extends A["class"] | B["class"]
290
+ ? true
291
+ : Same<A["class"], B["class"]>
292
+ : false
277
293
 
278
294
  type U64Wire<F extends AnyField> = F extends { readonly kind: "u64" }
279
295
  ? F extends { readonly closed: unknown }
@@ -390,6 +406,7 @@ export type {
390
406
  ClassedField,
391
407
  ExactVars,
392
408
  Flatten,
409
+ ImportedFieldVar,
393
410
  ImportedSource,
394
411
  ImportVars,
395
412
  InferredOf,
@@ -0,0 +1,61 @@
1
+ import { AuthoringError } from "#errors.ts"
2
+ import type { IntervalElementKind, IntervalField } from "#fields.ts"
3
+ import { type AnyVar, isTerm, term } from "#query/scope.ts"
4
+ import { recordValue } from "#values.ts"
5
+
6
+ type IntervalVar<E extends IntervalElementKind = IntervalElementKind> = AnyVar & { readonly field: IntervalField<E> }
7
+ type SegmentOp = "intersection" | "difference"
8
+
9
+ /** A relational find output, using the native binary segment operator directly. */
10
+ interface Segments<E extends IntervalElementKind = IntervalElementKind> {
11
+ readonly kind: "segments"
12
+ readonly op: SegmentOp
13
+ readonly left: IntervalVar<E>
14
+ readonly right: IntervalVar<E>
15
+ }
16
+
17
+ function segments<E extends IntervalElementKind>(
18
+ op: SegmentOp,
19
+ left: IntervalVar<E>,
20
+ right: IntervalVar<NoInfer<E>>
21
+ ): Segments<E> {
22
+ if (
23
+ !isTerm(left) ||
24
+ left[term] !== "var" ||
25
+ !isTerm(right) ||
26
+ right[term] !== "var" ||
27
+ left.field.kind !== "interval" ||
28
+ right.field.kind !== "interval" ||
29
+ left.field.element !== right.field.element
30
+ )
31
+ throw new AuthoringError({ message: `${op}: expected interval variables of the same element kind` })
32
+ return Object.freeze({ kind: "segments", op, left, right })
33
+ }
34
+
35
+ function intersection<E extends IntervalElementKind>(
36
+ left: IntervalVar<E>,
37
+ right: IntervalVar<NoInfer<E>>
38
+ ): Segments<E> {
39
+ return segments("intersection", left, right)
40
+ }
41
+
42
+ function difference<E extends IntervalElementKind>(left: IntervalVar<E>, right: IntervalVar<NoInfer<E>>): Segments<E> {
43
+ return segments("difference", left, right)
44
+ }
45
+
46
+ function isSegments(input: unknown): input is Segments {
47
+ if (typeof input !== "object" || input === null || !("kind" in input) || input.kind !== "segments") return false
48
+ recordValue("segments", input, ["kind", "op", "left", "right"])
49
+ const value = input as Segments
50
+ if (value.op !== "intersection" && value.op !== "difference")
51
+ throw new AuthoringError({ message: "unknown segment operator" })
52
+ segments(value.op, value.left, value.right)
53
+ return true
54
+ }
55
+
56
+ function segmentField(value: Segments): IntervalField<IntervalElementKind, undefined> {
57
+ return Object.freeze({ kind: "interval", element: value.left.field.element, width: undefined })
58
+ }
59
+
60
+ export type { IntervalVar, SegmentOp, Segments }
61
+ export { difference, intersection, isSegments, segmentField }
package/src/scalar.ts CHANGED
@@ -16,6 +16,7 @@
16
16
  * query IR — `{ kind: "literal", value: ValueSpec }` via `literalWireOf`
17
17
  */
18
18
  import { AuthoringError } from "#errors.ts"
19
+ import type { ScalarExprIr } from "#native.ts"
19
20
  import type { ValueSpec } from "#spec.ts"
20
21
 
21
22
  /** F0: leaf scope for the shared AST (C1). Query vars are typed; source fields stay unresolved. */
@@ -25,7 +26,10 @@ export type ScalarLeafScope = "query-var" | "source-field"
25
26
  type ScalarKind = "u64" | "i64" | "f64" | "bool"
26
27
 
27
28
  /** Cached result kind: known only when derivable; otherwise honestly unresolved. */
28
- type ScalarResultKind = ScalarKind | "unresolved"
29
+ type IntervalKind = "intervalU64" | "intervalI64" | "intervalF64"
30
+ type ScalarInputKind = ScalarKind | IntervalKind
31
+ type ScalarResultKind = ScalarInputKind | "unresolved"
32
+ type Rounding = "towardZero" | "nearestTiesAwayFromZero" | "nearestTiesToEven"
29
33
 
30
34
  /** Host value for a derived scalar kind. */
31
35
  type ScalarValue<K extends ScalarKind> = K extends "f64" ? number : K extends "bool" ? boolean : bigint
@@ -60,7 +64,18 @@ type ScalarLiteral =
60
64
  | { readonly i64: bigint }
61
65
  | { readonly f64: number }
62
66
 
63
- type ScalarOpKind = "literal" | "negate" | "isNaN" | "isFinite" | "add" | "subtract" | "multiply" | "divide" | "cast"
67
+ type ScalarOpKind =
68
+ | "literal"
69
+ | "negate"
70
+ | "isNaN"
71
+ | "isFinite"
72
+ | "add"
73
+ | "subtract"
74
+ | "multiply"
75
+ | "divide"
76
+ | "cast"
77
+ | "mulDiv"
78
+ | "measure"
64
79
 
65
80
  /**
66
81
  * Structural query-variable leaf. The query layer supplies a bound variable
@@ -79,6 +94,14 @@ type SourceFieldLeaf = { readonly kind: "field"; readonly name: string }
79
94
  type ScalarLeaf<S extends ScalarLeafScope> = S extends "query-var" ? QueryVarLeaf : SourceFieldLeaf
80
95
 
81
96
  type ScalarNodeBody<S extends ScalarLeafScope> =
97
+ | { readonly kind: "measure"; readonly expr: ScalarNode<S> }
98
+ | {
99
+ readonly kind: "mulDiv"
100
+ readonly a: ScalarNode<S>
101
+ readonly b: ScalarNode<S>
102
+ readonly divisor: ScalarNode<S>
103
+ readonly rounding: Rounding
104
+ }
82
105
  | ScalarLeaf<S>
83
106
  | { readonly kind: "literal"; readonly value: ScalarLiteral }
84
107
  | { readonly kind: "negate"; readonly expr: ScalarNode<S> }
@@ -197,13 +220,13 @@ function assertDepth(where: string, depth: number): void {
197
220
  }
198
221
  }
199
222
 
200
- function assertNumeric(where: string, kind: ScalarKind): void {
201
- if (kind === "bool") {
223
+ function assertNumeric(where: string, kind: ScalarInputKind): asserts kind is NumericKind {
224
+ if (kind !== "u64" && kind !== "i64" && kind !== "f64") {
202
225
  throw new AuthoringError({ message: `${where}: the operand is bool, not numeric (u64/i64/f64)` })
203
226
  }
204
227
  }
205
228
 
206
- function assertSameKind(where: string, left: ScalarKind, right: ScalarKind): ScalarKind {
229
+ function assertSameKind(where: string, left: ScalarInputKind, right: ScalarInputKind): ScalarInputKind {
207
230
  if (left !== right) {
208
231
  throw new AuthoringError({
209
232
  message: `${where}: operand kinds differ (${left} vs ${right}) — the engine has no mixed promotion; cast explicitly (Scalar.toF64/ toF64Exact/ toI64Exact/ toU64Exact)`
@@ -294,7 +317,7 @@ function sourceField(name: string): ScalarFieldRef {
294
317
  }
295
318
 
296
319
  /** Query-var leaf — kind is the variable's schema kind, already known. */
297
- function queryVarLeaf<K extends ScalarKind>(ref: ScalarQueryVar, kind: K): ScalarNode<"query-var", K> {
320
+ function queryVarLeaf<K extends ScalarInputKind>(ref: ScalarQueryVar, kind: K): ScalarNode<"query-var", K> {
298
321
  return admit("Scalar.queryVar", {
299
322
  kind: "var",
300
323
  ref,
@@ -334,6 +357,56 @@ function scalarBinary<S extends ScalarLeafScope>(
334
357
  })
335
358
  }
336
359
 
360
+ function roundingMode(value: unknown): Rounding {
361
+ if (value !== "towardZero" && value !== "nearestTiesAwayFromZero" && value !== "nearestTiesToEven")
362
+ throw new AuthoringError({ message: "unknown integer rounding mode" })
363
+ return value
364
+ }
365
+
366
+ function scalarMulDiv<S extends ScalarLeafScope>(
367
+ where: string,
368
+ a: ScalarNode<S>,
369
+ b: ScalarNode<S>,
370
+ divisor: ScalarNode<S>,
371
+ rounding: Rounding
372
+ ): ScalarNode<S> {
373
+ const scope = assertScope(where, a, b)
374
+ assertScope(where, a, divisor)
375
+ let known: "i64" | "u64" | undefined
376
+ for (const operand of [a, b, divisor]) {
377
+ if (operand.result === "unresolved") continue
378
+ if ((operand.result !== "i64" && operand.result !== "u64") || (known !== undefined && known !== operand.result))
379
+ throw new AuthoringError({ message: `${where}: operands must have one matching integer kind` })
380
+ known = operand.result
381
+ }
382
+ return admit(where, {
383
+ kind: "mulDiv",
384
+ a,
385
+ b,
386
+ divisor,
387
+ rounding: roundingMode(rounding),
388
+ scope,
389
+ result:
390
+ a.result === "unresolved" || b.result === "unresolved" || divisor.result === "unresolved"
391
+ ? "unresolved"
392
+ : a.result,
393
+ depth: Math.max(a.depth, b.depth, divisor.depth) + 1
394
+ })
395
+ }
396
+
397
+ function scalarMeasure<S extends ScalarLeafScope>(where: string, expr: ScalarNode<S>): ScalarNode<S> {
398
+ const kind = expr.result
399
+ if (kind !== "unresolved" && kind !== "intervalU64" && kind !== "intervalI64" && kind !== "intervalF64")
400
+ throw new AuthoringError({ message: `${where}: measure requires an interval` })
401
+ return admit(where, {
402
+ kind: "measure",
403
+ expr,
404
+ scope: expr.scope,
405
+ result: ({ unresolved: "unresolved", intervalF64: "f64", intervalI64: "u64", intervalU64: "u64" } as const)[kind],
406
+ depth: expr.depth + 1
407
+ })
408
+ }
409
+
337
410
  function scalarNegate<S extends ScalarLeafScope>(where: string, expr: ScalarNode<S>): ScalarNode<S, ScalarResultKind> {
338
411
  return admit(where, {
339
412
  kind: "negate",
@@ -391,12 +464,16 @@ function queryVarsOf(node: ScalarNode<"query-var">): readonly ScalarQueryVar[] {
391
464
  break
392
465
  case "literal":
393
466
  break
467
+ case "measure":
394
468
  case "negate":
395
469
  case "isNaN":
396
470
  case "isFinite":
397
471
  case "cast":
398
472
  pending.push(next.expr)
399
473
  break
474
+ case "mulDiv":
475
+ pending.push(next.a, next.b, next.divisor)
476
+ break
400
477
  case "add":
401
478
  case "subtract":
402
479
  case "multiply":
@@ -472,6 +549,27 @@ function divide<
472
549
  return scalarBinary("Scalar.divide", "divide", left, right) as ScalarExpr<CombineNumeric<L, R>>
473
550
  }
474
551
 
552
+ function mulDiv<
553
+ L extends "i64" | "u64" | "unresolved",
554
+ R extends CombineNumeric<L, R> extends never ? never : "i64" | "u64" | "unresolved",
555
+ D extends CombineNumeric<L, D> extends never
556
+ ? never
557
+ : CombineNumeric<R, D> extends never
558
+ ? never
559
+ : "i64" | "u64" | "unresolved"
560
+ >(
561
+ a: ScalarExpr<L>,
562
+ b: ScalarExpr<R>,
563
+ divisor: ScalarExpr<D>,
564
+ rounding: Rounding
565
+ ): ScalarExpr<CombineNumeric<CombineNumeric<L, R>, D>> {
566
+ return scalarMulDiv("Scalar.mulDiv", a, b, divisor, rounding) as ScalarExpr<CombineNumeric<CombineNumeric<L, R>, D>>
567
+ }
568
+
569
+ function measure(expr: ScalarFieldRef): ScalarExpr<"unresolved"> {
570
+ return scalarMeasure("Scalar.measure", expr) as ScalarExpr<"unresolved">
571
+ }
572
+
475
573
  function toF64(expr: ScalarExpr<NumericOrUnresolved>): ScalarExpr<"f64"> {
476
574
  return scalarCast("Scalar.toF64", "toF64", "f64", expr) as ScalarExpr<"f64">
477
575
  }
@@ -496,6 +594,32 @@ function isFiniteExpr(expr: ScalarExpr<"f64" | "unresolved">): ScalarExpr<"bool"
496
594
  return scalarFloatPredicate("Scalar.isFinite", "isFinite", expr)
497
595
  }
498
596
 
597
+ /** One lowering walk for query variables and row-field bindings. */
598
+ function scalarWire(node: ScalarNode, leaf: (node: QueryVarLeaf | SourceFieldLeaf) => number): ScalarExprIr {
599
+ const walk = (expr: ScalarNode): ScalarExprIr => scalarWire(expr, leaf)
600
+ switch (node.kind) {
601
+ case "var":
602
+ case "field":
603
+ return { kind: "var", var: leaf(node) }
604
+ case "literal":
605
+ return { kind: "literal", value: literalWireOf(node.value) }
606
+ case "measure":
607
+ case "negate":
608
+ case "isNaN":
609
+ case "isFinite":
610
+ return { kind: node.kind, expr: walk(node.expr) }
611
+ case "cast":
612
+ return { kind: node.kind, cast: node.cast, expr: walk(node.expr) }
613
+ case "mulDiv":
614
+ return { kind: node.kind, a: walk(node.a), b: walk(node.b), divisor: walk(node.divisor), rounding: node.rounding }
615
+ case "add":
616
+ case "subtract":
617
+ case "multiply":
618
+ case "divide":
619
+ return { kind: node.kind, left: walk(node.left), right: walk(node.right) }
620
+ }
621
+ }
622
+
499
623
  /** The authoring namespace: `Scalar.field("units")`, `Scalar.add(...)`. */
500
624
  const Scalar = Object.freeze({
501
625
  field,
@@ -509,6 +633,8 @@ const Scalar = Object.freeze({
509
633
  subtract,
510
634
  multiply,
511
635
  divide,
636
+ mulDiv,
637
+ measure,
512
638
  toF64,
513
639
  toF64Exact,
514
640
  toI64Exact,
@@ -519,12 +645,15 @@ const Scalar = Object.freeze({
519
645
 
520
646
  export type {
521
647
  CombineNumeric,
648
+ IntervalKind,
522
649
  NumericCast,
523
650
  NumericKind,
524
651
  NumericOrUnresolved,
525
652
  QueryVarLeaf,
653
+ Rounding,
526
654
  ScalarExpr,
527
655
  ScalarFieldRef,
656
+ ScalarInputKind,
528
657
  ScalarKind,
529
658
  ScalarLiteral,
530
659
  ScalarNode,
@@ -550,11 +679,15 @@ export {
550
679
  MAX_SCALAR_DEPTH,
551
680
  queryVarLeaf,
552
681
  queryVarsOf,
682
+ roundingMode,
553
683
  Scalar,
554
684
  scalarAuthoringWork,
555
685
  scalarBinary,
556
686
  scalarCast,
557
687
  scalarFloatPredicate,
558
688
  scalarLiteral,
559
- scalarNegate
689
+ scalarMeasure,
690
+ scalarMulDiv,
691
+ scalarNegate,
692
+ scalarWire
560
693
  }
package/src/shape.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Query } from "#query/lower.ts"
2
- import type { ParamsRecord } from "#query/scope.ts"
2
+ import type { inferred, ParamsRecord } from "#query/scope.ts"
3
3
  /**
4
4
  * Shared derived types: `S` is a
5
5
  * declared core schema value's type, `Rel<S>` its ordinary (writable)
@@ -24,7 +24,13 @@ type Key<K extends KeyStatement<AnyRelation, readonly string[]>> = Pick<
24
24
  Extract<K["projection"][number], keyof Fact<K["owner"]>>
25
25
  >
26
26
 
27
- /** Immutable typed query template. */
28
- type QueryTemplate<S extends AnySchema, P extends ParamsRecord, A> = Query<S["relations"], A, P>
27
+ /** The immutable executable view of a query. Readers consume its schema/data
28
+ * and inferred result/parameters, not the author's rule-building methods.
29
+ * Runtime schema identity remains authoritative for dynamically built schemas.
30
+ */
31
+ type QueryTemplate<S extends AnySchema, P extends ParamsRecord, A> = Pick<
32
+ Query<S["relations"], A, P>,
33
+ "schema" | "data" | typeof inferred
34
+ >
29
35
 
30
36
  export type { Key, QueryTemplate, Rel }