@bjornpagen/bumbledb 0.4.0 → 0.5.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 (92) hide show
  1. package/COOKBOOK.md +181 -47
  2. package/README.md +4 -2
  3. package/dist/closed.d.ts +30 -2
  4. package/dist/closed.d.ts.map +1 -1
  5. package/dist/closed.js +66 -20
  6. package/dist/closed.js.map +1 -1
  7. package/dist/db.d.ts +33 -6
  8. package/dist/db.d.ts.map +1 -1
  9. package/dist/db.js +87 -70
  10. package/dist/db.js.map +1 -1
  11. package/dist/exhume.d.ts.map +1 -1
  12. package/dist/exhume.js +1 -14
  13. package/dist/exhume.js.map +1 -1
  14. package/dist/face.d.ts +1 -1
  15. package/dist/face.d.ts.map +1 -1
  16. package/dist/face.js +2 -1
  17. package/dist/face.js.map +1 -1
  18. package/dist/fields.d.ts +18 -1
  19. package/dist/fields.d.ts.map +1 -1
  20. package/dist/fields.js +44 -16
  21. package/dist/fields.js.map +1 -1
  22. package/dist/index.d.ts +11 -6
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +8 -4
  25. package/dist/index.js.map +1 -1
  26. package/dist/law.d.ts +2 -1
  27. package/dist/law.d.ts.map +1 -1
  28. package/dist/law.js +15 -14
  29. package/dist/law.js.map +1 -1
  30. package/dist/lower.d.ts.map +1 -1
  31. package/dist/lower.js +1 -7
  32. package/dist/lower.js.map +1 -1
  33. package/dist/marshal.d.ts.map +1 -1
  34. package/dist/marshal.js +15 -27
  35. package/dist/marshal.js.map +1 -1
  36. package/dist/native.d.ts +21 -2
  37. package/dist/native.d.ts.map +1 -1
  38. package/dist/native.js +20 -3
  39. package/dist/native.js.map +1 -1
  40. package/dist/order.d.ts +36 -0
  41. package/dist/order.d.ts.map +1 -0
  42. package/dist/order.js +135 -0
  43. package/dist/order.js.map +1 -0
  44. package/dist/query/atom.d.ts +18 -15
  45. package/dist/query/atom.d.ts.map +1 -1
  46. package/dist/query/atom.js +9 -16
  47. package/dist/query/atom.js.map +1 -1
  48. package/dist/query/lower.d.ts +5 -8
  49. package/dist/query/lower.d.ts.map +1 -1
  50. package/dist/query/lower.js +87 -76
  51. package/dist/query/lower.js.map +1 -1
  52. package/dist/query/predicate.d.ts.map +1 -1
  53. package/dist/query/predicate.js +34 -2
  54. package/dist/query/predicate.js.map +1 -1
  55. package/dist/query/run.d.ts.map +1 -1
  56. package/dist/query/run.js +1 -2
  57. package/dist/query/run.js.map +1 -1
  58. package/dist/query/scope.d.ts +2 -16
  59. package/dist/query/scope.d.ts.map +1 -1
  60. package/dist/query/scope.js +7 -47
  61. package/dist/query/scope.js.map +1 -1
  62. package/dist/relation.d.ts +9 -22
  63. package/dist/relation.d.ts.map +1 -1
  64. package/dist/relation.js +9 -28
  65. package/dist/relation.js.map +1 -1
  66. package/dist/schema.d.ts.map +1 -1
  67. package/dist/schema.js +7 -31
  68. package/dist/schema.js.map +1 -1
  69. package/dist/statements.d.ts +5 -2
  70. package/dist/statements.d.ts.map +1 -1
  71. package/dist/statements.js +30 -35
  72. package/dist/statements.js.map +1 -1
  73. package/package.json +2 -5
  74. package/src/closed.ts +73 -28
  75. package/src/db.ts +122 -84
  76. package/src/exhume.ts +1 -15
  77. package/src/face.ts +4 -3
  78. package/src/fields.ts +58 -16
  79. package/src/index.ts +10 -8
  80. package/src/law.ts +15 -14
  81. package/src/lower.ts +2 -9
  82. package/src/marshal.ts +15 -31
  83. package/src/native.ts +22 -4
  84. package/src/order.ts +156 -0
  85. package/src/query/atom.ts +13 -21
  86. package/src/query/lower.ts +90 -91
  87. package/src/query/predicate.ts +39 -4
  88. package/src/query/run.ts +1 -3
  89. package/src/query/scope.ts +7 -59
  90. package/src/relation.ts +9 -51
  91. package/src/schema.ts +7 -33
  92. package/src/statements.ts +33 -38
