@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 LibreDB
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# LibreDB
|
|
2
|
+
|
|
3
|
+
> Status: pre-alpha. The kernel (`core.ts`) and all three lenses — `lens/kv.ts` (key-value), `lens/document.ts` (JSON documents), and `lens/relational.ts` (typed tables) — are implemented and tested.
|
|
4
|
+
|
|
5
|
+
**LibreDB is a small, readable, embeddable database.**
|
|
6
|
+
|
|
7
|
+
It is built on one idea: a database can be powerful and still remain understandable. You should be able to open its source, learn how a database actually works, and embed it into your own product in an afternoon.
|
|
8
|
+
|
|
9
|
+
- A single small storage core (`core.ts`), with relational, document, and key-value as thin *lenses* on top — not three separate engines (FoundationDB-style architecture).
|
|
10
|
+
- Written in TypeScript, shipped on npm.
|
|
11
|
+
- Open at the edges, guarded at the durability core. Every line of the core is tested.
|
|
12
|
+
|
|
13
|
+
LibreDB is the database in a three-product family that shares one spine:
|
|
14
|
+
|
|
15
|
+
- **LibreDB** — the database (this repository).
|
|
16
|
+
- **LibreDB Studio** — the open-source IDE for every database (Postgres, MySQL, MongoDB, Redis, and more). LibreDB is one database it supports natively, not a requirement.
|
|
17
|
+
- **LibreDB Platform** — the managed, team-oriented form of data.
|
|
18
|
+
|
|
19
|
+
## Usage — the key-value lens
|
|
20
|
+
|
|
21
|
+
The first lens is `kv`: a durable, ordered, string-keyed map over the kernel. You `open` a kernel database and put a `kv` lens over it.
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { open, kv } from "libredb";
|
|
25
|
+
|
|
26
|
+
// In-memory: the natural fit for tests and ephemeral use.
|
|
27
|
+
const db = open();
|
|
28
|
+
|
|
29
|
+
// Or file-backed and durable. Every write is appended to a write-ahead
|
|
30
|
+
// log and fsync'd before it returns, so a committed write survives a
|
|
31
|
+
// crash; reopening the same path reconstructs exactly the committed state:
|
|
32
|
+
//
|
|
33
|
+
// const db = open({ path: "data.libredb" });
|
|
34
|
+
|
|
35
|
+
const store = kv(db);
|
|
36
|
+
|
|
37
|
+
store.set("user:1", "Ada");
|
|
38
|
+
store.set("user:2", "Grace");
|
|
39
|
+
|
|
40
|
+
store.get("user:1"); // "Ada"
|
|
41
|
+
store.get("missing"); // undefined
|
|
42
|
+
|
|
43
|
+
db.close();
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Writes report what they changed
|
|
47
|
+
|
|
48
|
+
Every write returns a `WriteResult` (`{ changed }`), so a caller can always see what the write actually did:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
store.set("user:1", "Ada Lovelace").changed; // 1 (overwrote the existing value)
|
|
52
|
+
|
|
53
|
+
store.delete("user:2").changed; // 1 (it existed)
|
|
54
|
+
store.delete("user:2").changed; // 0 (already gone)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Ordered scans
|
|
58
|
+
|
|
59
|
+
Keys are ordered by byte order, so reads come back in ascending key order. `range(start, end)` scans the half-open interval `[start, end)` — `start` included, `end` excluded:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
store.set("a", "1");
|
|
63
|
+
store.set("b", "2");
|
|
64
|
+
store.set("c", "3");
|
|
65
|
+
|
|
66
|
+
store.range("a", "c").toArray();
|
|
67
|
+
// [{ key: "a", value: "1" }, { key: "b", value: "2" }] — "c" is excluded
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`prefix(p)` scans every key beginning with `p` — the canonical ordered-KV query:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
store.prefix("user:").toArray();
|
|
74
|
+
// [{ key: "user:1", value: "Ada Lovelace" }]
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
A read returns a `Result`, which is **lazy and re-iterable**: nothing runs until you iterate it (or call `toArray()`), and each pass re-runs the scan against the current state. Iterate it directly or materialize it:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
for (const { key, value } of store.prefix("user:")) {
|
|
81
|
+
console.log(key, value);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Multi-key atomicity
|
|
86
|
+
|
|
87
|
+
Each `kv` operation auto-commits in its own transaction. When you need several writes to apply atomically, drop to the kernel's `transact` directly — it commits all of the body's writes together, or nothing if the body throws:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
const encode = new TextEncoder();
|
|
91
|
+
|
|
92
|
+
db.transact((tx) => {
|
|
93
|
+
tx.set(encode.encode("from"), encode.encode("90"));
|
|
94
|
+
tx.set(encode.encode("to"), encode.encode("110"));
|
|
95
|
+
}); // both writes commit together, or neither does
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The kernel speaks raw bytes on purpose; the `kv` lens is the string-ergonomic face over it.
|
|
99
|
+
|
|
100
|
+
## Usage — the document lens
|
|
101
|
+
|
|
102
|
+
The `doc` lens is the JSON face over the same kernel: a *collection* of JSON documents, each stored under a string `id`. Where `kv` stores strings, `doc` stores objects — numbers stay numbers, and nested objects and arrays survive the round-trip.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { open, doc } from "libredb";
|
|
106
|
+
|
|
107
|
+
const db = open(); // or open({ path: "data.libredb" }) for durability
|
|
108
|
+
|
|
109
|
+
const users = doc(db, "users");
|
|
110
|
+
|
|
111
|
+
users.put("1", { name: "Ada", age: 36, active: true });
|
|
112
|
+
users.put("2", { name: "Grace", age: 45, active: false });
|
|
113
|
+
|
|
114
|
+
users.get("1"); // { name: "Ada", age: 36, active: true }
|
|
115
|
+
users.get("missing"); // undefined
|
|
116
|
+
|
|
117
|
+
db.close();
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Documents are scoped to their collection: `doc(db, "users")` never sees a `doc(db, "orders")` document — and the boundary is exact, so `users` and `users2` stay fully separate.
|
|
121
|
+
|
|
122
|
+
### Writes report what they changed
|
|
123
|
+
|
|
124
|
+
Like the kv lens, every write returns a `WriteResult` (`{ changed }`):
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
users.put("1", { name: "Ada Lovelace", age: 36 }).changed; // 1 (overwrote the existing document)
|
|
128
|
+
|
|
129
|
+
users.delete("2").changed; // 1 (it existed)
|
|
130
|
+
users.delete("2").changed; // 0 (already gone)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Scanning and finding
|
|
134
|
+
|
|
135
|
+
`all()` returns every document in the collection as `{ id, doc }` rows, in ascending `id` (byte) order:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
const people = doc(open(), "people");
|
|
139
|
+
|
|
140
|
+
people.put("1", { name: "Ada", team: "research", active: true });
|
|
141
|
+
people.put("2", { name: "Grace", team: "research", active: false });
|
|
142
|
+
people.put("3", { name: "Edsger", team: "ops", active: true });
|
|
143
|
+
|
|
144
|
+
people.all().toArray();
|
|
145
|
+
// [
|
|
146
|
+
// { id: "1", doc: { name: "Ada", team: "research", active: true } },
|
|
147
|
+
// { id: "2", doc: { name: "Grace", team: "research", active: false } },
|
|
148
|
+
// { id: "3", doc: { name: "Edsger", team: "ops", active: true } },
|
|
149
|
+
// ]
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`find(predicate)` returns the documents whose top-level fields all equal the predicate's. Multiple fields are AND'd, and equality is deep and type-sensitive — `1` does not match `"1"`:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
people.find({ team: "research", active: true }).toArray();
|
|
156
|
+
// [{ id: "1", doc: { name: "Ada", team: "research", active: true } }]
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`find` is an O(n) full collection scan with an in-engine predicate — there are no secondary indexes (a deliberate v1 omission, so you can read exactly what a query costs). Like every read it returns a lazy, re-iterable `Result`, so you can iterate it directly or call `toArray()`.
|
|
160
|
+
|
|
161
|
+
## Usage — the relational lens
|
|
162
|
+
|
|
163
|
+
The `table` lens is the *typed* face over the same kernel: a table is a schema-validated collection of rows. Where `doc` accepts any JSON object, `table` declares its columns and their types up front and enforces them at insert. A row is stored under `<table>:<pk>`, so a table is literally a schema-validated document collection — it reuses the document codec and the same collection-isolation boundary.
|
|
164
|
+
|
|
165
|
+
You declare a schema (the columns, their types, and which one is the primary key) when you open the table:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { open, table } from "libredb";
|
|
169
|
+
|
|
170
|
+
const db = open(); // or open({ path: "data.libredb" }) for durability
|
|
171
|
+
|
|
172
|
+
const users = table(db, "users", {
|
|
173
|
+
primaryKey: "id",
|
|
174
|
+
columns: { id: "string", name: "string", age: "number", active: "boolean" },
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
users.insert({ id: "1", name: "Ada", age: 36, active: true });
|
|
178
|
+
users.insert({ id: "2", name: "Grace", age: 45, active: false });
|
|
179
|
+
|
|
180
|
+
users.get("1"); // { id: "1", name: "Ada", age: 36, active: true }
|
|
181
|
+
users.get("missing"); // undefined
|
|
182
|
+
|
|
183
|
+
db.close();
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
A column type is one of `"string"`, `"number"`, `"boolean"`, or `"object"` (a plain JSON object). The `primaryKey` must name a declared `"string"` column — it becomes the kernel key.
|
|
187
|
+
|
|
188
|
+
### Validation is strict at insert
|
|
189
|
+
|
|
190
|
+
Insert rejects a row that is missing a declared column, has a wrong-typed value, or carries a field the schema does not declare — there is no silent coercion or field-dropping:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
users.insert({ id: "3", name: "Edsger" });
|
|
194
|
+
// throws: missing required column "age"
|
|
195
|
+
|
|
196
|
+
users.insert({ id: "3", name: "Edsger", age: "old", active: true });
|
|
197
|
+
// throws: column "age" expected number, got string
|
|
198
|
+
|
|
199
|
+
users.insert({ id: "3", name: "Edsger", age: 40, active: true, role: "ops" });
|
|
200
|
+
// throws: unknown column "role" (not declared in the table schema)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Writes report what they changed
|
|
204
|
+
|
|
205
|
+
Like the other lenses, every write returns a `WriteResult` (`{ changed }`):
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
users.insert({ id: "1", name: "Ada Lovelace", age: 36, active: true }).changed; // 1 (overwrote)
|
|
209
|
+
|
|
210
|
+
users.delete("2").changed; // 1 (it existed)
|
|
211
|
+
users.delete("2").changed; // 0 (already gone)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Querying — where, select, join
|
|
215
|
+
|
|
216
|
+
Reads return a chainable `Query` (which is itself a lazy, re-iterable `Result`). `where(predicate)` keeps the rows whose top-level fields all equal the predicate's — deep and type-sensitive, just like document `find`, so `36` does not match `"36"`. `select(...columns)` projects each row down to the named columns. They compose in any order:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
const people = table(open(), "people", {
|
|
220
|
+
primaryKey: "id",
|
|
221
|
+
columns: { id: "string", name: "string", team: "string", active: "boolean" },
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
people.insert({ id: "1", name: "Ada", team: "research", active: true });
|
|
225
|
+
people.insert({ id: "2", name: "Grace", team: "research", active: false });
|
|
226
|
+
people.insert({ id: "3", name: "Edsger", team: "ops", active: true });
|
|
227
|
+
|
|
228
|
+
people.where({ team: "research", active: true }).select("name").toArray();
|
|
229
|
+
// [{ name: "Ada" }]
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
`join(other, leftField, rightField)` is an inner equi-join via nested loop: it pairs each left row with every right row whose `rightField` equals the left row's `leftField`. The result rows carry every column of both sides, qualified as `table.column`, so the two sides never collide — and `select`/`where` name a qualified column the same way as any other:
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
const orders = table(db, "orders", {
|
|
236
|
+
primaryKey: "id",
|
|
237
|
+
columns: { id: "string", userId: "string", total: "number" },
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
orders.insert({ id: "o1", userId: "1", total: 42 });
|
|
241
|
+
orders.insert({ id: "o2", userId: "1", total: 7 });
|
|
242
|
+
|
|
243
|
+
users.join(orders, "id", "userId").select("users.name", "orders.total").toArray();
|
|
244
|
+
// [
|
|
245
|
+
// { "users.name": "Ada Lovelace", "orders.total": 42 },
|
|
246
|
+
// { "users.name": "Ada Lovelace", "orders.total": 7 },
|
|
247
|
+
// ]
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
`where` is O(n) in the table size and `join` is O(n*m) — nested-loop, no indexes (the same deliberate v1 omission as the document lens). Unmatched rows on either side are dropped (inner join), and a left key matching several right rows fans out to several result rows.
|
|
251
|
+
|
|
252
|
+
## Read first
|
|
253
|
+
|
|
254
|
+
- [`MANIFESTO.md`](./MANIFESTO.md) — what LibreDB is and what it refuses to be.
|
|
255
|
+
- [`DESIGN.md`](./DESIGN.md) — the engineering decision record behind the manifesto.
|
|
256
|
+
|
|
257
|
+
## License
|
|
258
|
+
|
|
259
|
+
Open source and free under the MIT License.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapter/store.ts — the storage seam a lens depends on.
|
|
3
|
+
*
|
|
4
|
+
* This is an edge, not the kernel: the open, fast-contribution tier (DESIGN.md
|
|
5
|
+
* section 5), holding no durability logic of its own. It names the one thing a
|
|
6
|
+
* lens needs from storage and nothing more.
|
|
7
|
+
*
|
|
8
|
+
* A lens (kv, and later document, relational) is a view over stored bytes. It
|
|
9
|
+
* reads and writes through transactions; it never opens, closes, or recovers the
|
|
10
|
+
* store — that lifecycle belongs to whoever called {@link import("../core.ts").open}.
|
|
11
|
+
* So the seam the lens depends on is exactly one method, `transact`, deliberately
|
|
12
|
+
* NARROWER than the kernel's {@link import("../core.ts").Database} (which also
|
|
13
|
+
* carries `close`). Depending on the narrow port instead of the whole Database
|
|
14
|
+
* keeps the open-edge lenses from binding to the guarded core's lifecycle, and
|
|
15
|
+
* lets any object that can run a transaction back a lens: the kernel today, an
|
|
16
|
+
* async-wrapped or remote core later, a fake in a test. The kernel's `Database`
|
|
17
|
+
* satisfies this port structurally, so the real, durable path runs through it.
|
|
18
|
+
*
|
|
19
|
+
* This is intentionally thin. The seam is a port the lens needs, not an
|
|
20
|
+
* abstraction layer the user pays for — there is no adapter class to subclass,
|
|
21
|
+
* just the minimal contract.
|
|
22
|
+
*/
|
|
23
|
+
import type { Transaction } from "../core.ts";
|
|
24
|
+
/**
|
|
25
|
+
* The minimal storage capability a lens consumes: run a unit of atomic work and
|
|
26
|
+
* return its result. Semantics are the kernel's — see
|
|
27
|
+
* {@link import("../core.ts").Database.transact}.
|
|
28
|
+
*/
|
|
29
|
+
export interface Store {
|
|
30
|
+
transact<T>(run: (tx: Transaction) => T): T;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/adapter/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;GAIG;AACH,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,CAAC,GAAG,CAAC,CAAC;CAC7C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/adapter/store.ts"],"names":[],"mappings":""}
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core.ts — the LibreDB kernel.
|
|
3
|
+
*
|
|
4
|
+
* This file is the trust boundary, the comprehension boundary, and the
|
|
5
|
+
* architecture boundary all at once (see DESIGN.md section 5). Everything that
|
|
6
|
+
* touches durability lives here and nowhere else:
|
|
7
|
+
*
|
|
8
|
+
* - storage an ordered key-value substrate
|
|
9
|
+
* - transactions atomic apply with the isolation scoped in DESIGN.md
|
|
10
|
+
* - recovery survive a crash and reopen with consistent state
|
|
11
|
+
*
|
|
12
|
+
* The kernel is small because it is genuinely minimal, never because
|
|
13
|
+
* complexity was swept into sibling files. Model lenses (lens/kv.ts,
|
|
14
|
+
* lens/document.ts, lens/relational.ts) sit on top of this; they never
|
|
15
|
+
* reach around it.
|
|
16
|
+
*
|
|
17
|
+
* The boundaries are declared first as types — the contract every lens builds
|
|
18
|
+
* on — followed by the implementation: an ordered key-value store, serializable
|
|
19
|
+
* transactions, and a write-ahead log for durability. Recovery replays that log
|
|
20
|
+
* and discards any record a crash left half-written.
|
|
21
|
+
*/
|
|
22
|
+
/** The LibreDB package version. Kept in sync with package.json. */
|
|
23
|
+
export declare const version = "0.0.1";
|
|
24
|
+
/**
|
|
25
|
+
* A key in the kernel: an immutable sequence of bytes.
|
|
26
|
+
*
|
|
27
|
+
* Keys are bytes, not strings, on purpose. Byte order is unambiguous and
|
|
28
|
+
* stable (unlike JavaScript's UTF-16 string order), and only bytes let the
|
|
29
|
+
* higher lenses build COMPOSITE keys by concatenation and scan them as ranges
|
|
30
|
+
* — the move the whole multi-model architecture rests on. The kv lens adds
|
|
31
|
+
* string ergonomics on top; the kernel stays at the honest substrate.
|
|
32
|
+
*/
|
|
33
|
+
export type Key = Uint8Array;
|
|
34
|
+
/** A value in the kernel: an opaque sequence of bytes. The kernel never
|
|
35
|
+
* interprets a value; serialization is a lens concern, not a kernel one. */
|
|
36
|
+
export type Value = Uint8Array;
|
|
37
|
+
/** One key/value pair yielded by an ordered range scan. */
|
|
38
|
+
export interface Entry {
|
|
39
|
+
readonly key: Key;
|
|
40
|
+
readonly value: Value;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The unit of atomic work against the kernel.
|
|
44
|
+
*
|
|
45
|
+
* Every read and write happens inside a transaction — it is the kernel's one
|
|
46
|
+
* atomicity primitive. Within a transaction, reads observe the transaction's
|
|
47
|
+
* own pending writes (read-your-writes). Across transactions the isolation
|
|
48
|
+
* level is SERIALIZABLE: the synchronous, single-threaded API runs each body to
|
|
49
|
+
* completion before the next begins, so the schedule is always serial. The
|
|
50
|
+
* kernel forbids re-entrant transactions (the only way to overlap two) to keep
|
|
51
|
+
* that guarantee real — see {@link Database.transact}.
|
|
52
|
+
*
|
|
53
|
+
* Keys are ordered by unsigned byte-lexicographic comparison. That ordering is
|
|
54
|
+
* the kernel's defining property: it is what makes `getRange` meaningful and
|
|
55
|
+
* what later lenses encode their indexes against.
|
|
56
|
+
*/
|
|
57
|
+
export interface Transaction {
|
|
58
|
+
/** The value for `key`, or `undefined` if it is not set. */
|
|
59
|
+
get(key: Key): Value | undefined;
|
|
60
|
+
/** Set `key` to `value`, overwriting any existing value. */
|
|
61
|
+
set(key: Key, value: Value): void;
|
|
62
|
+
/** Remove `key`. A no-op if it is not set. */
|
|
63
|
+
delete(key: Key): void;
|
|
64
|
+
/**
|
|
65
|
+
* Scan the half-open range `[start, end)` in ascending key order:
|
|
66
|
+
* `start` is included, `end` is excluded. Lazy by design — callers iterate
|
|
67
|
+
* without the kernel materializing the whole range.
|
|
68
|
+
*/
|
|
69
|
+
getRange(start: Key, end: Key): Iterable<Entry>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* An open kernel instance: the storage substrate plus its durability.
|
|
73
|
+
*
|
|
74
|
+
* The API is synchronous. LibreDB is embedded and single-process (its first
|
|
75
|
+
* home is test/dev), and SQLite shows a synchronous core is both correct and
|
|
76
|
+
* far easier to read than one colored by promises everywhere. Durability uses
|
|
77
|
+
* synchronous fsync (see the write-ahead log further down).
|
|
78
|
+
*/
|
|
79
|
+
export interface Database {
|
|
80
|
+
/**
|
|
81
|
+
* Run `run` inside a transaction and apply its writes atomically. If `run`
|
|
82
|
+
* returns, the writes commit together and become durable; if `run` throws,
|
|
83
|
+
* the transaction aborts and applies nothing. The return value of `run` is
|
|
84
|
+
* passed through.
|
|
85
|
+
*/
|
|
86
|
+
transact<T>(run: (tx: Transaction) => T): T;
|
|
87
|
+
/** Flush pending state and release resources. Safe to call once. */
|
|
88
|
+
close(): void;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* How to open a kernel instance.
|
|
92
|
+
*
|
|
93
|
+
* With `path`, the kernel is file-backed and durable: reopening the same path
|
|
94
|
+
* after a crash reconstructs exactly the committed state. Without `path`, the
|
|
95
|
+
* kernel is purely in-memory — the natural fit for tests and ephemeral use.
|
|
96
|
+
*/
|
|
97
|
+
export interface OpenOptions {
|
|
98
|
+
readonly path?: string;
|
|
99
|
+
}
|
|
100
|
+
/** The signature of the kernel's entry point (see {@link open} for the
|
|
101
|
+
* implementation). Naming the contract separately lets lenses depend on it. */
|
|
102
|
+
export type Open = (options?: OpenOptions) => Database;
|
|
103
|
+
/**
|
|
104
|
+
* Open a kernel instance. See {@link Open} and {@link OpenOptions}.
|
|
105
|
+
*
|
|
106
|
+
* With a `path` the store is recovered from its write-ahead log and future
|
|
107
|
+
* commits are appended to it durably. Without one the store is purely
|
|
108
|
+
* in-memory.
|
|
109
|
+
*/
|
|
110
|
+
export declare const open: Open;
|
|
111
|
+
//# sourceMappingURL=core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAYH,mEAAmE;AACnE,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B;;;;;;;;GAQG;AACH,MAAM,MAAM,GAAG,GAAG,UAAU,CAAC;AAE7B;4EAC4E;AAC5E,MAAM,MAAM,KAAK,GAAG,UAAU,CAAC;AAE/B,2DAA2D;AAC3D,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;IAClB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACvB;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,WAAW;IAC1B,4DAA4D;IAC5D,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,KAAK,GAAG,SAAS,CAAC;IACjC,4DAA4D;IAC5D,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAClC,8CAA8C;IAC9C,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;CACjD;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,QAAQ;IACvB;;;;;OAKG;IACH,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,CAAC,GAAG,CAAC,CAAC;IAC5C,oEAAoE;IACpE,KAAK,IAAI,IAAI,CAAC;CACf;AAED;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;+EAC+E;AAC/E,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,EAAE,WAAW,KAAK,QAAQ,CAAC;AAwSvD;;;;;;GAMG;AACH,eAAO,MAAM,IAAI,EAAE,IA0DlB,CAAC"}
|