@bjornpagen/bumbledb 0.1.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/LICENSE +12 -0
- package/README.md +84 -0
- package/dist/brand.d.ts +59 -0
- package/dist/brand.d.ts.map +1 -0
- package/dist/brand.js +47 -0
- package/dist/brand.js.map +1 -0
- package/dist/closed.d.ts +97 -0
- package/dist/closed.d.ts.map +1 -0
- package/dist/closed.js +107 -0
- package/dist/closed.js.map +1 -0
- package/dist/count.d.ts +55 -0
- package/dist/count.d.ts.map +1 -0
- package/dist/count.js +92 -0
- package/dist/count.js.map +1 -0
- package/dist/db.d.ts +341 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +1016 -0
- package/dist/db.js.map +1 -0
- package/dist/exhume.d.ts +130 -0
- package/dist/exhume.d.ts.map +1 -0
- package/dist/exhume.js +145 -0
- package/dist/exhume.js.map +1 -0
- package/dist/face.d.ts +91 -0
- package/dist/face.d.ts.map +1 -0
- package/dist/face.js +69 -0
- package/dist/face.js.map +1 -0
- package/dist/fields.d.ts +187 -0
- package/dist/fields.d.ts.map +1 -0
- package/dist/fields.js +204 -0
- package/dist/fields.js.map +1 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/lower.d.ts +34 -0
- package/dist/lower.d.ts.map +1 -0
- package/dist/lower.js +105 -0
- package/dist/lower.js.map +1 -0
- package/dist/marshal.d.ts +79 -0
- package/dist/marshal.d.ts.map +1 -0
- package/dist/marshal.js +167 -0
- package/dist/marshal.js.map +1 -0
- package/dist/native.d.ts +532 -0
- package/dist/native.d.ts.map +1 -0
- package/dist/native.js +64 -0
- package/dist/native.js.map +1 -0
- package/dist/query/atom.d.ts +285 -0
- package/dist/query/atom.d.ts.map +1 -0
- package/dist/query/atom.js +281 -0
- package/dist/query/atom.js.map +1 -0
- package/dist/query/lower.d.ts +145 -0
- package/dist/query/lower.d.ts.map +1 -0
- package/dist/query/lower.js +604 -0
- package/dist/query/lower.js.map +1 -0
- package/dist/query/predicate.d.ts +101 -0
- package/dist/query/predicate.d.ts.map +1 -0
- package/dist/query/predicate.js +85 -0
- package/dist/query/predicate.js.map +1 -0
- package/dist/query/run.d.ts +30 -0
- package/dist/query/run.d.ts.map +1 -0
- package/dist/query/run.js +94 -0
- package/dist/query/run.js.map +1 -0
- package/dist/query/scope.d.ts +168 -0
- package/dist/query/scope.d.ts.map +1 -0
- package/dist/query/scope.js +134 -0
- package/dist/query/scope.js.map +1 -0
- package/dist/query/select.d.ts +106 -0
- package/dist/query/select.d.ts.map +1 -0
- package/dist/query/select.js +69 -0
- package/dist/query/select.js.map +1 -0
- package/dist/relation.d.ts +120 -0
- package/dist/relation.d.ts.map +1 -0
- package/dist/relation.js +108 -0
- package/dist/relation.js.map +1 -0
- package/dist/schema.d.ts +50 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +235 -0
- package/dist/schema.js.map +1 -0
- package/dist/spec.d.ts +218 -0
- package/dist/spec.d.ts.map +1 -0
- package/dist/spec.js +154 -0
- package/dist/spec.js.map +1 -0
- package/dist/statements.d.ts +91 -0
- package/dist/statements.d.ts.map +1 -0
- package/dist/statements.js +101 -0
- package/dist/statements.js.map +1 -0
- package/package.json +66 -0
- package/src/brand.ts +82 -0
- package/src/closed.ts +228 -0
- package/src/count.ts +117 -0
- package/src/db.ts +1519 -0
- package/src/exhume.ts +243 -0
- package/src/face.ts +161 -0
- package/src/fields.ts +385 -0
- package/src/index.ts +185 -0
- package/src/lower.ts +118 -0
- package/src/marshal.ts +220 -0
- package/src/native.ts +576 -0
- package/src/query/atom.ts +556 -0
- package/src/query/lower.ts +855 -0
- package/src/query/predicate.ts +195 -0
- package/src/query/run.ts +106 -0
- package/src/query/scope.ts +301 -0
- package/src/query/select.ts +140 -0
- package/src/relation.ts +252 -0
- package/src/schema.ts +297 -0
- package/src/spec.ts +325 -0
- package/src/statements.ts +148 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Copyright © 2026 Bjorn Pagen <11238136+bjornpagen@users.noreply.github.com>
|
|
2
|
+
|
|
3
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
4
|
+
purpose with or without fee is hereby granted.
|
|
5
|
+
|
|
6
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
7
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
8
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
9
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
10
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
11
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
12
|
+
PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# @bjornpagen/bumbledb
|
|
2
|
+
|
|
3
|
+
Type-theoretic TypeScript SDK for the [bumbledb](https://github.com/bjornpagen/bumbledb) embedded relational engine.
|
|
4
|
+
|
|
5
|
+
bumbledb models data as relations judged by statements (functionality, containment, cardinality) and queried with Datalog expressed as plain values — no SQL, no query-string parser. The SDK is a thin, fully typed surface over an in-process native engine (LMDB storage, MVCC snapshots, a single-writer witnessed write loop).
|
|
6
|
+
|
|
7
|
+
> **Research-grade, one platform.** This is a `0.x` release of an embedded engine under active development. It targets a single platform today (below), the API is not yet frozen across `0.x`, and the FFI ABI is pinned exactly per version. Treat it as an early adopter's tool, not a production datastore.
|
|
8
|
+
|
|
9
|
+
## Platform support
|
|
10
|
+
|
|
11
|
+
This release targets **darwin-arm64 (macOS Apple Silicon) only**. The native binary ships as the optional platform package `@bjornpagen/bumbledb-darwin-arm64`, resolved automatically at install on a matching host. Installs on other platforms succeed (the main package is pure JS) but throw a typed, actionable error at first load naming the running platform and that only `darwin-arm64` ships today. More targets are pure addition — one more `os`/`cpu`-gated package plus a CI matrix — not a redesign.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
pnpm add @bjornpagen/bumbledb
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
Declare a schema, write facts through a transaction, and query with Datalog as
|
|
22
|
+
values. Everything is typed end to end — branded ids, inferred query rows, and
|
|
23
|
+
rejections that arrive as data rather than exceptions.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { Db, relation, schema, contained, on, query, match, u64, str, type Brand, type Scope } from "@bjornpagen/bumbledb"
|
|
27
|
+
|
|
28
|
+
// Branded, fresh-minted id types.
|
|
29
|
+
const HolderId = u64.newtype("HolderId")
|
|
30
|
+
const AccountId = u64.newtype("AccountId")
|
|
31
|
+
|
|
32
|
+
// Relations. `.fresh` marks an engine-minted primary key.
|
|
33
|
+
const Holder = relation("Holder", { id: HolderId.fresh, name: str })
|
|
34
|
+
const Account = relation("Account", { id: AccountId.fresh, holder: HolderId })
|
|
35
|
+
|
|
36
|
+
// A theory: every Account.holder must reference an existing Holder.id.
|
|
37
|
+
const Ledger = schema("Ledger", { Holder, Account }, [contained(on(Account, "holder"), on(Holder, "id"))])
|
|
38
|
+
|
|
39
|
+
const db = await Db.create("./ledger.db", Ledger)
|
|
40
|
+
|
|
41
|
+
// Write. The delta is judged against every statement at commit.
|
|
42
|
+
let adaId: Brand<bigint, "HolderId"> | undefined
|
|
43
|
+
const result = db.write((tx) => {
|
|
44
|
+
const ada = tx.insert(Holder, { name: "ada" }) // ada.id is a branded HolderId
|
|
45
|
+
adaId = ada.id
|
|
46
|
+
tx.insert(Account, { holder: ada.id })
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// Rejection-as-data: no throw — a rejected commit is a typed value carrying
|
|
50
|
+
// every violated statement, cited once, with its canonical spelling and facts.
|
|
51
|
+
if (!result.ok) {
|
|
52
|
+
for (const v of result.violations) {
|
|
53
|
+
console.error(v.kind, v.canonical, v.facts)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Query: Datalog as values. Rows are typed from the `select` shape.
|
|
58
|
+
const accountsOf = query(Ledger, ($: Scope<(typeof Ledger)["relations"]>) => {
|
|
59
|
+
const acct = $.var(Account.fields.id)
|
|
60
|
+
const holder = $.param("holder", Holder.fields.id)
|
|
61
|
+
return { rules: [[match(Account, { id: acct, holder })]], select: { acct } }
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const prepared = db.prepare(accountsOf)
|
|
65
|
+
const rows = db.execute(prepared, { holder: adaId }) // rows: { acct: AccountId }[]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Surface
|
|
69
|
+
|
|
70
|
+
- The type kernel — brands, fields, `relation()`, `closed()`.
|
|
71
|
+
- The statement algebra — `schema()`, `key`, `contained`, `mirrors`, `window`.
|
|
72
|
+
- The `Db` runtime — path-cached stores, transactions, typed violations, scoped snapshot reads, the witnessed write loop.
|
|
73
|
+
- The query surface — Datalog as values: scoped vars/params, atoms, negation, conditions, aggregates, engine recursion via predicates, `db.prepare`.
|
|
74
|
+
- The exhume surface — `Db.exhume`, the schema-independent read path: a store's self-described shapes and raw facts by name.
|
|
75
|
+
|
|
76
|
+
## Architecture
|
|
77
|
+
|
|
78
|
+
The SDK is a typed surface over the native engine; the model (relations,
|
|
79
|
+
statement-based judgment, Datalog evaluation, MVCC storage, the witnessed
|
|
80
|
+
write loop) is documented in the [bumbledb engine repository](https://github.com/bjornpagen/bumbledb).
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
0BSD
|
package/dist/brand.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nominal branding — the TS analog of the Rust macro's host newtypes
|
|
3
|
+
* (`docs/architecture/10-data-model.md`, the nominal-safety layer). A brand
|
|
4
|
+
* is a phantom: it exists only in the type, tsc polices the wall exactly as
|
|
5
|
+
* rustc polices newtype domains, and nothing is allocated or wrapped at
|
|
6
|
+
* runtime.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* The brand key. A real runtime symbol (so modules can import it without a
|
|
10
|
+
* `declare`-only lie), but no branded value ever carries the property — the
|
|
11
|
+
* brand is purely a typing device.
|
|
12
|
+
*/
|
|
13
|
+
declare const brand: unique symbol;
|
|
14
|
+
/**
|
|
15
|
+
* The phantom-value key used by field values, field references, and faces
|
|
16
|
+
* to carry their value type without any runtime representation.
|
|
17
|
+
*/
|
|
18
|
+
declare const phantom: unique symbol;
|
|
19
|
+
/**
|
|
20
|
+
* A branded scalar: `T` walled off under the literal name `Name`. A
|
|
21
|
+
* `Brand<bigint, "HolderId">` is not assignable where a
|
|
22
|
+
* `Brand<bigint, "AccountId">` is expected — the Rust newtype wall,
|
|
23
|
+
* verbatim. Scalars brand as `bigint` (u64/i64), `Uint8Array` (bytes), and
|
|
24
|
+
* whole interval objects (`Interval<Name>`); `bool` and `str` take no
|
|
25
|
+
* newtype, exactly as the macro's `as` grammar refuses them.
|
|
26
|
+
*/
|
|
27
|
+
type Brand<T, Name extends string> = T & {
|
|
28
|
+
readonly [brand]: Name;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* A half-open interval `[start, end)` as a plain value object. The ray is
|
|
32
|
+
* representable (`end` = the element domain's MAX_END); widths and
|
|
33
|
+
* signedness are NOT modeled here — the engine judges widths at the typed
|
|
34
|
+
* write boundary, the brand blocks cross-field assignment, and nothing
|
|
35
|
+
* else is TS's business. Interval newtypes derive no order (the Rust
|
|
36
|
+
* refusal, `docs/architecture/10-data-model.md`), so no comparators exist.
|
|
37
|
+
*/
|
|
38
|
+
interface IntervalValue {
|
|
39
|
+
readonly start: bigint;
|
|
40
|
+
readonly end: bigint;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A branded interval: the whole `{ start, end }` object walled under
|
|
44
|
+
* `Name` — the `interval<i64> as ActiveDuring` analog.
|
|
45
|
+
*/
|
|
46
|
+
type Interval<Name extends string> = Brand<IntervalValue, Name>;
|
|
47
|
+
/**
|
|
48
|
+
* Constructs an interval literal — the `start..end` spelling. Half-open
|
|
49
|
+
* and nonempty by construction: `start >= end` is a typed construction
|
|
50
|
+
* error (parse, don't validate — the same invariant Rust's
|
|
51
|
+
* `Interval::new` enforces at the host boundary). The default `never`
|
|
52
|
+
* brand makes a fresh literal assignable to any interval field or brand,
|
|
53
|
+
* the wrap-at-construction idiom; pass the brand explicitly
|
|
54
|
+
* (`span<"ActiveDuring">(0n, 10n)`) to pin it.
|
|
55
|
+
*/
|
|
56
|
+
declare function span<Name extends string = never>(start: bigint, end: bigint): Interval<Name>;
|
|
57
|
+
export type { Brand, Interval, IntervalValue };
|
|
58
|
+
export { brand, phantom, span };
|
|
59
|
+
//# sourceMappingURL=brand.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brand.d.ts","sourceRoot":"","sources":["../src/brand.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAeH;;;;GAIG;AACH,QAAA,MAAM,KAAK,EAAE,OAAO,MAAiC,CAAA;AAErD;;;GAGG;AACH,QAAA,MAAM,OAAO,EAAE,OAAO,MAAmC,CAAA;AAEzD;;;;;;;GAOG;AACH,KAAK,KAAK,CAAC,CAAC,EAAE,IAAI,SAAS,MAAM,IAAI,CAAC,GAAG;IAAE,QAAQ,CAAC,CAAC,KAAK,CAAC,EAAE,IAAI,CAAA;CAAE,CAAA;AAEnE;;;;;;;GAOG;AACH,UAAU,aAAa;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACpB;AAED;;;GAGG;AACH,KAAK,QAAQ,CAAC,IAAI,SAAS,MAAM,IAAI,KAAK,CAAC,aAAa,EAAE,IAAI,CAAC,CAAA;AAE/D;;;;;;;;GAQG;AACH,iBAAS,IAAI,CAAC,IAAI,SAAS,MAAM,GAAG,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAMrF;AAED,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAA;AAC9C,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA"}
|
package/dist/brand.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nominal branding — the TS analog of the Rust macro's host newtypes
|
|
3
|
+
* (`docs/architecture/10-data-model.md`, the nominal-safety layer). A brand
|
|
4
|
+
* is a phantom: it exists only in the type, tsc polices the wall exactly as
|
|
5
|
+
* rustc polices newtype domains, and nothing is allocated or wrapped at
|
|
6
|
+
* runtime.
|
|
7
|
+
*/
|
|
8
|
+
import * as errors from "@superbuilders/errors";
|
|
9
|
+
/**
|
|
10
|
+
* The brand-minting guard behind {@link span} — the one nominal step, as a
|
|
11
|
+
* type guard carrying the interval's REAL invariant (`start < end`, the
|
|
12
|
+
* same check Rust's `Interval::new` runs): a value that passes IS a legal
|
|
13
|
+
* interval of any brand, exactly as a Rust newtype wraps a checked
|
|
14
|
+
* `Interval<T>` at construction.
|
|
15
|
+
*/
|
|
16
|
+
function isNonemptyInterval(value) {
|
|
17
|
+
return value.start < value.end;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The brand key. A real runtime symbol (so modules can import it without a
|
|
21
|
+
* `declare`-only lie), but no branded value ever carries the property — the
|
|
22
|
+
* brand is purely a typing device.
|
|
23
|
+
*/
|
|
24
|
+
const brand = Symbol("bumbledb.brand");
|
|
25
|
+
/**
|
|
26
|
+
* The phantom-value key used by field values, field references, and faces
|
|
27
|
+
* to carry their value type without any runtime representation.
|
|
28
|
+
*/
|
|
29
|
+
const phantom = Symbol("bumbledb.phantom");
|
|
30
|
+
/**
|
|
31
|
+
* Constructs an interval literal — the `start..end` spelling. Half-open
|
|
32
|
+
* and nonempty by construction: `start >= end` is a typed construction
|
|
33
|
+
* error (parse, don't validate — the same invariant Rust's
|
|
34
|
+
* `Interval::new` enforces at the host boundary). The default `never`
|
|
35
|
+
* brand makes a fresh literal assignable to any interval field or brand,
|
|
36
|
+
* the wrap-at-construction idiom; pass the brand explicitly
|
|
37
|
+
* (`span<"ActiveDuring">(0n, 10n)`) to pin it.
|
|
38
|
+
*/
|
|
39
|
+
function span(start, end) {
|
|
40
|
+
const value = Object.freeze({ start, end });
|
|
41
|
+
if (!isNonemptyInterval(value)) {
|
|
42
|
+
throw errors.new(`interval is half-open and nonempty: start must be < end (got ${start}..${end})`);
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
export { brand, phantom, span };
|
|
47
|
+
//# sourceMappingURL=brand.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brand.js","sourceRoot":"","sources":["../src/brand.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAA;AAE/C;;;;;;GAMG;AACH,SAAS,kBAAkB,CAAsB,KAAoB;IACpE,OAAO,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,GAAG,CAAA;AAC/B,CAAC;AAED;;;;GAIG;AACH,MAAM,KAAK,GAAkB,MAAM,CAAC,gBAAgB,CAAC,CAAA;AAErD;;;GAGG;AACH,MAAM,OAAO,GAAkB,MAAM,CAAC,kBAAkB,CAAC,CAAA;AA+BzD;;;;;;;;GAQG;AACH,SAAS,IAAI,CAA8B,KAAa,EAAE,GAAW;IACpE,MAAM,KAAK,GAAkB,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;IAC1D,IAAI,CAAC,kBAAkB,CAAO,KAAK,CAAC,EAAE,CAAC;QACtC,MAAM,MAAM,CAAC,GAAG,CAAC,gEAAgE,KAAK,KAAK,GAAG,GAAG,CAAC,CAAA;IACnG,CAAC;IACD,OAAO,KAAK,CAAA;AACb,CAAC;AAGD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA"}
|
package/dist/closed.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Closed relations (`docs/architecture/10-data-model.md` § closed
|
|
3
|
+
* relations): a vocabulary whose extension is declared in the schema — two
|
|
4
|
+
* tiers, one function. The emission per closed relation mirrors the macro's
|
|
5
|
+
* (host-enum analog): handle CONSTANTS on the value (`Kind.Checking`, ids =
|
|
6
|
+
* declaration order), the `fromId` weld, an `id` field constructor
|
|
7
|
+
* pre-branded with the handle newtype for other relations' field blocks
|
|
8
|
+
* (`kind: Kind.id`), and payload readback (`Kind.axioms`). No fact type and
|
|
9
|
+
* no insert surface exist — closed relations are unwritable by
|
|
10
|
+
* construction: the value simply lacks the writable relation shape.
|
|
11
|
+
*/
|
|
12
|
+
import type { Brand } from "#brand.ts";
|
|
13
|
+
import { type AnyField, type ClosedIdField, type FieldData, type FieldValue } from "#fields.ts";
|
|
14
|
+
import type { LiteralSpec } from "#spec.ts";
|
|
15
|
+
/**
|
|
16
|
+
* A payload column of a closed relation: any field constructor except a
|
|
17
|
+
* fresh-marked one (a vocabulary's rows are ground axioms, never minted).
|
|
18
|
+
*/
|
|
19
|
+
type PayloadField = AnyField & {
|
|
20
|
+
readonly data: FieldData<false>;
|
|
21
|
+
};
|
|
22
|
+
/** One declared payload column: name plus its field description. */
|
|
23
|
+
interface ClosedColumn {
|
|
24
|
+
readonly name: string;
|
|
25
|
+
readonly field: FieldData;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* One ground axiom, already lowered: the handle plus one wire literal per
|
|
29
|
+
* declared column in column-declaration order (row id = index). Lowered
|
|
30
|
+
* EAGERLY at construction so axiom literals ride the same selection-literal
|
|
31
|
+
* machine as `where()` bindings, with the same errors (the macro's rule).
|
|
32
|
+
*/
|
|
33
|
+
interface ClosedRow {
|
|
34
|
+
readonly handle: string;
|
|
35
|
+
readonly values: readonly LiteralSpec[];
|
|
36
|
+
}
|
|
37
|
+
/** A closed relation's runtime description. */
|
|
38
|
+
interface ClosedData {
|
|
39
|
+
readonly name: string;
|
|
40
|
+
readonly handles: readonly string[];
|
|
41
|
+
readonly columns: readonly ClosedColumn[];
|
|
42
|
+
readonly rows: readonly ClosedRow[];
|
|
43
|
+
}
|
|
44
|
+
/** One axiom row as the host writes and reads it: column name to host value. */
|
|
45
|
+
type AxiomRow<Cols> = {
|
|
46
|
+
readonly [C in keyof Cols]: FieldValue<Cols[C]>;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* The whole axiom record: every handle exactly once, every column exactly
|
|
50
|
+
* once per row — a missing or extra axiom, column, or handle is a TYPE
|
|
51
|
+
* error (mapped over the handle tuple).
|
|
52
|
+
*/
|
|
53
|
+
type Axioms<Handles extends readonly string[], Cols> = {
|
|
54
|
+
readonly [H in Handles[number]]: AxiomRow<Cols>;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* The named surface of a closed relation value, minus the handle constants
|
|
58
|
+
* (which {@link Closed} intersects in).
|
|
59
|
+
*/
|
|
60
|
+
interface ClosedCore<Name extends string, Handles extends readonly string[], Cols> {
|
|
61
|
+
readonly name: Name;
|
|
62
|
+
/**
|
|
63
|
+
* The pre-branded u64 field constructor: `kind: Kind.id` in another
|
|
64
|
+
* relation's field block is the reference through which bare handles
|
|
65
|
+
* become legal in that relation's selections.
|
|
66
|
+
*/
|
|
67
|
+
readonly id: ClosedIdField<Name>;
|
|
68
|
+
readonly data: ClosedData;
|
|
69
|
+
/** Payload readback: handle to its declared column values. */
|
|
70
|
+
readonly axioms: Axioms<Handles, Cols>;
|
|
71
|
+
/** The weld: declaration-order id back to its handle, or undefined beyond the roster. */
|
|
72
|
+
fromId(id: Brand<bigint, Name>): Handles[number] | undefined;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* A closed relation value: the core surface plus one branded constant per
|
|
76
|
+
* handle (`Kind.Checking: Brand<bigint, "Kind">`, ids = declaration order).
|
|
77
|
+
*/
|
|
78
|
+
type Closed<Name extends string, Handles extends readonly string[], Cols> = ClosedCore<Name, Handles, Cols> & {
|
|
79
|
+
readonly [H in Handles[number]]: Brand<bigint, Name>;
|
|
80
|
+
};
|
|
81
|
+
/** Any closed relation value, whatever its roster and columns. */
|
|
82
|
+
interface AnyClosed {
|
|
83
|
+
readonly name: string;
|
|
84
|
+
readonly id: ClosedIdField<string>;
|
|
85
|
+
readonly data: ClosedData;
|
|
86
|
+
readonly axioms: Readonly<Record<string, object>>;
|
|
87
|
+
}
|
|
88
|
+
/** Bare tier: `closed("Kind", ["Checking", "Savings"])` — handles only. */
|
|
89
|
+
declare function closed<const Name extends string, const Handles extends readonly [string, ...string[]]>(name: Name, handles: Handles): Closed<Name, Handles, Record<never, never>>;
|
|
90
|
+
/**
|
|
91
|
+
* Payload tier: declared columns plus ground axioms, every handle with
|
|
92
|
+
* every column exactly once (type-enforced by {@link Axioms}).
|
|
93
|
+
*/
|
|
94
|
+
declare function closed<const Name extends string, const Handles extends readonly [string, ...string[]], const Cols extends Record<string, PayloadField>>(name: Name, handles: Handles, columns: Cols, axioms: Axioms<Handles, Cols>): Closed<Name, Handles, Cols>;
|
|
95
|
+
export type { AnyClosed, AxiomRow, Axioms, Closed, ClosedColumn, ClosedCore, ClosedData, ClosedRow, PayloadField };
|
|
96
|
+
export { closed };
|
|
97
|
+
//# sourceMappingURL=closed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"closed.d.ts","sourceRoot":"","sources":["../src/closed.ts"],"names":[],"mappings":"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"}
|
package/dist/closed.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Closed relations (`docs/architecture/10-data-model.md` § closed
|
|
3
|
+
* relations): a vocabulary whose extension is declared in the schema — two
|
|
4
|
+
* tiers, one function. The emission per closed relation mirrors the macro's
|
|
5
|
+
* (host-enum analog): handle CONSTANTS on the value (`Kind.Checking`, ids =
|
|
6
|
+
* declaration order), the `fromId` weld, an `id` field constructor
|
|
7
|
+
* pre-branded with the handle newtype for other relations' field blocks
|
|
8
|
+
* (`kind: Kind.id`), and payload readback (`Kind.axioms`). No fact type and
|
|
9
|
+
* no insert surface exist — closed relations are unwritable by
|
|
10
|
+
* construction: the value simply lacks the writable relation shape.
|
|
11
|
+
*/
|
|
12
|
+
import * as errors from "@superbuilders/errors";
|
|
13
|
+
import { assertDeclarationOrderKey, fieldData, literalOf } from "#fields.ts";
|
|
14
|
+
/**
|
|
15
|
+
* The value-surface property names a handle may not shadow — the macro's
|
|
16
|
+
* name-collision diagnostic, here over the closed value's own properties
|
|
17
|
+
* (`relation`/`selection` are reserved so a closed value can never be
|
|
18
|
+
* mistaken for a selected relation by `on()`'s discriminant).
|
|
19
|
+
*/
|
|
20
|
+
const reservedHandleNames = Object.freeze([
|
|
21
|
+
"name",
|
|
22
|
+
"id",
|
|
23
|
+
"data",
|
|
24
|
+
"axioms",
|
|
25
|
+
"fromId",
|
|
26
|
+
"relation",
|
|
27
|
+
"selection"
|
|
28
|
+
]);
|
|
29
|
+
/**
|
|
30
|
+
* Reads one handle's axiom row, refusing absence loudly: the payload tier's
|
|
31
|
+
* overload types the record exhaustively, so a missing row is ill-typed
|
|
32
|
+
* input, and the bare tier never reaches here (it declares no columns).
|
|
33
|
+
*/
|
|
34
|
+
function axiomRow(name, axioms, handle) {
|
|
35
|
+
if (axioms === undefined) {
|
|
36
|
+
throw errors.new(`closed relation ${name}: payload columns declared without ground axioms`);
|
|
37
|
+
}
|
|
38
|
+
const row = axioms[handle];
|
|
39
|
+
if (row === undefined) {
|
|
40
|
+
throw errors.new(`closed relation ${name}: no ground axiom for handle ${handle}`);
|
|
41
|
+
}
|
|
42
|
+
return row;
|
|
43
|
+
}
|
|
44
|
+
function closed(name, handles, columns, axioms) {
|
|
45
|
+
const seen = new Set();
|
|
46
|
+
for (const handle of handles) {
|
|
47
|
+
if (seen.has(handle)) {
|
|
48
|
+
throw errors.new(`closed relation ${name}: duplicate handle ${handle}`);
|
|
49
|
+
}
|
|
50
|
+
seen.add(handle);
|
|
51
|
+
if (reservedHandleNames.includes(handle)) {
|
|
52
|
+
throw errors.new(`closed relation ${name}: handle ${handle} collides with the closed value's own surface (${reservedHandleNames.join(", ")})`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const roster = Object.freeze({ name, handles: Object.freeze([...handles]) });
|
|
56
|
+
const cols = [];
|
|
57
|
+
if (columns !== undefined) {
|
|
58
|
+
for (const [columnName, field] of Object.entries(columns)) {
|
|
59
|
+
assertDeclarationOrderKey(`closed relation ${name} column`, columnName);
|
|
60
|
+
cols.push(Object.freeze({ name: columnName, field: field.data }));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const rows = handles.map(function lowerRow(handle) {
|
|
64
|
+
const values = cols.map(function lowerAxiomLiteral(column) {
|
|
65
|
+
const row = axiomRow(name, axioms, handle);
|
|
66
|
+
return Object.freeze(literalOf(column.field, row[column.name]));
|
|
67
|
+
});
|
|
68
|
+
return Object.freeze({ handle, values: Object.freeze(values) });
|
|
69
|
+
});
|
|
70
|
+
const data = Object.freeze({
|
|
71
|
+
name,
|
|
72
|
+
handles: roster.handles,
|
|
73
|
+
columns: Object.freeze(cols),
|
|
74
|
+
rows: Object.freeze(rows)
|
|
75
|
+
});
|
|
76
|
+
const id = Object.freeze({
|
|
77
|
+
data: fieldData({ kind: "u64" }, name, false, roster)
|
|
78
|
+
});
|
|
79
|
+
/**
|
|
80
|
+
* Handle names are arbitrary identifiers, so rows and constants are
|
|
81
|
+
* minted with OWN-property definition, never assignment: a handle named
|
|
82
|
+
* "__proto__" would otherwise ride the Object.prototype accessor —
|
|
83
|
+
* silently swapping the record's prototype instead of creating the row,
|
|
84
|
+
* and no-oping the constant (a primitive through the setter) — minting a
|
|
85
|
+
* value whose type claims Brand<bigint, Name> but reads back an object.
|
|
86
|
+
*/
|
|
87
|
+
const axiomsOut = {};
|
|
88
|
+
for (const handle of handles) {
|
|
89
|
+
const row = axioms === undefined ? Object.freeze({}) : Object.freeze({ ...axiomRow(name, axioms, handle) });
|
|
90
|
+
Object.defineProperty(axiomsOut, handle, { value: row, enumerable: true });
|
|
91
|
+
}
|
|
92
|
+
const value = {
|
|
93
|
+
name,
|
|
94
|
+
id,
|
|
95
|
+
data,
|
|
96
|
+
axioms: Object.freeze(axiomsOut),
|
|
97
|
+
fromId(idValue) {
|
|
98
|
+
return roster.handles[Number(idValue)];
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
handles.forEach(function mintHandleConstant(handle, index) {
|
|
102
|
+
Object.defineProperty(value, handle, { value: BigInt(index), enumerable: true });
|
|
103
|
+
});
|
|
104
|
+
return Object.freeze(value);
|
|
105
|
+
}
|
|
106
|
+
export { closed };
|
|
107
|
+
//# sourceMappingURL=closed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"closed.js","sourceRoot":"","sources":["../src/closed.ts"],"names":[],"mappings":"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"}
|
package/dist/count.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cardinality-window counts — exactly five constructors, and nothing else
|
|
3
|
+
* (`docs/architecture/70-api.md` § the canonical-utterance law). The ban
|
|
4
|
+
* table is enforced REPRESENTATIONALLY, stronger than Rust's expansion
|
|
5
|
+
* errors: `{1..*}`, `{n..n}`, `{0..0}`, `{0..*}`, and inverted windows have
|
|
6
|
+
* NO constructor — the spellings that could produce them are construction
|
|
7
|
+
* errors naming the canonical form, and no other spelling exists at all.
|
|
8
|
+
* Bounds are `bigint` (u64 crosses as bigint always, PRD-04's law).
|
|
9
|
+
*/
|
|
10
|
+
import type { WindowSpec } from "#spec.ts";
|
|
11
|
+
/**
|
|
12
|
+
* The admission brand — a module-private symbol, deliberately unexported:
|
|
13
|
+
* `WindowSpec` is a public wire type, so without this brand every banned
|
|
14
|
+
* spelling in the ban table would be writable as a plain object literal
|
|
15
|
+
* (`{ window: { kind: "floor", lo: 1n } }` typechecks structurally). The
|
|
16
|
+
* symbol makes the five constructors the ONLY producers of a `Count`
|
|
17
|
+
* value, which is what "the ban table is unwritable" means.
|
|
18
|
+
*/
|
|
19
|
+
declare const admitted: unique symbol;
|
|
20
|
+
/**
|
|
21
|
+
* An admitted window count — opaque and inert: a fact about the theory,
|
|
22
|
+
* not a builder. Only the five constructors below produce one (the
|
|
23
|
+
* module-private {@link admitted} brand forecloses structural literals).
|
|
24
|
+
*/
|
|
25
|
+
interface Count {
|
|
26
|
+
readonly window: WindowSpec;
|
|
27
|
+
readonly [admitted]: true;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* `{n}` — THE exact-count spelling, n ≥ 1. `exactly(0)` is the exclusion
|
|
31
|
+
* respelled and rejected naming `none`.
|
|
32
|
+
*/
|
|
33
|
+
declare function exactly(n: bigint): Count;
|
|
34
|
+
/** `{0}` — the exclusion: no source fact may pair with the target group. */
|
|
35
|
+
declare const none: Count;
|
|
36
|
+
/**
|
|
37
|
+
* `{lo..hi}` — both bounds explicit, 0 ≤ lo < hi. `lo === hi` is the exact
|
|
38
|
+
* count respelled (rejected naming `exactly(n)`, or `none` at 0); an
|
|
39
|
+
* inverted window is unsatisfiable and rejected.
|
|
40
|
+
*/
|
|
41
|
+
declare function between(lo: bigint, hi: bigint): Count;
|
|
42
|
+
/**
|
|
43
|
+
* `{lo..*}` — a floor with no ceiling, lo ≥ 2: `atLeast(1)` says only what
|
|
44
|
+
* the bare containment says (rejected naming `contained`), and
|
|
45
|
+
* `atLeast(0)` is vacuous (rejected naming deletion).
|
|
46
|
+
*/
|
|
47
|
+
declare function atLeast(lo: bigint): Count;
|
|
48
|
+
/**
|
|
49
|
+
* `{0..hi}` — a ceiling, hi ≥ 1: `atMost(0)` is the exclusion respelled
|
|
50
|
+
* and rejected naming `none`.
|
|
51
|
+
*/
|
|
52
|
+
declare function atMost(hi: bigint): Count;
|
|
53
|
+
export type { Count };
|
|
54
|
+
export { atLeast, atMost, between, exactly, none };
|
|
55
|
+
//# sourceMappingURL=count.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"count.d.ts","sourceRoot":"","sources":["../src/count.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAK1C;;;;;;;GAOG;AACH,QAAA,MAAM,QAAQ,EAAE,OAAO,MAA0C,CAAA;AAEjE;;;;GAIG;AACH,UAAU,KAAK;IACd,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;IAC3B,QAAQ,CAAC,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAA;CACzB;AAOD;;;GAGG;AACH,iBAAS,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,KAAK,CAQjC;AAED,4EAA4E;AAC5E,QAAA,MAAM,IAAI,EAAE,KAAwB,CAAA;AAEpC;;;;GAIG;AACH,iBAAS,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,KAAK,CAgB9C;AAED;;;;GAIG;AACH,iBAAS,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,KAAK,CAalC;AAED;;;GAGG;AACH,iBAAS,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,KAAK,CAQjC;AAED,YAAY,EAAE,KAAK,EAAE,CAAA;AACrB,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA"}
|
package/dist/count.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cardinality-window counts — exactly five constructors, and nothing else
|
|
3
|
+
* (`docs/architecture/70-api.md` § the canonical-utterance law). The ban
|
|
4
|
+
* table is enforced REPRESENTATIONALLY, stronger than Rust's expansion
|
|
5
|
+
* errors: `{1..*}`, `{n..n}`, `{0..0}`, `{0..*}`, and inverted windows have
|
|
6
|
+
* NO constructor — the spellings that could produce them are construction
|
|
7
|
+
* errors naming the canonical form, and no other spelling exists at all.
|
|
8
|
+
* Bounds are `bigint` (u64 crosses as bigint always, PRD-04's law).
|
|
9
|
+
*/
|
|
10
|
+
import * as errors from "@superbuilders/errors";
|
|
11
|
+
/** The exclusion's one spelling, shared by `none`. */
|
|
12
|
+
const exclusion = Object.freeze({ kind: "exact", n: 0n });
|
|
13
|
+
/**
|
|
14
|
+
* The admission brand — a module-private symbol, deliberately unexported:
|
|
15
|
+
* `WindowSpec` is a public wire type, so without this brand every banned
|
|
16
|
+
* spelling in the ban table would be writable as a plain object literal
|
|
17
|
+
* (`{ window: { kind: "floor", lo: 1n } }` typechecks structurally). The
|
|
18
|
+
* symbol makes the five constructors the ONLY producers of a `Count`
|
|
19
|
+
* value, which is what "the ban table is unwritable" means.
|
|
20
|
+
*/
|
|
21
|
+
const admitted = Symbol("bumbledb.count.admitted");
|
|
22
|
+
/** Stamps one admitted window as a frozen `Count` value. */
|
|
23
|
+
function admit(window) {
|
|
24
|
+
return Object.freeze({ window, [admitted]: true });
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* `{n}` — THE exact-count spelling, n ≥ 1. `exactly(0)` is the exclusion
|
|
28
|
+
* respelled and rejected naming `none`.
|
|
29
|
+
*/
|
|
30
|
+
function exactly(n) {
|
|
31
|
+
if (n < 0n) {
|
|
32
|
+
throw errors.new(`window counts are u64: exactly(${n}) is out of domain`);
|
|
33
|
+
}
|
|
34
|
+
if (n === 0n) {
|
|
35
|
+
throw errors.new("`{0..0}`-shaped spelling: the exclusion is written `{0}` — use none");
|
|
36
|
+
}
|
|
37
|
+
return admit(Object.freeze({ kind: "exact", n }));
|
|
38
|
+
}
|
|
39
|
+
/** `{0}` — the exclusion: no source fact may pair with the target group. */
|
|
40
|
+
const none = admit(exclusion);
|
|
41
|
+
/**
|
|
42
|
+
* `{lo..hi}` — both bounds explicit, 0 ≤ lo < hi. `lo === hi` is the exact
|
|
43
|
+
* count respelled (rejected naming `exactly(n)`, or `none` at 0); an
|
|
44
|
+
* inverted window is unsatisfiable and rejected.
|
|
45
|
+
*/
|
|
46
|
+
function between(lo, hi) {
|
|
47
|
+
if (lo < 0n || hi < 0n) {
|
|
48
|
+
throw errors.new(`window counts are u64: between(${lo}, ${hi}) is out of domain`);
|
|
49
|
+
}
|
|
50
|
+
if (hi < lo) {
|
|
51
|
+
throw errors.new(`the window \`{${lo}..${hi}}\` is inverted — no count satisfies it; bounds are \`{lo..hi}\` with lo < hi (an exact count is \`{n}\`: exactly(n))`);
|
|
52
|
+
}
|
|
53
|
+
if (lo === hi) {
|
|
54
|
+
if (lo === 0n) {
|
|
55
|
+
throw errors.new("`{0..0}` — the exclusion is written `{0}`: use none");
|
|
56
|
+
}
|
|
57
|
+
throw errors.new(`\`{${lo}..${lo}}\` — an exact count is written \`{${lo}}\`: use exactly(${lo})`);
|
|
58
|
+
}
|
|
59
|
+
return admit(Object.freeze({ kind: "range", lo, hi }));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* `{lo..*}` — a floor with no ceiling, lo ≥ 2: `atLeast(1)` says only what
|
|
63
|
+
* the bare containment says (rejected naming `contained`), and
|
|
64
|
+
* `atLeast(0)` is vacuous (rejected naming deletion).
|
|
65
|
+
*/
|
|
66
|
+
function atLeast(lo) {
|
|
67
|
+
if (lo < 0n) {
|
|
68
|
+
throw errors.new(`window counts are u64: atLeast(${lo}) is out of domain`);
|
|
69
|
+
}
|
|
70
|
+
if (lo === 0n) {
|
|
71
|
+
throw errors.new("the `{0..*}` window is vacuous — it provably says nothing; delete the statement");
|
|
72
|
+
}
|
|
73
|
+
if (lo === 1n) {
|
|
74
|
+
throw errors.new("`{1..*}` says only what the bare containment says — drop the annotation and write the containment: contained(source, target)");
|
|
75
|
+
}
|
|
76
|
+
return admit(Object.freeze({ kind: "floor", lo }));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* `{0..hi}` — a ceiling, hi ≥ 1: `atMost(0)` is the exclusion respelled
|
|
80
|
+
* and rejected naming `none`.
|
|
81
|
+
*/
|
|
82
|
+
function atMost(hi) {
|
|
83
|
+
if (hi < 0n) {
|
|
84
|
+
throw errors.new(`window counts are u64: atMost(${hi}) is out of domain`);
|
|
85
|
+
}
|
|
86
|
+
if (hi === 0n) {
|
|
87
|
+
throw errors.new("`{0..0}` — the exclusion is written `{0}`: use none");
|
|
88
|
+
}
|
|
89
|
+
return admit(Object.freeze({ kind: "range", lo: 0n, hi }));
|
|
90
|
+
}
|
|
91
|
+
export { atLeast, atMost, between, exactly, none };
|
|
92
|
+
//# sourceMappingURL=count.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"count.js","sourceRoot":"","sources":["../src/count.ts"],"names":[],"mappings":"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"}
|