package/src/marshal.ts CHANGED
@@ -29,6 +29,7 @@
29
29
 
30
30
  import * as errors from "@superbuilders/errors"
31
31
  import type { AnyField, ClosedRoster } from "#fields.ts"
32
+ import { isIntervalValue, literalShapeError, rosterOf } from "#fields.ts"
32
33
  import type { FactValue } from "#native.ts"
33
34
  import type { AnyRelation, Fact, FreshKeys, RelationData } from "#relation.ts"
34
35
 
@@ -66,23 +67,6 @@ type KeyFact<R extends AnyRelation> = [FreshKeys<R>] extends [never]
66
67
  ? Partial<Fact<R>>
67
68
  : { [K in FreshKeys<R>]: Fact<R>[K] }
68
69
 
69
- /** The typed shape refusal of the row marshaler — a genuine failure, never data. */
70
- function cellShapeError(context: string, expected: string, value: unknown): Error {
71
- return errors.new(`${context}: expected ${expected}, got ${typeof value}`)
72
- }
73
-
74
- /** Narrows an interval cell: a plain object with bigint start/end. */
75
- function isIntervalCell(value: unknown): value is { readonly start: bigint; readonly end: bigint } {
76
- return (
77
- typeof value === "object" &&
78
- value !== null &&
79
- "start" in value &&
80
- "end" in value &&
81
- typeof value.start === "bigint" &&
82
- typeof value.end === "bigint"
83
- )
84
- }
85
-
86
70
  /**
87
71
  * Reprojects any host object to a string-indexed record — the boundary
88
72
  * through which generic fact objects (whose type parameters carry no index
@@ -121,7 +105,7 @@ function closedCellOf(context: string, closed: ClosedRoster, name: string): Fact
121
105
  */
