@libredb/libredb 0.0.1
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 +21 -0
- package/README.md +259 -0
- package/dist/adapter/store.d.ts +32 -0
- package/dist/adapter/store.d.ts.map +1 -0
- package/dist/adapter/store.js +2 -0
- package/dist/adapter/store.js.map +1 -0
- package/dist/core.d.ts +111 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +343 -0
- package/dist/core.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/lens/document.d.ts +104 -0
- package/dist/lens/document.d.ts.map +1 -0
- package/dist/lens/document.js +152 -0
- package/dist/lens/document.js.map +1 -0
- package/dist/lens/kv.d.ts +59 -0
- package/dist/lens/kv.d.ts.map +1 -0
- package/dist/lens/kv.js +87 -0
- package/dist/lens/kv.js.map +1 -0
- package/dist/lens/relational.d.ts +119 -0
- package/dist/lens/relational.d.ts.map +1 -0
- package/dist/lens/relational.js +213 -0
- package/dist/lens/relational.js.map +1 -0
- package/dist/lens/types.d.ts +58 -0
- package/dist/lens/types.d.ts.map +1 -0
- package/dist/lens/types.js +35 -0
- package/dist/lens/types.js.map +1 -0
- package/dist/query/range.d.ts +44 -0
- package/dist/query/range.d.ts.map +1 -0
- package/dist/query/range.js +60 -0
- package/dist/query/range.js.map +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.js","sourceRoot":"","sources":["../../src/lens/document.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lens/kv.ts — the key-value lens, LibreDB's proof lens.
|
|
3
|
+
*
|
|
4
|
+
* The kernel (core.ts) is already an ordered byte key-value store, so this lens
|
|
5
|
+
* is the thinnest one possible: it does not add storage, transactions, or
|
|
6
|
+
* recovery — it only puts an ergonomic face on what the kernel already does.
|
|
7
|
+
* That makes it the honest proof of the architecture (DESIGN.md section 6):
|
|
8
|
+
* "here is the ordered KV core, usable directly."
|
|
9
|
+
*
|
|
10
|
+
* What the lens adds, and nothing more:
|
|
11
|
+
* - STRING ergonomics. The kernel speaks raw bytes on purpose; this lens
|
|
12
|
+
* encodes string keys/values as UTF-8 on the way in and decodes on the way
|
|
13
|
+
* out, so it reads like the `Map<string, string>` it means to replace.
|
|
14
|
+
* - The SHARED ENVELOPE. Reads return a {@link Result}, writes return a
|
|
15
|
+
* {@link WriteResult} (lens/types.ts), the shapes every later lens reuses.
|
|
16
|
+
*
|
|
17
|
+
* Each operation runs in its own transaction (auto-commit), so the lens behaves
|
|
18
|
+
* like a durable map: a write is atomic and, on a file-backed database, fsync'd
|
|
19
|
+
* before it returns. Multi-key atomicity is a conscious omission for the proof
|
|
20
|
+
* lens — a caller that needs it reaches the kernel's `transact` directly. The
|
|
21
|
+
* lens is a view: it depends only on the {@link Store} seam (`transact`), never
|
|
22
|
+
* on the store's lifecycle; whoever opened the store closes it.
|
|
23
|
+
*/
|
|
24
|
+
import { type Result, type WriteResult } from "./types.ts";
|
|
25
|
+
import type { Store } from "../adapter/store.ts";
|
|
26
|
+
/** One key/value pair from a range scan, decoded to strings. The kv-lens
|
|
27
|
+
* counterpart of the kernel's byte-level {@link import("../core.ts").Entry}. */
|
|
28
|
+
export interface KvEntry {
|
|
29
|
+
readonly key: string;
|
|
30
|
+
readonly value: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The key-value lens surface: a durable, ordered, string-keyed map.
|
|
34
|
+
*
|
|
35
|
+
* Keys are ordered by the UTF-8 byte order the kernel sorts on, so `range`
|
|
36
|
+
* yields entries in ascending key order over the half-open interval
|
|
37
|
+
* `[start, end)` — `start` included, `end` excluded.
|
|
38
|
+
*/
|
|
39
|
+
export interface Kv {
|
|
40
|
+
/** The value stored for `key`, or `undefined` if it is not set. */
|
|
41
|
+
get(key: string): string | undefined;
|
|
42
|
+
/** Store `value` under `key`, overwriting any existing value. Always reports
|
|
43
|
+
* one changed entry. */
|
|
44
|
+
set(key: string, value: string): WriteResult;
|
|
45
|
+
/** Remove `key`. Reports one changed entry if it existed, zero if it did not. */
|
|
46
|
+
delete(key: string): WriteResult;
|
|
47
|
+
/** Scan `[start, end)` in ascending key order. The {@link Result} is lazy and
|
|
48
|
+
* re-iterable: each pass re-runs the scan against the current state. */
|
|
49
|
+
range(start: string, end: string): Result<KvEntry>;
|
|
50
|
+
/** Scan every key beginning with `prefix`, in ascending key order — the
|
|
51
|
+
* canonical ordered-KV query. Throws if `prefix` is empty (it has no finite
|
|
52
|
+
* upper bound; use {@link range} to scan an explicit interval). The
|
|
53
|
+
* {@link Result} is lazy and re-iterable, like {@link range}. */
|
|
54
|
+
prefix(prefix: string): Result<KvEntry>;
|
|
55
|
+
}
|
|
56
|
+
/** Build a {@link Kv} lens over a {@link Store} (the kernel's `Database`
|
|
57
|
+
* satisfies it, as does any object that can run a transaction). */
|
|
58
|
+
export declare function kv(store: Store): Kv;
|
|
59
|
+
//# sourceMappingURL=kv.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kv.d.ts","sourceRoot":"","sources":["../../src/lens/kv.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAU,KAAK,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAEnE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEjD;gFACgF;AAChF,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,EAAE;IACjB,mEAAmE;IACnE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACrC;4BACwB;IACxB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CAAC;IAC7C,iFAAiF;IACjF,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAAC;IACjC;4EACwE;IACxE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACnD;;;qEAGiE;IACjE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;CACzC;AAOD;mEACmE;AACnE,wBAAgB,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,EAAE,CAmCnC"}
|
package/dist/lens/kv.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lens/kv.ts — the key-value lens, LibreDB's proof lens.
|
|
3
|
+
*
|
|
4
|
+
* The kernel (core.ts) is already an ordered byte key-value store, so this lens
|
|
5
|
+
* is the thinnest one possible: it does not add storage, transactions, or
|
|
6
|
+
* recovery — it only puts an ergonomic face on what the kernel already does.
|
|
7
|
+
* That makes it the honest proof of the architecture (DESIGN.md section 6):
|
|
8
|
+
* "here is the ordered KV core, usable directly."
|
|
9
|
+
*
|
|
10
|
+
* What the lens adds, and nothing more:
|
|
11
|
+
* - STRING ergonomics. The kernel speaks raw bytes on purpose; this lens
|
|
12
|
+
* encodes string keys/values as UTF-8 on the way in and decodes on the way
|
|
13
|
+
* out, so it reads like the `Map<string, string>` it means to replace.
|
|
14
|
+
* - The SHARED ENVELOPE. Reads return a {@link Result}, writes return a
|
|
15
|
+
* {@link WriteResult} (lens/types.ts), the shapes every later lens reuses.
|
|
16
|
+
*
|
|
17
|
+
* Each operation runs in its own transaction (auto-commit), so the lens behaves
|
|
18
|
+
* like a durable map: a write is atomic and, on a file-backed database, fsync'd
|
|
19
|
+
* before it returns. Multi-key atomicity is a conscious omission for the proof
|
|
20
|
+
* lens — a caller that needs it reaches the kernel's `transact` directly. The
|
|
21
|
+
* lens is a view: it depends only on the {@link Store} seam (`transact`), never
|
|
22
|
+
* on the store's lifecycle; whoever opened the store closes it.
|
|
23
|
+
*/
|
|
24
|
+
import { result } from "./types.js";
|
|
25
|
+
import { prefixRange } from "../query/range.js";
|
|
26
|
+
const utf8 = new TextEncoder();
|
|
27
|
+
const fromUtf8 = new TextDecoder();
|
|
28
|
+
const encode = (s) => utf8.encode(s);
|
|
29
|
+
const decode = (b) => fromUtf8.decode(b);
|
|
30
|
+
/** Build a {@link Kv} lens over a {@link Store} (the kernel's `Database`
|
|
31
|
+
* satisfies it, as does any object that can run a transaction). */
|
|
32
|
+
export function kv(store) {
|
|
33
|
+
return {
|
|
34
|
+
get(key) {
|
|
35
|
+
return store.transact((tx) => {
|
|
36
|
+
const value = tx.get(encode(key));
|
|
37
|
+
return value === undefined ? undefined : decode(value);
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
set(key, value) {
|
|
41
|
+
store.transact((tx) => {
|
|
42
|
+
tx.set(encode(key), encode(value));
|
|
43
|
+
});
|
|
44
|
+
return { changed: 1 };
|
|
45
|
+
},
|
|
46
|
+
delete(key) {
|
|
47
|
+
const changed = store.transact((tx) => {
|
|
48
|
+
const k = encode(key);
|
|
49
|
+
const existed = tx.get(k) !== undefined;
|
|
50
|
+
tx.delete(k);
|
|
51
|
+
return existed ? 1 : 0;
|
|
52
|
+
});
|
|
53
|
+
return { changed };
|
|
54
|
+
},
|
|
55
|
+
range(start, end) {
|
|
56
|
+
return scan(store, encode(start), encode(end));
|
|
57
|
+
},
|
|
58
|
+
prefix(p) {
|
|
59
|
+
// prefixRange computes the [start, end) bounds on bytes so they agree with
|
|
60
|
+
// the kernel's order, and rejects a prefix with no finite end (an empty
|
|
61
|
+
// string). It runs at call time, so a bad prefix fails here, not on a
|
|
62
|
+
// later iteration; the scan itself stays lazy.
|
|
63
|
+
const { start, end } = prefixRange(encode(p));
|
|
64
|
+
return scan(store, start, end);
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* A lazy, re-iterable {@link Result} over the kernel's half-open `[start, end)`
|
|
70
|
+
* byte scan, decoded to string {@link KvEntry} rows. Shared by `range` and
|
|
71
|
+
* `prefix` so the decode-and-materialize step has one definition.
|
|
72
|
+
*
|
|
73
|
+
* The thunk runs on each iteration (laziness + re-iterability come from
|
|
74
|
+
* result()), so every pass observes the current committed state. Rows are
|
|
75
|
+
* materialized inside the read transaction because the kernel's range is only
|
|
76
|
+
* valid within the transaction body; nothing runs until the Result is iterated.
|
|
77
|
+
*/
|
|
78
|
+
function scan(store, start, end) {
|
|
79
|
+
return result(() => store.transact((tx) => {
|
|
80
|
+
const rows = [];
|
|
81
|
+
for (const entry of tx.getRange(start, end)) {
|
|
82
|
+
rows.push({ key: decode(entry.key), value: decode(entry.value) });
|
|
83
|
+
}
|
|
84
|
+
return rows;
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=kv.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kv.js","sourceRoot":"","sources":["../../src/lens/kv.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { type Result, type WriteResult } from "./types.ts";
|
|
2
|
+
import type { Store } from "../adapter/store.ts";
|
|
3
|
+
/**
|
|
4
|
+
* A column's declared type. These are the JSON shapes a relational column can
|
|
5
|
+
* hold (DESIGN.md section 6.2): the three scalars plus a nested object. Arrays
|
|
6
|
+
* and `null` are deliberately not column types in v1 — `"object"` means a plain
|
|
7
|
+
* JSON object, and nullable/optional columns are an honest deferral.
|
|
8
|
+
*/
|
|
9
|
+
export type ColumnType = "string" | "number" | "boolean" | "object";
|
|
10
|
+
/**
|
|
11
|
+
* A table schema: the primary-key column name and the declared columns with
|
|
12
|
+
* their types. `primaryKey` must name a declared column whose type is `"string"`
|
|
13
|
+
* (it becomes the kernel key `<table>:<pk>`), checked when the {@link table}
|
|
14
|
+
* handle is built.
|
|
15
|
+
*/
|
|
16
|
+
export interface TableSchema {
|
|
17
|
+
readonly primaryKey: string;
|
|
18
|
+
readonly columns: {
|
|
19
|
+
readonly [name: string]: ColumnType;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A table row: a JSON object whose fields are the schema's columns. The value
|
|
24
|
+
* type mirrors {@link ColumnType} (string/number/boolean/object). A row is
|
|
25
|
+
* validated against the schema on insert before it is stored.
|
|
26
|
+
*/
|
|
27
|
+
export type Row = {
|
|
28
|
+
[column: string]: string | number | boolean | object;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* A lazy, chainable query over a table's rows.
|
|
32
|
+
*
|
|
33
|
+
* A Query IS a {@link Result} (it is iterable and has `toArray`), so it can be
|
|
34
|
+
* consumed directly; it additionally offers `where` and `select`, each returning
|
|
35
|
+
* a new Query so operations compose (`table.where(...).select(...)`). Like every
|
|
36
|
+
* lens read it stays lazy and re-iterable: nothing runs until iterated, and each
|
|
37
|
+
* pass re-runs the underlying table scan against current state.
|
|
38
|
+
*/
|
|
39
|
+
export interface Query extends Result<Row> {
|
|
40
|
+
/** Keep only the rows whose top-level fields all equal `predicate`'s (deep
|
|
41
|
+
* structural equality, type-sensitive — the same matcher the document lens
|
|
42
|
+
* uses). Multiple predicate fields are an implicit AND; an empty predicate
|
|
43
|
+
* keeps every row. Chained `where`s narrow further (also an AND). */
|
|
44
|
+
where(predicate: Row): Query;
|
|
45
|
+
/** Project each row down to only the named `columns`. A requested column that
|
|
46
|
+
* a row does not have is simply omitted from that row's projection. On a joined
|
|
47
|
+
* result the columns are the qualified `table.column` names, which `select`
|
|
48
|
+
* matches literally — so projecting `"users.id"` works the same as any column. */
|
|
49
|
+
select(...columns: string[]): Query;
|
|
50
|
+
/**
|
|
51
|
+
* Inner equi-join this query's rows against `other`'s on `this[leftField]`
|
|
52
|
+
* deeply equal to `other[rightField]`, returning a chainable {@link Query} of
|
|
53
|
+
* the matched, combined rows. Each result row carries every column of both
|
|
54
|
+
* sides, qualified as `table.column` (e.g. `users.id`, `orders.total`), so the
|
|
55
|
+
* two sides never collide and `select`/`where` can name a column unambiguously.
|
|
56
|
+
*
|
|
57
|
+
* Inner means unmatched rows on either side are dropped; one left key matching
|
|
58
|
+
* several right rows fans out to several result rows (and vice versa). Joining
|
|
59
|
+
* on a key that a row lacks never matches that row (a missing key is not a join
|
|
60
|
+
* value), so it cannot produce a cross product by accident.
|
|
61
|
+
*
|
|
62
|
+
* Cost: O(n*m) — a nested loop over this query's `n` rows and `other`'s `m`
|
|
63
|
+
* rows, the honest minimal join with no indexes (DESIGN.md section 6.2).
|
|
64
|
+
*/
|
|
65
|
+
join(other: Table, leftField: string, rightField: string): Query;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A handle on one typed table over a {@link Store}.
|
|
69
|
+
*
|
|
70
|
+
* Like the other lenses, the handle is a view: it depends only on the `Store`
|
|
71
|
+
* seam and never touches lifecycle. `insert` auto-commits in its own kernel
|
|
72
|
+
* transaction (a file-backed write is fsync'd before it returns). Read-side
|
|
73
|
+
* operations are added in later phases.
|
|
74
|
+
*/
|
|
75
|
+
export interface Table {
|
|
76
|
+
/** This table's name — the qualifier its columns take in a join
|
|
77
|
+
* (`<name>.<column>`). A join reads it off the right-hand table. */
|
|
78
|
+
readonly name: string;
|
|
79
|
+
/**
|
|
80
|
+
* Validate `row` against the schema and store it under `<table>:<pk>`,
|
|
81
|
+
* overwriting any existing row at that primary key. Throws if the row is
|
|
82
|
+
* invalid (missing/wrong-typed column, or an unknown field). Reports one
|
|
83
|
+
* changed entry.
|
|
84
|
+
*/
|
|
85
|
+
insert(row: Row): WriteResult;
|
|
86
|
+
/** The row stored under primary key `pk`, or `undefined` if none exists. The
|
|
87
|
+
* returned row is the whole stored object, including the primary-key column. */
|
|
88
|
+
get(pk: string): Row | undefined;
|
|
89
|
+
/** Remove the row at primary key `pk`. Reports one changed entry if it
|
|
90
|
+
* existed, zero if it did not. */
|
|
91
|
+
delete(pk: string): WriteResult;
|
|
92
|
+
/** Every row in the table, in ascending primary-key (byte) order, as a
|
|
93
|
+
* chainable {@link Query}. Each row is the whole stored object (the primary key
|
|
94
|
+
* is a declared column, so it is part of the row — there is no separate id).
|
|
95
|
+
* The Query is lazy and re-iterable: each pass re-runs the scan against current
|
|
96
|
+
* state. The scan sees only this table's rows — the `<table>:` byte prefix is a
|
|
97
|
+
* sound boundary, so a sibling whose name shares a prefix (`users` vs `users2`)
|
|
98
|
+
* is never included. */
|
|
99
|
+
all(): Query;
|
|
100
|
+
/** The rows matching `predicate` (top-level field equality). Sugar for
|
|
101
|
+
* `all().where(predicate)`; returns a chainable {@link Query}. */
|
|
102
|
+
where(predicate: Row): Query;
|
|
103
|
+
/** Every row projected to only `columns`. Sugar for `all().select(...columns)`;
|
|
104
|
+
* returns a chainable {@link Query}. */
|
|
105
|
+
select(...columns: string[]): Query;
|
|
106
|
+
/** Inner equi-join every row of this table against `other` on
|
|
107
|
+
* `this[leftField]` equal to `other[rightField]`. Sugar for
|
|
108
|
+
* `all().join(other, leftField, rightField)`; returns a chainable {@link Query}
|
|
109
|
+
* of qualified `table.column` rows. */
|
|
110
|
+
join(other: Table, leftField: string, rightField: string): Query;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Build a {@link Table} handle for `name` with `schema` over a {@link Store}
|
|
114
|
+
* (the kernel's `Database` satisfies it). The schema is validated immediately:
|
|
115
|
+
* the `primaryKey` must name a declared column whose type is `"string"`, since
|
|
116
|
+
* the primary key becomes the string kernel key.
|
|
117
|
+
*/
|
|
118
|
+
export declare function table(store: Store, name: string, schema: TableSchema): Table;
|
|
119
|
+
//# sourceMappingURL=relational.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relational.d.ts","sourceRoot":"","sources":["../../src/lens/relational.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAU,KAAK,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AACnE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEpE;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,CAAA;KAAE,CAAC;CAC3D;AAED;;;;GAIG;AACH,MAAM,MAAM,GAAG,GAAG;IAAE,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAA;CAAE,CAAC;AA0C3E;;;;;;;;GAQG;AACH,MAAM,WAAW,KAAM,SAAQ,MAAM,CAAC,GAAG,CAAC;IACxC;;;yEAGqE;IACrE,KAAK,CAAC,SAAS,EAAE,GAAG,GAAG,KAAK,CAAC;IAC7B;;;sFAGkF;IAClF,MAAM,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IACpC;;;;;;;;;;;;;;OAcG;IACH,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,KAAK,CAAC;CAClE;AAuGD;;;;;;;GAOG;AACH,MAAM,WAAW,KAAK;IACpB;wEACoE;IACpE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,WAAW,CAAC;IAC9B;oFACgF;IAChF,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS,CAAC;IACjC;sCACkC;IAClC,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,CAAC;IAChC;;;;;;4BAMwB;IACxB,GAAG,IAAI,KAAK,CAAC;IACb;sEACkE;IAClE,KAAK,CAAC,SAAS,EAAE,GAAG,GAAG,KAAK,CAAC;IAC7B;4CACwC;IACxC,MAAM,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IACpC;;;2CAGuC;IACvC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,KAAK,CAAC;CAClE;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,KAAK,CAoD5E"}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lens/relational.ts — the relational lens, LibreDB's reach lens.
|
|
3
|
+
*
|
|
4
|
+
* This is a deliberately minimal *relational view*, NOT a SQL engine (DESIGN.md
|
|
5
|
+
* section 6.2): there is no SQL text, lexer, or planner. A table is a typed,
|
|
6
|
+
* schema-validated collection of rows stored as JSON objects under a
|
|
7
|
+
* `<table>:<pk>` kernel key — the exact storage scheme the document lens uses,
|
|
8
|
+
* reusing its JSON codec. The relational value the lens adds over the document
|
|
9
|
+
* lens is the *schema*: declared columns with declared types, enforced at insert.
|
|
10
|
+
*
|
|
11
|
+
* Schema validation lives here; row STORAGE is delegated to the document lens.
|
|
12
|
+
* Because a validated row is just a JSON object stored at `<table>:<pk>` — the
|
|
13
|
+
* exact key the document lens uses for `<collection>:<id>` — a table is literally
|
|
14
|
+
* a schema-validated document collection. The relational lens holds a
|
|
15
|
+
* {@link doc} handle for the same name and reads/writes rows through it, so it
|
|
16
|
+
* inherits the document lens's codec and its collection-isolation-safe prefix
|
|
17
|
+
* scan for free (a sibling like `users2` never leaks into `users`).
|
|
18
|
+
*
|
|
19
|
+
* On top of the schema/row vocabulary, the {@link table} handle, insert-time
|
|
20
|
+
* validation, and by-pk CRUD (`get`/`delete`/`all`), this lens adds a small
|
|
21
|
+
* chainable query surface: `where` (top-level field-equality filter, reusing the
|
|
22
|
+
* document lens's matcher), `select` (column projection), and `join` (inner
|
|
23
|
+
* equi-join via nested loop, producing rows with columns qualified as
|
|
24
|
+
* `table.column`). The kernel is unchanged.
|
|
25
|
+
*/
|
|
26
|
+
import { doc, matches } from "./document.js";
|
|
27
|
+
import { result } from "./types.js";
|
|
28
|
+
/** Whether `value` matches the declared `type`. `"object"` is a plain JSON
|
|
29
|
+
* object — `null` (typeof `"object"`) and arrays are rejected, since v1 has
|
|
30
|
+
* neither nullable columns nor an array column type. */
|
|
31
|
+
function matchesType(value, type) {
|
|
32
|
+
switch (type) {
|
|
33
|
+
case "string":
|
|
34
|
+
return typeof value === "string";
|
|
35
|
+
case "number":
|
|
36
|
+
return typeof value === "number";
|
|
37
|
+
case "boolean":
|
|
38
|
+
return typeof value === "boolean";
|
|
39
|
+
case "object":
|
|
40
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Validate `row` against `schema`, throwing on the first violation (DESIGN.md
|
|
45
|
+
* section 6.2: declared columns required, types checked, unknown fields
|
|
46
|
+
* rejected). Strict by design — there is no silent coercion or dropping.
|
|
47
|
+
*/
|
|
48
|
+
function validateRow(schema, row) {
|
|
49
|
+
// Unknown fields: every row key must be a declared column.
|
|
50
|
+
for (const key of Object.keys(row)) {
|
|
51
|
+
if (!Object.prototype.hasOwnProperty.call(schema.columns, key)) {
|
|
52
|
+
throw new Error(`unknown column "${key}" (not declared in the table schema)`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Declared columns: each must be present and match its declared type. A
|
|
56
|
+
// missing or null value fails here (no nullable/optional columns in v1).
|
|
57
|
+
for (const [name, type] of Object.entries(schema.columns)) {
|
|
58
|
+
if (!Object.prototype.hasOwnProperty.call(row, name)) {
|
|
59
|
+
throw new Error(`missing required column "${name}"`);
|
|
60
|
+
}
|
|
61
|
+
if (!matchesType(row[name], type)) {
|
|
62
|
+
throw new Error(`column "${name}" expected ${type}, got ${typeof row[name]}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Whether `row` matches `predicate` by the document lens's field-equality
|
|
67
|
+
* matcher. The casts are the same Row/Doc type-system artifact `table` uses
|
|
68
|
+
* elsewhere (their value unions overlap but neither is a subtype of the other);
|
|
69
|
+
* the runtime values are plain JSON. */
|
|
70
|
+
function rowMatches(row, predicate) {
|
|
71
|
+
return matches(row, predicate);
|
|
72
|
+
}
|
|
73
|
+
/** A row containing only `columns` that are actually present on `row`. A row's
|
|
74
|
+
* values are always defined (it comes from JSON, which has no `undefined`), so an
|
|
75
|
+
* absent column reads as `undefined` and is omitted from the projection. */
|
|
76
|
+
function project(row, columns) {
|
|
77
|
+
const out = {};
|
|
78
|
+
for (const column of columns) {
|
|
79
|
+
const value = row[column];
|
|
80
|
+
if (value !== undefined) {
|
|
81
|
+
out[column] = value;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
/** Whether a left row joins to a right row: `left[leftField]` deeply equals
|
|
87
|
+
* `right[rightField]`. A join match is literally "the left row matches a
|
|
88
|
+
* predicate built from the right row's join value", so it reuses the document
|
|
89
|
+
* lens's `matches` for the exact same deep, type-sensitive equality `where` uses.
|
|
90
|
+
* A field a row lacks reads as `undefined`, which is never a join value (a row
|
|
91
|
+
* from JSON never holds `undefined`), so a missing key on either side never
|
|
92
|
+
* matches — an inner join, not an accidental cross product. */
|
|
93
|
+
function joinKeyEqual(left, leftField, right, rightField) {
|
|
94
|
+
const rightValue = right[rightField];
|
|
95
|
+
if (left[leftField] === undefined || rightValue === undefined)
|
|
96
|
+
return false;
|
|
97
|
+
return matches(left, { [leftField]: rightValue });
|
|
98
|
+
}
|
|
99
|
+
/** Combine a matched left/right pair into one row whose columns are qualified by
|
|
100
|
+
* their table name (`<table>.<column>`), so columns of the same name on the two
|
|
101
|
+
* sides stay distinct and a later `select`/`where` can name either unambiguously. */
|
|
102
|
+
function qualify(leftName, left, rightName, right) {
|
|
103
|
+
const out = {};
|
|
104
|
+
for (const [column, value] of Object.entries(left))
|
|
105
|
+
out[`${leftName}.${column}`] = value;
|
|
106
|
+
for (const [column, value] of Object.entries(right))
|
|
107
|
+
out[`${rightName}.${column}`] = value;
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
/** The inner equi-join itself: a nested loop pairing each left row with each
|
|
111
|
+
* right row that shares the join key, emitting qualified rows in left-major order
|
|
112
|
+
* (every match for the first left row, then the second, ...). O(n*m). */
|
|
113
|
+
function joinRows(leftName, leftRows, other, leftField, rightField) {
|
|
114
|
+
const rightRows = other.all().toArray();
|
|
115
|
+
const out = [];
|
|
116
|
+
for (const left of leftRows) {
|
|
117
|
+
for (const right of rightRows) {
|
|
118
|
+
if (joinKeyEqual(left, leftField, right, rightField)) {
|
|
119
|
+
out.push(qualify(leftName, left, other.name, right));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Wrap a lazy row source as a {@link Query}. `where`/`select`/`join` build new
|
|
127
|
+
* Queries over the materialized rows of this one, so they re-run the source each
|
|
128
|
+
* pass (laziness and re-iterability carry through the whole chain). All filtering,
|
|
129
|
+
* projection, and joining happen in-engine over the rows — there are no indexes,
|
|
130
|
+
* so a `where` is O(n) in the table size and a `join` is O(n*m), the same cost as
|
|
131
|
+
* the underlying scans.
|
|
132
|
+
*
|
|
133
|
+
* `name` is the qualifier the rows belong to — the table name. It is carried so
|
|
134
|
+
* a later `join` can prefix this side's columns as `<name>.<column>`; `where` and
|
|
135
|
+
* `select` do not use it (they match/project by literal column name), so it rides
|
|
136
|
+
* through them unchanged.
|
|
137
|
+
*/
|
|
138
|
+
function query(name, source) {
|
|
139
|
+
const base = result(source);
|
|
140
|
+
return {
|
|
141
|
+
[Symbol.iterator]() {
|
|
142
|
+
return base[Symbol.iterator]();
|
|
143
|
+
},
|
|
144
|
+
toArray() {
|
|
145
|
+
return base.toArray();
|
|
146
|
+
},
|
|
147
|
+
where(predicate) {
|
|
148
|
+
return query(name, () => base.toArray().filter((row) => rowMatches(row, predicate)));
|
|
149
|
+
},
|
|
150
|
+
select(...columns) {
|
|
151
|
+
return query(name, () => base.toArray().map((row) => project(row, columns)));
|
|
152
|
+
},
|
|
153
|
+
join(other, leftField, rightField) {
|
|
154
|
+
return query(name, () => joinRows(name, base.toArray(), other, leftField, rightField));
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Build a {@link Table} handle for `name` with `schema` over a {@link Store}
|
|
160
|
+
* (the kernel's `Database` satisfies it). The schema is validated immediately:
|
|
161
|
+
* the `primaryKey` must name a declared column whose type is `"string"`, since
|
|
162
|
+
* the primary key becomes the string kernel key.
|
|
163
|
+
*/
|
|
164
|
+
export function table(store, name, schema) {
|
|
165
|
+
const pkType = schema.columns[schema.primaryKey];
|
|
166
|
+
if (pkType === undefined) {
|
|
167
|
+
throw new Error(`primaryKey "${schema.primaryKey}" is not a declared column`);
|
|
168
|
+
}
|
|
169
|
+
if (pkType !== "string") {
|
|
170
|
+
throw new Error(`primaryKey "${schema.primaryKey}" must be a string column, but is ${pkType}`);
|
|
171
|
+
}
|
|
172
|
+
// A table is a schema-validated document collection: rows are stored, read,
|
|
173
|
+
// deleted, and scanned through the document lens at `<table>:<pk>`. This is
|
|
174
|
+
// what reuses the document codec and the collection-isolation-safe prefix scan;
|
|
175
|
+
// a row is a Doc whose fields happen to be the declared columns. The Row/Doc
|
|
176
|
+
// casts are type-system artifacts (their value unions overlap but neither is a
|
|
177
|
+
// subtype of the other); validation guarantees what crosses the boundary.
|
|
178
|
+
const rows = doc(store, name);
|
|
179
|
+
// Every read starts from the full table scan re-wrapped as a chainable Query:
|
|
180
|
+
// drop the document lens's DocEntry id wrapper (the pk lives in the row), then
|
|
181
|
+
// let where/select compose over it. Each call rebuilds the Query so laziness
|
|
182
|
+
// and re-iterability carry through from the document scan.
|
|
183
|
+
const allRows = () => query(name, () => rows.all().toArray().map((entry) => entry.doc));
|
|
184
|
+
return {
|
|
185
|
+
name,
|
|
186
|
+
insert(row) {
|
|
187
|
+
validateRow(schema, row);
|
|
188
|
+
// The primary-key column is validated as a string above and required, so
|
|
189
|
+
// this read is a checked string, safe as the document id.
|
|
190
|
+
const pk = row[schema.primaryKey];
|
|
191
|
+
return rows.put(pk, row);
|
|
192
|
+
},
|
|
193
|
+
get(pk) {
|
|
194
|
+
return rows.get(pk);
|
|
195
|
+
},
|
|
196
|
+
delete(pk) {
|
|
197
|
+
return rows.delete(pk);
|
|
198
|
+
},
|
|
199
|
+
all() {
|
|
200
|
+
return allRows();
|
|
201
|
+
},
|
|
202
|
+
where(predicate) {
|
|
203
|
+
return allRows().where(predicate);
|
|
204
|
+
},
|
|
205
|
+
select(...columns) {
|
|
206
|
+
return allRows().select(...columns);
|
|
207
|
+
},
|
|
208
|
+
join(other, leftField, rightField) {
|
|
209
|
+
return allRows().join(other, leftField, rightField);
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=relational.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relational.js","sourceRoot":"","sources":["../../src/lens/relational.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lens/types.ts — the shared query/result shape every lens reuses.
|
|
3
|
+
*
|
|
4
|
+
* A lens (kv, document, relational) is a typed view over the kernel in core.ts.
|
|
5
|
+
* Lenses differ wildly in HOW you ask for data — a kv key range, a document
|
|
6
|
+
* filter, a relational SELECT — and that is on purpose: DESIGN.md section 2 (and
|
|
7
|
+
* the founding brainstorm in HANDOFF.md) reject a single unified query
|
|
8
|
+
* *language* as a lowest-common-denominator trap. So what lenses share is not
|
|
9
|
+
* the query syntax but the RESULT envelope: every read hands back a
|
|
10
|
+
* {@link Result}, every write reports a {@link WriteResult}. Pinning these two
|
|
11
|
+
* shapes once lets later lenses — and the universal tooling above them (Studio,
|
|
12
|
+
* Platform) — consume any lens uniformly without flattening their differences.
|
|
13
|
+
*
|
|
14
|
+
* This file is an edge, not the kernel: it holds no durability logic, only the
|
|
15
|
+
* vocabulary the lenses speak.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* What every lens read returns: a lazy, typed, re-iterable sequence of rows.
|
|
19
|
+
*
|
|
20
|
+
* `Row` is the lens's own unit — a kv {@link import("../core.ts").Entry}, a
|
|
21
|
+
* document, a relational tuple — so the shape is shared without erasing what
|
|
22
|
+
* each lens actually yields.
|
|
23
|
+
*
|
|
24
|
+
* Lazy and re-iterable are deliberate. The kernel's range scan is itself lazy
|
|
25
|
+
* (`Iterable<Entry>`), so a Result must not force a large scan to materialize
|
|
26
|
+
* just to be wrapped. Re-iterable means a Result behaves like the query it
|
|
27
|
+
* stands for: iterating it twice runs the read twice, instead of the classic
|
|
28
|
+
* generator footgun where the second pass silently yields nothing. Because of
|
|
29
|
+
* that, the Result *is* the deferred query — there is no separate query object
|
|
30
|
+
* to define.
|
|
31
|
+
*/
|
|
32
|
+
export interface Result<Row> extends Iterable<Row> {
|
|
33
|
+
/** Materialize every row into a fresh array. Convenience over iterating; the
|
|
34
|
+
* returned array is the caller's to keep and mutate. */
|
|
35
|
+
toArray(): Row[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The outcome of a lens write.
|
|
39
|
+
*
|
|
40
|
+
* `changed` is the number of stored entries the write created, overwrote, or
|
|
41
|
+
* removed. It is uniform across lenses (a kv set is 1, a delete of an absent
|
|
42
|
+
* key is 0, a relational UPDATE is its matched-row count) and keeps writes
|
|
43
|
+
* visible by default (DESIGN.md principle 2): a caller can always see what a
|
|
44
|
+
* write actually did.
|
|
45
|
+
*/
|
|
46
|
+
export interface WriteResult {
|
|
47
|
+
readonly changed: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Build a {@link Result} from a source of rows.
|
|
51
|
+
*
|
|
52
|
+
* `source` is a thunk, not an iterable, so the read is deferred and repeatable:
|
|
53
|
+
* nothing runs until the Result is iterated, and each iteration calls `source`
|
|
54
|
+
* again for a fresh pass. Every lens constructs its reads through this one
|
|
55
|
+
* helper so "what a Result is" has a single definition.
|
|
56
|
+
*/
|
|
57
|
+
export declare function result<Row>(source: () => Iterable<Row>): Result<Row>;
|
|
58
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lens/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,MAAM,CAAC,GAAG,CAAE,SAAQ,QAAQ,CAAC,GAAG,CAAC;IAChD;4DACwD;IACxD,OAAO,IAAI,GAAG,EAAE,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CASpE"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lens/types.ts — the shared query/result shape every lens reuses.
|
|
3
|
+
*
|
|
4
|
+
* A lens (kv, document, relational) is a typed view over the kernel in core.ts.
|
|
5
|
+
* Lenses differ wildly in HOW you ask for data — a kv key range, a document
|
|
6
|
+
* filter, a relational SELECT — and that is on purpose: DESIGN.md section 2 (and
|
|
7
|
+
* the founding brainstorm in HANDOFF.md) reject a single unified query
|
|
8
|
+
* *language* as a lowest-common-denominator trap. So what lenses share is not
|
|
9
|
+
* the query syntax but the RESULT envelope: every read hands back a
|
|
10
|
+
* {@link Result}, every write reports a {@link WriteResult}. Pinning these two
|
|
11
|
+
* shapes once lets later lenses — and the universal tooling above them (Studio,
|
|
12
|
+
* Platform) — consume any lens uniformly without flattening their differences.
|
|
13
|
+
*
|
|
14
|
+
* This file is an edge, not the kernel: it holds no durability logic, only the
|
|
15
|
+
* vocabulary the lenses speak.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Build a {@link Result} from a source of rows.
|
|
19
|
+
*
|
|
20
|
+
* `source` is a thunk, not an iterable, so the read is deferred and repeatable:
|
|
21
|
+
* nothing runs until the Result is iterated, and each iteration calls `source`
|
|
22
|
+
* again for a fresh pass. Every lens constructs its reads through this one
|
|
23
|
+
* helper so "what a Result is" has a single definition.
|
|
24
|
+
*/
|
|
25
|
+
export function result(source) {
|
|
26
|
+
return {
|
|
27
|
+
[Symbol.iterator]() {
|
|
28
|
+
return source()[Symbol.iterator]();
|
|
29
|
+
},
|
|
30
|
+
toArray() {
|
|
31
|
+
return [...source()];
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/lens/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAoCH;;;;;;;GAOG;AACH,MAAM,UAAU,MAAM,CAAM,MAA2B;IACrD,OAAO;QACL,CAAC,MAAM,CAAC,QAAQ,CAAC;YACf,OAAO,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrC,CAAC;QACD,OAAO;YACL,OAAO,CAAC,GAAG,MAAM,EAAE,CAAC,CAAC;QACvB,CAAC;KACF,CAAC;AACJ,CAAC"}
|