122
106
  function handleOf(context: string, closed: ClosedRoster, cell: FactValue): string {
123
107
  if (typeof cell !== "bigint") {
124
- throw cellShapeError(context, `a ${closed.name} handle id (bigint)`, cell)
108
+ throw literalShapeError(context, `a ${closed.name} handle id (bigint)`, cell)
125
109
  }
126
110
  const handle = closed.handles[Number(cell)]
127
111
  if (handle === undefined) {
@@ -144,29 +128,30 @@ function handleOf(context: string, closed: ClosedRoster, cell: FactValue): strin
144
128
  * boundary.
145
129
  */
146
130
  function cellOf(context: string, field: AnyField, value: unknown): FactValue {
147
- if ("closed" in field) {
131
+ const roster = rosterOf(field)
132
+ if (roster !== undefined) {
148
133
  if (typeof value !== "string") {
149
- throw cellShapeError(context, `a ${field.closed.name} handle name (string)`, value)
134
+ throw literalShapeError(context, `a ${roster.name} handle name (string)`, value)
150
135
  }
151
- return closedCellOf(context, field.closed, value)
136
+ return closedCellOf(context, roster, value)
152
137
  }
153
138
  switch (field.kind) {
154
139
  case "bool": {
155
140
  if (typeof value !== "boolean") {
156
- throw cellShapeError(context, "boolean", value)
141
+ throw literalShapeError(context, "boolean", value)
157
142
  }
158
143
  return value
159
144
  }
160
145
  case "u64":
161
146
  case "i64": {
162
147
  if (typeof value !== "bigint") {
163
- throw cellShapeError(context, "bigint", value)
148
+ throw literalShapeError(context, "bigint", value)
164
149
  }
165
150
  return value
166
151
  }
167
152
  case "str": {
168
153
  if (typeof value !== "string") {
169
- throw cellShapeError(context, "string", value)
154
+ throw literalShapeError(context, "string", value)
170
155
  }
171
156
  /**
172
157
  * A lone surrogate would be lossily replaced with U+FFFD at the
@@ -176,19 +161,19 @@ function cellOf(context: string, field: AnyField, value: unknown): FactValue {
176
161
  * lookup lowers through.
177
162
  */
178
163
  if (!value.isWellFormed()) {
179
- throw cellShapeError(context, "well-formed string", value)
164
+ throw literalShapeError(context, "well-formed string", value)
180
165
  }
181
166
  return value
182
167
  }
183
168
  case "bytes": {
184
169
  if (!(value instanceof Uint8Array)) {
185
- throw cellShapeError(context, "Uint8Array", value)
170
+ throw literalShapeError(context, "Uint8Array", value)
186
171
  }
187
172
  return value
188
173
  }
189
174
  case "interval": {
190
- if (!isIntervalCell(value)) {
191
- throw cellShapeError(context, "interval ({ start, end } bigints)", value)
175
+ if (!isIntervalValue(value)) {
176
+ throw literalShapeError(context, "interval ({ start, end } bigints)", value)
192
177
  }
193
178
  return { start: value.start, end: value.end }
194
179
  }
@@ -289,10 +274,9 @@ function factOf<R extends AnyRelation>(relation: R, row: readonly FactValue[]):
289
274
  if (cell === undefined) {
290
275
  throw errors.new(`relation ${data.name}: row cell ${ordinal} (${declared.name}) is absent`)
291
276
  }
277
+ const roster = rosterOf(declared.field)
292
278
  decoded[declared.name] =
293
- "closed" in declared.field
294
- ? handleOf(`relation ${data.name} field ${declared.name}`, declared.field.closed, cell)
295
- : cell
279
+ roster !== undefined ? handleOf(`relation ${data.name} field ${declared.name}`, roster, cell) : cell
296
280
  })
297
281
  Object.freeze(decoded)
298
282
  if (!isCompleteFact(relation, decoded)) {
package/src/native.ts CHANGED
@@ -241,7 +241,7 @@ interface Violation {
241
241
  }
242
242
 
243
243
  /**
244
- * `dbCreate`/`dbOpen`'s domain outcome. `schemaError` covers both spec
244
+ * `dbCreate`/`dbOpen`'s domain outcome. `schemaError` spans both spec
245
245
  * resolution (unresolvable names, banned spellings — every issue in one
246
246
  * message) and schema validation at the declaration boundary;
247
247
  * `newtypeMismatch` is the coherence wall's own kind — a spec whose
@@ -471,8 +471,12 @@ interface Native {
471
471
  * The sole platform this release ships (PRD-03 ruling 1: prebuilt-only,
472
472
  * darwin-arm64). The per-platform-package structure below makes adding
473
473
  * `darwin-x64`/`linux-*`/`win32-*` pure addition — one more `os`/`cpu`-gated
474
- * package plus a CI matrix — never a redesign, so this string is the only
475
- * place the shipped set is named for the unsupported-platform message.
474
+ * package plus a CI matrix — never a redesign. This constant names the
475
+ * shipped set for the unsupported-platform message; the build's
476
+ * `PUBLISH_PLATFORM` (`scripts/platform.ts` — src cannot import scripts,
477
+ * the packaging boundary) and the `ts/.gitignore` carve-out spell the same
478
+ * target, and the single-source pin in `test/build-platform.test.ts` holds
479
+ * all three in lockstep.
476
480
  */
477
481
  const SHIPPED_PLATFORMS = "darwin-arm64"
478
482
 
@@ -536,6 +540,20 @@ function loadNativeBinding(platform: string, arch: string): Native {
536
540
  */
537
541
  const native: Native = loadNativeBinding(process.platform, process.arch)
538
542
 
543
+ /**
544
+ * The bridge guard — THE one wrapper every native call crosses (db.ts and
545
+ * exhume.ts both import it): runs one native call and wraps anything it
546
+ * throws, so marshal-shape refusals and handle-lifecycle refusals cross as
547
+ * genuine typed failures, never bare foreign errors.
548
+ */
549
+ function bridged<T>(context: string, run: () => T): T {
550
+ const result = errors.trySync(run)
551
+ if (result.error) {
552
+ throw errors.wrap(result.error, context)
553
+ }
554
+ return result.data
555
+ }
556
+
539
557
  export type {
540
558
  AggOpIr,
541
559
  AtomIr,
@@ -577,4 +595,4 @@ export type {
577
595
  ViolationFact,
578
596
  WriteFromResult
579
597
  }
580
- export { loadNativeBinding, native }
598
+ export { bridged, loadNativeBinding, native, SHIPPED_PLATFORMS }
package/src/order.ts ADDED
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Host-side answer ordering — the census-fired convenience
3
+ * (`docs/architecture/70-api.md` § the freeze ledger). Answers are SETS and
4
+ * the ENGINE NEVER ORDERS; the language owns the sort (`rows.sort(...)`)
5
+ * and the limit (`.slice(0, n)`) — the drizzle law. What JS lacks is a
6
+ * number-returning comparator over the SDK's bigint-bearing cells, so the
7
+ * SDK ships exactly that: sort keys are DATA — a bare column name is
8
+ * ascending (the punning spelling; no `asc` wrapper exists — one spelling
9
+ * per meaning) and `desc(name)` is the one descending spelling — folded by
10
+ * `by(...)` into one row-typed comparator. Cross-type cells cannot arise
11
+ * within one column (one column, one domain), and the cell order is TOTAL
12
+ * anyway (the type-rank wall), so the comparator never throws.
13
+ */
14
+
15
+ import type { FactValue } from "#native.ts"
16
+
17
+ /** One DESCENDING sort key, plain data — built by {@link desc}. */
18
+ interface Desc<K extends string> {
19
+ readonly key: K
20
+ readonly desc: true
21
+ }
22
+
23
+ /** One sort key: a bare column name (ascending — the punning spelling) or `desc(name)`. */
24
+ type SortKey<K extends string> = K | Desc<K>
25
+
26
+ /**
27
+ * The type-rank wall: boolean 0, bigint 1, string 2, bytes 3, interval 4.
28
+ * One column carries one domain, so a mixed pair never arises from decoded
29
+ * answer rows — the wall exists to keep the cell order TOTAL (never a
30
+ * throw), not to be reached.
31
+ */
32
+ function cellRank(value: FactValue): number {
33
+ if (typeof value === "boolean") {
34
+ return 0
35
+ }
36
+ if (typeof value === "bigint") {
37
+ return 1
38
+ }
39
+ if (typeof value === "string") {
40
+ return 2
41
+ }
42
+ if (value instanceof Uint8Array) {
43
+ return 3
44
+ }
45
+ return 4
46
+ }
47
+
48
+ /**
49
+ * One cell against one cell. Same-type arms: boolean orders false < true;
50
+ * bigint by `<`/`>`; string by the host language's own `<`/`>` (flavor,
51
+ * recorded); bytes bytewise over the shared prefix, then by length;
52
+ * intervals by start, then end. A mixed pair falls through to the
53
+ * type-rank wall.
54
+ */
55
+ function cellCmp(left: FactValue, right: FactValue): number {
56
+ if (typeof left === "boolean" && typeof right === "boolean") {
57
+ if (left === right) {
58
+ return 0
59
+ }
60
+ if (left) {
61
+ return 1
62
+ }
63
+ return -1
64
+ }
65
+ if (typeof left === "bigint" && typeof right === "bigint") {
66
+ if (left < right) {
67
+ return -1
68
+ }
69
+ if (left > right) {
70
+ return 1
71
+ }
72
+ return 0
73
+ }
74
+ if (typeof left === "string" && typeof right === "string") {
75
+ if (left < right) {
76
+ return -1
77
+ }
78
+ if (left > right) {
79
+ return 1
80
+ }
81
+ return 0
82
+ }
83
+ if (left instanceof Uint8Array && right instanceof Uint8Array) {
84
+ const shared = Math.min(left.length, right.length)
85
+ for (let index = 0; index < shared; index += 1) {
86
+ const leftByte = left[index]
87
+ const rightByte = right[index]
88
+ // `index < shared` keeps both reads in bounds; the `undefined`
89
+ // arms are the checker's indexed-access tax, never taken.
90
+ if (leftByte !== undefined && rightByte !== undefined && leftByte !== rightByte) {
91
+ return leftByte - rightByte
92
+ }
93
+ }
94
+ return left.length - right.length
95
+ }
96
+ if (
97
+ typeof left === "object" &&
98
+ !(left instanceof Uint8Array) &&
99
+ typeof right === "object" &&
100
+ !(right instanceof Uint8Array)
101
+ ) {
102
+ if (left.start < right.start) {
103
+ return -1
104
+ }
105
+ if (left.start > right.start) {
106
+ return 1
107
+ }
108
+ if (left.end < right.end) {
109
+ return -1
110
+ }
111
+ if (left.end > right.end) {
112
+ return 1
113
+ }
114
+ return 0
115
+ }
116
+ return cellRank(left) - cellRank(right)
117
+ }
118
+
119
+ /** Marks one sort key DESCENDING — the single descending spelling (a bare name is already ascending). */
120
+ function desc<const K extends string>(key: K): Desc<K> {
121
+ const marker: Desc<K> = { key, desc: true }
122
+ return Object.freeze(marker)
123
+ }
124
+
125
+ /**
126
+ * Folds sort keys into ONE comparator typed against the row —
127
+ * `Row extends Readonly<Record<K, FactValue>>` — so a key the row lacks, or
128
+ * a column typed `number` (outside the cell domain), is a COMPILE error at
129
+ * the `.sort` call site: the laws typed the columns, and the row type
130
+ * carries that proof here (parse-don't-validate). The generic RETURN is the
131
+ * load-bearing trick: `rows.sort(by("rank"))` instantiates `Row` from the
132
+ * array's own element type and checks the key set right there.
133
+ */
134
+ function by<const K extends string>(
135
+ first: SortKey<K>,
136
+ ...rest: ReadonlyArray<SortKey<K>>
137
+ ): <Row extends Readonly<Record<K, FactValue>>>(left: Row, right: Row) => number {
138
+ const entries = [first, ...rest].map(function normalizeKey(sortKey): { readonly key: K; readonly factor: 1 | -1 } {
139
+ if (typeof sortKey === "string") {
140
+ return { key: sortKey, factor: 1 }
141
+ }
142
+ return { key: sortKey.key, factor: -1 }
143
+ })
144
+ return function compare<Row extends Readonly<Record<K, FactValue>>>(left: Row, right: Row): number {
145
+ for (const entry of entries) {
146
+ const order = cellCmp(left[entry.key], right[entry.key]) * entry.factor
147
+ if (order !== 0) {
148
+ return order
149
+ }
150
+ }
151
+ return 0
152
+ }
153
+ }
154
+
155
+ export type { Desc, SortKey }
156
+ export { by, desc }
package/src/query/atom.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  * by owner ruling; see {@link BindingInput})
10
10
  * (unmentioned fields ARE the wildcard — no wildcard value exists);
11
11
  * `not(Rel, {...})` is negation-as-position (anti-join); `eq`/`ne` and the
12
- * order roster, `pointIn`/`covers` (both spellings of `ir::CmpOp::PointIn`,
12
+ * order roster, `pointIn` (the one spelling of `ir::CmpOp::PointIn`,
13
13
  * always lowered interval-left), `allen` (the 13-bit mask pair
14
14
  * comparison), and `and`/`or` (the input condition-tree grammar) complete
15
15
  * the roster. Nothing beyond the IR exists here — and the walls the engine
@@ -62,7 +62,7 @@ type MatchOwner = AnyRelation | AnyClosed
62
62
  * union rides into ψ id bindings and joins exactly as it does on a
63
63
  * referencing column) first, then the declared payload columns read
64
64
  * through the typed `columns` carrier (the one source of payload typing —
65
- * no parallel column table exists). The runtime twin is `matchFieldsOf` in
65
+ * no parallel column table exists). The runtime twin is `sealedFieldsOf` in
66
66
  * `#query/lower.ts`; the id-first ordinal shift the two tiers share is
67
67
  * pinned by the lowering golden.
68
68
  */
@@ -355,10 +355,10 @@ type NeRight = Var<string> | Param<string> | bigint | string | boolean | Uint8Ar
355
355
  /** One side of an order comparison: orderable terms only (the IR's comparison rules). */
356
356
  type OrderSide = Var<string> | Param<string> | Duration<string> | bigint
357
357
 
358
- /** The point side of `pointIn`/`covers`. */
358
+ /** The point side of `pointIn`. */
359
359
  type PointSide = Var<string> | Param<string> | bigint
360
360
 
361
- /** The interval side of `pointIn`/`covers`/`allen`. */
361
+ /** The interval side of `pointIn`/`allen`. */
362
362
  type IntervalSide = Var<string> | Param<string> | IntervalValue
363
363
 
364
364
  /** Builds one comparison value. */
@@ -426,29 +426,21 @@ function ge<const L extends OrderSide, const R extends OrderSide>(left: L, right
426
426
  }
427
427
 
428
428
  /**
429
- * Point membership as a predicate (`ir::CmpOp::PointIn`), membership
429
+ * Point membership as a predicate (`ir::CmpOp::PointIn`) — THE one
430
430
  * spelling: `pointIn(t, w)` holds iff `w.start ≤ t < w.end`. The IR
431
431
  * orders the operands interval-left, point-right; the value stores them
432
- * that way whatever the surface spelling.
432
+ * that way whatever the surface argument order (a literal `span(...)`
433
+ * interval operand is legal and tags by the point sibling's element
434
+ * domain — the bug-hunt fix, now also a type-level guarantee).
435
+ * Interval ⊇ interval is NOT this operator; that predicate is
436
+ * `allen(a, ALLEN.covers, b)` — the name `covers` belongs to the Allen
437
+ * roster alone (the canonical-utterance law: one meaning, one spelling).
433
438
  */
434
439
  function pointIn<const P extends PointSide, const I extends IntervalSide>(point: P, interval: I): Cmp<"pointIn", I, P> {
435
440
  assertTermSide("pointIn", point, interval)
436
441
  return comparison("pointIn", interval, point, undefined)
437
442
  }
438
443
 
439
- /**
440
- * Point membership, coverage spelling — `covers(w, t)` is `pointIn(t, w)`
441
- * with the interval written first (the IR's own operand order; a literal
442
- * `span(...)` left operand is legal and tags by the point sibling's
443
- * element domain — the bug-hunt fix, now also a type-level guarantee).
444
- * Interval ⊇ interval is NOT this operator; that predicate is
445
- * `allen(a, ALLEN.covers, b)`.
446
- */
447
- function covers<const I extends IntervalSide, const P extends PointSide>(interval: I, point: P): Cmp<"pointIn", I, P> {
448
- assertTermSide("covers", interval, point)
449
- return comparison("pointIn", interval, point, undefined)
450
- }
451
-
452
444
  /**
453
445
  * The 13-bit mask range: bits above the low 13 are unrepresentable in the
454
446
  * engine's `AllenMask` (`bumbledb/crates/bumbledb/src/allen.rs`:
@@ -619,7 +611,7 @@ type NotOk<Env extends EnvShape, F extends FieldsShape, CR, B> = false extends {
619
611
  * twin of the engine's comparison roster: class-equal joins (off the
620
612
  * schema type's class map), orderable order sides (an interval var under a
621
613
  * non-`pointIn` op is exactly here refused), kind-correct
622
- * `pointIn`/`covers`/`allen` sides, and negated-atom safety (the negated
614
+ * `pointIn`/`allen` sides, and negated-atom safety (the negated
623
615
  * relation's class record is resolved through `Classes` by its name). The
624
616
  * leading `[AnyTreeChild] extends [C]` arm is the recursion's base case:
625
617
  * at an UNRESOLVED constraint (the whole condition union — or a tree's
@@ -747,4 +739,4 @@ export type {
747
739
  Tree,
748
740
  TreeData
749
741
  }
750
- export { ALLEN, allen, and, comparison, covers, eq, ge, gt, le, lt, ne, not, or, pointIn }
742
+ export { ALLEN, ALLEN_ALL_BITS, allen, and, comparison, eq, ge, gt, le, lt, ne, not, or, pointIn }