@noorm/broccolidb 2.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 +219 -0
- package/dist/TokenCompressionService.d.ts +59 -0
- package/dist/TokenCompressionService.d.ts.map +1 -0
- package/dist/TokenCompressionService.js +179 -0
- package/dist/TokenCompressionService.js.map +1 -0
- package/dist/broccolidb-aggregation.d.ts +14 -0
- package/dist/broccolidb-aggregation.d.ts.map +1 -0
- package/dist/broccolidb-aggregation.js +158 -0
- package/dist/broccolidb-aggregation.js.map +1 -0
- package/dist/broccolidb-cas.d.ts +56 -0
- package/dist/broccolidb-cas.d.ts.map +1 -0
- package/dist/broccolidb-cas.js +285 -0
- package/dist/broccolidb-cas.js.map +1 -0
- package/dist/broccolidb-kernel.d.ts +63 -0
- package/dist/broccolidb-kernel.d.ts.map +1 -0
- package/dist/broccolidb-kernel.js +287 -0
- package/dist/broccolidb-kernel.js.map +1 -0
- package/dist/broccolidb-mutex.d.ts +37 -0
- package/dist/broccolidb-mutex.d.ts.map +1 -0
- package/dist/broccolidb-mutex.js +121 -0
- package/dist/broccolidb-mutex.js.map +1 -0
- package/dist/broccolidb-natural-query.d.ts +13 -0
- package/dist/broccolidb-natural-query.d.ts.map +1 -0
- package/dist/broccolidb-natural-query.js +188 -0
- package/dist/broccolidb-natural-query.js.map +1 -0
- package/dist/broccolidb-table.d.ts +62 -0
- package/dist/broccolidb-table.d.ts.map +1 -0
- package/dist/broccolidb-table.js +893 -0
- package/dist/broccolidb-table.js.map +1 -0
- package/dist/broccolidb-wal.d.ts +49 -0
- package/dist/broccolidb-wal.d.ts.map +1 -0
- package/dist/broccolidb-wal.js +168 -0
- package/dist/broccolidb-wal.js.map +1 -0
- package/dist/broccolidb.contracts.d.ts +232 -0
- package/dist/broccolidb.contracts.d.ts.map +1 -0
- package/dist/broccolidb.contracts.js +7 -0
- package/dist/broccolidb.contracts.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/docs/API.md +208 -0
- package/docs/ARCHITECTURE.md +160 -0
- package/docs/BRIEF.md +56 -0
- package/docs/CONTRIBUTING.md +82 -0
- package/docs/GLOSSARY.md +23 -0
- package/docs/OPERATIONS.md +176 -0
- package/docs/PHILOSOPHY.md +91 -0
- package/docs/README.md +116 -0
- package/docs/RELEASE_NOTES.md +34 -0
- package/docs/TROUBLESHOOTING.md +145 -0
- package/docs/adr/ADR-001-portable-inmemory-kernel.md +74 -0
- package/docs/adr/README.md +38 -0
- package/docs/adr/TEMPLATE.md +35 -0
- package/package.json +43 -0
package/docs/API.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# API reference
|
|
2
|
+
|
|
3
|
+
This reference documents the supported public entry point:
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { ... } from "@noorm/broccolidb"
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The complete type-level contract is in [`src/broccolidb.contracts.ts`](../src/broccolidb.contracts.ts)
|
|
10
|
+
and the export list is in [`src/index.ts`](../src/index.ts).
|
|
11
|
+
|
|
12
|
+
## Kernel
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
interface DatabaseKernelOptions {
|
|
16
|
+
workspaceRoot?: string
|
|
17
|
+
walDebounceMs?: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const db = new BroccoliDatabaseKernel(options)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
| Method | Returns | Notes |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `start()` | `Promise<void>` | Creates storage directories, loads checkpoint, and replays WAL. Idempotent. |
|
|
26
|
+
| `stop()` | `Promise<void>` | Flushes WAL and stops WAL/CAS services. |
|
|
27
|
+
| `flush()` | `Promise<void>` | Writes buffered WAL frames. |
|
|
28
|
+
| `getTable<T>(name)` | `IDbTable<T>` | Returns or creates a typed in-memory table. |
|
|
29
|
+
| `transaction(fn)` | `Promise<R>` | Concrete kernel method; runs an async callback under the re-entrant mutex and flushes afterward. |
|
|
30
|
+
| `checkpoint(label?)` | `Promise<TimelineCheckpointRecord>` | Writes a base snapshot, history record, and WAL rotation. |
|
|
31
|
+
| `rollback(id)` | `Promise<boolean>` | Restores a cached or on-disk checkpoint. |
|
|
32
|
+
| `listCheckpoints()` | `readonly TimelineCheckpointRecord[]` | Lists checkpoint records known to the current process. |
|
|
33
|
+
| `health()` | `Promise<DbHealthReport>` | Reports writeability, CAS metrics, WAL metrics, and table counts. |
|
|
34
|
+
| `storeBlob(content)` | `Promise<string>` | Stores bytes/string in CAS and returns the SHA-256 address. |
|
|
35
|
+
| `readBlob(hash)` | `Promise<Buffer \| null>` | Returns verified content or `null` when the blob is absent. |
|
|
36
|
+
| `gc()` | `Promise<number>` | Removes CAS files not referenced by current table string values prefixed `CAS:`. |
|
|
37
|
+
|
|
38
|
+
The package also exports `broccolidb`, a singleton constructed with the default
|
|
39
|
+
workspace root. Prefer an explicitly constructed kernel when an application has
|
|
40
|
+
more than one workspace, test isolation, or lifecycle owner.
|
|
41
|
+
|
|
42
|
+
## Tables
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
type User = { id: string; name: string; score: number; active: boolean }
|
|
46
|
+
const users = db.getTable<User>("users")
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
| Method | Description |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `get(id)` | Read one record or return `undefined`. |
|
|
52
|
+
| `getAll()` | Return a readonly snapshot of current records. |
|
|
53
|
+
| `put(id, record, options?)` | Insert or replace a record; `ttlMs` schedules expiration. `idempotencyKey` is accepted by the contract but is not currently used for deduplication. |
|
|
54
|
+
| `putMany(entries)` | Apply multiple puts. |
|
|
55
|
+
| `compareAndSwap(id, predicate, updater, options?)` | Apply an update only when the predicate accepts the current record. |
|
|
56
|
+
| `delete(id)` | Delete one record and return whether it existed. |
|
|
57
|
+
| `deleteWhere(where)` | Delete matching records and return the count. |
|
|
58
|
+
| `updateWhere(where, updater)` | Update matching records and return the count. |
|
|
59
|
+
| `query(options?)` | Filter, order, paginate, and return readonly records. |
|
|
60
|
+
| `aggregate(query)` | Group and calculate statistical metrics. |
|
|
61
|
+
| `select()` | Start a fluent query builder. |
|
|
62
|
+
| `subscribe(callback, filter?)` | Subscribe to change events; call `.unsubscribe()` to remove it. |
|
|
63
|
+
| `count()` | Return current record count. |
|
|
64
|
+
| `clear()` | Remove all records and emit a clear event. |
|
|
65
|
+
| `createSnapshot()` / `restoreSnapshot()` | Capture or replace the table's in-memory map. |
|
|
66
|
+
|
|
67
|
+
## Indexes
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
users.createIndex("active")
|
|
71
|
+
users.createSortedIndex("score")
|
|
72
|
+
users.createCompositeIndex(["active", "name"])
|
|
73
|
+
users.createPrefixIndex("name")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Index creation is explicit and does not change query semantics. Available index
|
|
77
|
+
types are `equality`, `sorted`, `composite`, and `prefix`. The fluent builder's
|
|
78
|
+
`explain()` method returns the selected strategy and candidate counts.
|
|
79
|
+
|
|
80
|
+
## Query options
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
const result = users.query({
|
|
84
|
+
where: {
|
|
85
|
+
active: true,
|
|
86
|
+
score: { $gte: 80, $lt: 100 },
|
|
87
|
+
},
|
|
88
|
+
sortBy: "score",
|
|
89
|
+
sortOrder: "desc",
|
|
90
|
+
limit: 20,
|
|
91
|
+
offset: 0,
|
|
92
|
+
})
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Supported field operators:
|
|
96
|
+
|
|
97
|
+
| Operator | Meaning |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `$eq`, `$ne` | Equality or inequality |
|
|
100
|
+
| `$gt`, `$gte`, `$lt`, `$lte` | Ordered comparison |
|
|
101
|
+
| `$in`, `$nin` | Membership or non-membership |
|
|
102
|
+
| `$between` | Inclusive two-value range |
|
|
103
|
+
| `$startsWith`, `$endsWith` | String prefix/suffix |
|
|
104
|
+
| `$contains` | String containment |
|
|
105
|
+
| `$regex` | Regular expression match |
|
|
106
|
+
| `$exists` | Field presence check |
|
|
107
|
+
|
|
108
|
+
Boolean clauses are available through `and`, `or`, and `not`. `sortBy` and
|
|
109
|
+
`sortOrder` accept one field or parallel readonly arrays.
|
|
110
|
+
|
|
111
|
+
## Fluent queries
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
const topUsers = users
|
|
115
|
+
.select()
|
|
116
|
+
.where("active").equals(true)
|
|
117
|
+
.and("score").greaterThanOrEqual(80)
|
|
118
|
+
.orderBy("score", "desc")
|
|
119
|
+
.limit(10)
|
|
120
|
+
|
|
121
|
+
console.log(topUsers.explain())
|
|
122
|
+
console.log(topUsers.execute())
|
|
123
|
+
console.log(topUsers.first())
|
|
124
|
+
console.log(topUsers.count())
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Predicates include `equals`, `notEquals`, `greaterThan`,
|
|
128
|
+
`greaterThanOrEqual`, `lessThan`, `lessThanOrEqual`, `in`, `notIn`, `between`,
|
|
129
|
+
`startsWith`, `contains`, and `matches`.
|
|
130
|
+
|
|
131
|
+
## Aggregation
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
const summary = users.aggregate({
|
|
135
|
+
groupBy: ["active"],
|
|
136
|
+
metrics: {
|
|
137
|
+
users: { metric: "count" },
|
|
138
|
+
averageScore: { metric: "avg", field: "score" },
|
|
139
|
+
scoreSpread: { metric: "stddev", field: "score" },
|
|
140
|
+
},
|
|
141
|
+
having: { users: { $gte: 2 } },
|
|
142
|
+
})
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Metrics are `sum`, `avg`, `min`, `max`, `count`, and `stddev`. Results include
|
|
146
|
+
groups, grand totals, records evaluated, and elapsed microseconds.
|
|
147
|
+
|
|
148
|
+
## Change events and TTL
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
const subscription = users.subscribe((event) => {
|
|
152
|
+
console.log(event.operation, event.recordId, event.before, event.after)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
users.put("temporary", { id: "temporary", name: "Temp", score: 0, active: true }, { ttlMs: 5_000 })
|
|
156
|
+
subscription.unsubscribe()
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Change operations are `INSERT`, `UPDATE`, `DELETE`, `CLEAR`, and `EXPIRE`.
|
|
160
|
+
Events may include `before`, `after`, and a field-level `diff`.
|
|
161
|
+
|
|
162
|
+
## Natural-language parsing
|
|
163
|
+
|
|
164
|
+
`BroccoliNaturalQueryParser` is a deterministic offline parser, not an LLM:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
const parsed = BroccoliNaturalQueryParser.parse(
|
|
168
|
+
"from users where score >= 80 sorted by score desc limit 10",
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
const rows = db.getTable<User>(parsed.targetTable).query(parsed.queryOptions)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
It recognizes table selection, limit/offset, sorting, comparisons, ranges,
|
|
175
|
+
membership, prefixes, contains, and equality phrases. The returned `confidence`
|
|
176
|
+
is a parsing coverage heuristic; applications should validate user-facing input.
|
|
177
|
+
|
|
178
|
+
## WAL and CAS classes
|
|
179
|
+
|
|
180
|
+
Advanced consumers can use the lower-level services directly:
|
|
181
|
+
|
|
182
|
+
- `BroccoliWriteAheadLog` supports `start`, `appendFrame`, `flush`, `replay`,
|
|
183
|
+
`truncate`, and `getMetrics`.
|
|
184
|
+
- `BroccoliCASStorageService` supports `start`, `store`, `read`, `exists`,
|
|
185
|
+
`pruneUnreferenced`, `getStats`, and `getBaseDir`.
|
|
186
|
+
- `ReentrantAsyncMutex` supports `acquire`, `runLocked`, `isLocked`,
|
|
187
|
+
`getCurrentHolder`, and `getQueueLength`.
|
|
188
|
+
|
|
189
|
+
Prefer the kernel for normal application use. Direct services are useful for
|
|
190
|
+
specialized adapters, diagnostics, and tests.
|
|
191
|
+
|
|
192
|
+
## Errors
|
|
193
|
+
|
|
194
|
+
| Error | Raised when |
|
|
195
|
+
|---|---|
|
|
196
|
+
| `WalIntegrityError` | A WAL line is invalid JSON or its checksum does not match. |
|
|
197
|
+
| `StorageIntegrityError` | A CAS blob cannot be decompressed or its content hash mismatches. |
|
|
198
|
+
| `DatabaseLockError` | Base mutex coordination fails. |
|
|
199
|
+
| `DeadlockTimeoutError` | A mutex waiter exceeds its configured timeout. |
|
|
200
|
+
|
|
201
|
+
## Prompt compression
|
|
202
|
+
|
|
203
|
+
`TokenCompressionService.getInstance().compactPrompt()` normalizes whitespace
|
|
204
|
+
without flattening structured message blocks. Signed thinking blocks are left
|
|
205
|
+
unchanged. Results include a SHA-256 prompt key, estimated token counts,
|
|
206
|
+
compression ratio, requested-model echo, and an L1 cache flag.
|
|
207
|
+
|
|
208
|
+
The estimate is `ceil(characters / 4)` and is not provider usage data.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
BroccoliDB is a four-layer embedded kernel with a process-local coordination
|
|
4
|
+
boundary. The layers are deliberately composable: tables can be used directly,
|
|
5
|
+
while the kernel adds WAL, checkpoints, CAS, and lifecycle management.
|
|
6
|
+
|
|
7
|
+
## Layer map
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
flowchart TB
|
|
11
|
+
K[BroccoliDatabaseKernel]
|
|
12
|
+
T[Reactive in-memory tables\nCRUD · indexes · filters · CDC · TTL]
|
|
13
|
+
W[Write-ahead log\nJSONL · checksum frames · micro-batching]
|
|
14
|
+
C[Checkpoints\natomic base snapshot · timeline history]
|
|
15
|
+
S[CAS vault\nSHA-256 · Brotli · quarantine · GC]
|
|
16
|
+
M[ReentrantAsyncMutex\nprocess-local async coordination]
|
|
17
|
+
|
|
18
|
+
K --> T
|
|
19
|
+
K --> W
|
|
20
|
+
K --> C
|
|
21
|
+
K --> S
|
|
22
|
+
K --> M
|
|
23
|
+
T --> W
|
|
24
|
+
T --> C
|
|
25
|
+
T -. CAS:hash references .-> S
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| Layer | Responsibility | Implementation |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| **L1 tables** | In-memory records, indexes, filtering, aggregation, fluent queries, TTL, CDC | `src/broccolidb-table.ts` |
|
|
31
|
+
| **L2 WAL** | Append mutation frames, micro-batched flush, checksum validation, replay, rotation | `src/broccolidb-wal.ts` |
|
|
32
|
+
| **L3 CAS** | Content-addressed blobs, optional Brotli compression, hash verification, quarantine, mark-sweep | `src/broccolidb-cas.ts` |
|
|
33
|
+
| **L4 checkpointing** | Atomic base snapshot, checkpoint history, rollback metadata | `src/broccolidb-kernel.ts` |
|
|
34
|
+
| **Coordination** | Re-entrant async lock for transactions and serialized WAL/checkpoint operations | `src/broccolidb-mutex.ts` |
|
|
35
|
+
| **Contracts** | Public types and behavior vocabulary | `src/broccolidb.contracts.ts` |
|
|
36
|
+
|
|
37
|
+
## Startup and recovery
|
|
38
|
+
|
|
39
|
+
`start()` is idempotent. The kernel performs the following sequence:
|
|
40
|
+
|
|
41
|
+
1. Create `.broccolidb/` and its checkpoint directory.
|
|
42
|
+
2. Start the CAS service and WAL service.
|
|
43
|
+
3. Load `.broccolidb/checkpoint.db` if it exists.
|
|
44
|
+
4. Replay frames remaining in `.broccolidb/wal.log`.
|
|
45
|
+
5. Mark the kernel started.
|
|
46
|
+
|
|
47
|
+
Checkpoint data is loaded into tables before WAL replay. A missing base snapshot
|
|
48
|
+
is treated as a fresh database. A malformed or checksum-invalid WAL frame is a
|
|
49
|
+
`WalIntegrityError` and should be investigated rather than silently discarded.
|
|
50
|
+
|
|
51
|
+
## Mutation flow
|
|
52
|
+
|
|
53
|
+
```mermaid
|
|
54
|
+
sequenceDiagram
|
|
55
|
+
participant App
|
|
56
|
+
participant Table
|
|
57
|
+
participant Kernel
|
|
58
|
+
participant WAL
|
|
59
|
+
|
|
60
|
+
App->>Kernel: getTable("users")
|
|
61
|
+
Kernel-->>App: IDbTable<User>
|
|
62
|
+
App->>Table: put(id, record)
|
|
63
|
+
Table->>Table: update record and indexes
|
|
64
|
+
Table-->>App: record
|
|
65
|
+
Table->>Kernel: WAL hook (async append)
|
|
66
|
+
Kernel->>WAL: append frame
|
|
67
|
+
WAL-->>WAL: debounce / batch
|
|
68
|
+
App->>Kernel: flush or transaction
|
|
69
|
+
Kernel->>WAL: write JSONL frames
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The table mutation is synchronous from the caller's perspective. The table's
|
|
73
|
+
WAL hook schedules an asynchronous frame append, so callers that need a durable
|
|
74
|
+
boundary must await `flush()`, `transaction()`, `checkpoint()`, or `stop()`.
|
|
75
|
+
|
|
76
|
+
## Checkpoint flow
|
|
77
|
+
|
|
78
|
+
`checkpoint(label)` runs under the kernel mutex:
|
|
79
|
+
|
|
80
|
+
1. Flush pending WAL frames.
|
|
81
|
+
2. Serialize every currently registered table.
|
|
82
|
+
3. Compute a SHA-256 snapshot hash.
|
|
83
|
+
4. Write a temporary base file and rename it to `checkpoint.db`.
|
|
84
|
+
5. Write a named history file under `checkpoints/<checkpointId>.json`.
|
|
85
|
+
6. Cache the timeline record and in-memory snapshots.
|
|
86
|
+
7. Rotate the WAL and append a checkpoint marker.
|
|
87
|
+
|
|
88
|
+
The double-buffered base write protects the previous base snapshot if the
|
|
89
|
+
process fails during the write. Checkpoint history is ordinary JSON and can be
|
|
90
|
+
copied with the rest of `.broccolidb/`.
|
|
91
|
+
|
|
92
|
+
## Rollback flow
|
|
93
|
+
|
|
94
|
+
`rollback(checkpointId)` first checks the process-local snapshot cache. If the
|
|
95
|
+
checkpoint was created in the current process, it restores those maps directly.
|
|
96
|
+
Otherwise it loads the checkpoint history JSON and rebuilds the checkpoint's
|
|
97
|
+
tables. It appends a rollback marker to the WAL. The method returns `false` when
|
|
98
|
+
the requested history file cannot be read or parsed.
|
|
99
|
+
|
|
100
|
+
Rollback is an application state operation, not a distributed transaction or a
|
|
101
|
+
schema migration. Take a backup before destructive rollback workflows.
|
|
102
|
+
|
|
103
|
+
## Table execution model
|
|
104
|
+
|
|
105
|
+
Each table owns:
|
|
106
|
+
|
|
107
|
+
- a `Map<string, T>` of records;
|
|
108
|
+
- equality maps for exact lookups;
|
|
109
|
+
- sorted entries for range/order queries;
|
|
110
|
+
- composite maps keyed by field tuples;
|
|
111
|
+
- prefix maps for string-prefix lookups;
|
|
112
|
+
- subscriptions and TTL timers;
|
|
113
|
+
- the query, aggregation, and fluent-builder helpers.
|
|
114
|
+
|
|
115
|
+
The query planner reports the selected `scanStrategy`, matched index, candidate
|
|
116
|
+
count, match count, and elapsed microseconds through `explain()`. A query can
|
|
117
|
+
fall back to `FULL_TABLE_SCAN`; creating an index is an optimization, not a
|
|
118
|
+
semantic requirement.
|
|
119
|
+
|
|
120
|
+
## CAS model
|
|
121
|
+
|
|
122
|
+
The CAS service hashes raw content before storage. Blobs are sharded by the
|
|
123
|
+
first two hash characters and stored with a small format marker:
|
|
124
|
+
|
|
125
|
+
- `BR_RAW\0` for uncompressed bytes;
|
|
126
|
+
- `BR_BRZ\0` for Brotli-compressed bytes when the compressed representation
|
|
127
|
+
meets the savings threshold.
|
|
128
|
+
|
|
129
|
+
Reads decompress when needed, recompute the raw SHA-256, and quarantine a blob
|
|
130
|
+
when decompression or hash verification fails. `BroccoliDatabaseKernel.gc()`
|
|
131
|
+
collects hashes referenced by string fields beginning with `CAS:` in current
|
|
132
|
+
table records.
|
|
133
|
+
|
|
134
|
+
## Failure model
|
|
135
|
+
|
|
136
|
+
| Failure | Behavior | Caller action |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| Missing workspace directory | Created on `start()` | Normal startup |
|
|
139
|
+
| Missing checkpoint | Treated as fresh state | Continue or restore a backup |
|
|
140
|
+
| Invalid WAL JSON/checksum | `WalIntegrityError` during replay | Preserve the directory, inspect/restore, then retry |
|
|
141
|
+
| Unreadable CAS blob | `null` for missing blob; integrity error for corruption | Inspect quarantine manifest and restore if needed |
|
|
142
|
+
| Non-writable state directory | Health status is degraded/corrupted | Fix permissions or choose another root |
|
|
143
|
+
| Mutex wait exceeds timeout | `DeadlockTimeoutError` | Find long-held/nested lock or reduce contention |
|
|
144
|
+
| Process termination before flush | Recent buffered frames may be absent | Use explicit transaction/flush boundaries |
|
|
145
|
+
|
|
146
|
+
## Implementation map
|
|
147
|
+
|
|
148
|
+
| Concern | File |
|
|
149
|
+
|---|---|
|
|
150
|
+
| Export surface | `src/index.ts` |
|
|
151
|
+
| Types and public contracts | `src/broccolidb.contracts.ts` |
|
|
152
|
+
| Kernel lifecycle and recovery | `src/broccolidb-kernel.ts` |
|
|
153
|
+
| Table/index/query behavior | `src/broccolidb-table.ts` |
|
|
154
|
+
| Aggregation | `src/broccolidb-aggregation.ts` |
|
|
155
|
+
| Natural-language parser | `src/broccolidb-natural-query.ts` |
|
|
156
|
+
| WAL | `src/broccolidb-wal.ts` |
|
|
157
|
+
| CAS | `src/broccolidb-cas.ts` |
|
|
158
|
+
| Mutex | `src/broccolidb-mutex.ts` |
|
|
159
|
+
| Prompt compression | `src/TokenCompressionService.ts` |
|
|
160
|
+
| Contract tests | `test/` |
|
package/docs/BRIEF.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# BroccoliDB brief
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
|
|
5
|
+
Applications often need a small, local state substrate with indexed records,
|
|
6
|
+
durability, recovery, and predictable behavior. A native SQLite binding can be
|
|
7
|
+
excellent, but it introduces platform-specific binaries, rebuilds, ABI coupling,
|
|
8
|
+
and dependency installation failure modes that are disproportionate for an
|
|
9
|
+
embedded table workload.
|
|
10
|
+
|
|
11
|
+
## Solution
|
|
12
|
+
|
|
13
|
+
BroccoliDB keeps the hot path in ordinary JavaScript/TypeScript memory and adds
|
|
14
|
+
explicit filesystem layers only where durability needs them:
|
|
15
|
+
|
|
16
|
+
1. **Reactive tables** hold typed records and maintain secondary indexes.
|
|
17
|
+
2. **WAL** appends mutation frames and replays them after a restart.
|
|
18
|
+
3. **Checkpoints** write a complete JSON base snapshot and rotate the WAL.
|
|
19
|
+
4. **CAS** stores large or reusable byte payloads by SHA-256 content address.
|
|
20
|
+
5. **Mutex and transactions** serialize related async mutations in one process.
|
|
21
|
+
|
|
22
|
+
## Guarantees
|
|
23
|
+
|
|
24
|
+
- The package has no production dependencies or native database driver.
|
|
25
|
+
- Reads and index lookups use in-memory table state.
|
|
26
|
+
- `flush()` writes buffered WAL frames; `stop()` flushes before returning.
|
|
27
|
+
- `start()` loads a checkpoint and replays readable WAL frames.
|
|
28
|
+
- Checkpoint files are written through a temporary file and rename.
|
|
29
|
+
- CAS reads verify the content hash and quarantine corrupted payloads.
|
|
30
|
+
- Query, aggregation, natural-query, and prompt-compression helpers are
|
|
31
|
+
deterministic and offline.
|
|
32
|
+
|
|
33
|
+
These guarantees are deliberately narrower than a replicated database. See
|
|
34
|
+
[Operations](OPERATIONS.md) for durability boundaries and process limitations.
|
|
35
|
+
|
|
36
|
+
## Good fit
|
|
37
|
+
|
|
38
|
+
- Extension or CLI-local state
|
|
39
|
+
- Agent/session tables
|
|
40
|
+
- Durable caches and indexes scoped to one workspace
|
|
41
|
+
- Embedded tools that need rollback checkpoints
|
|
42
|
+
- Portable test fixtures and hermetic local storage
|
|
43
|
+
|
|
44
|
+
## Not a fit
|
|
45
|
+
|
|
46
|
+
- Multiple writers across independent processes without external coordination
|
|
47
|
+
- Cross-machine replication or consensus
|
|
48
|
+
- SQL dialect compatibility or migrations from arbitrary relational schemas
|
|
49
|
+
- Provider billing/token accounting
|
|
50
|
+
- Large analytical datasets that do not fit comfortably in memory
|
|
51
|
+
|
|
52
|
+
## Decision in one sentence
|
|
53
|
+
|
|
54
|
+
Use a small, inspectable, dependency-free table kernel when portability and
|
|
55
|
+
application-owned durability matter more than SQL compatibility or distributed
|
|
56
|
+
database features.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
BroccoliDB is intentionally small and portable. Contributions should preserve
|
|
4
|
+
that boundary and make behavior easier to inspect, test, and transfer.
|
|
5
|
+
|
|
6
|
+
## Source map
|
|
7
|
+
|
|
8
|
+
| Area | Location |
|
|
9
|
+
|---|---|
|
|
10
|
+
| Public exports | `src/index.ts` |
|
|
11
|
+
| Public contracts | `src/broccolidb.contracts.ts` |
|
|
12
|
+
| Kernel lifecycle | `src/broccolidb-kernel.ts` |
|
|
13
|
+
| Tables and indexes | `src/broccolidb-table.ts` |
|
|
14
|
+
| WAL | `src/broccolidb-wal.ts` |
|
|
15
|
+
| CAS | `src/broccolidb-cas.ts` |
|
|
16
|
+
| Queries and aggregation | `src/broccolidb-natural-query.ts`, `src/broccolidb-aggregation.ts` |
|
|
17
|
+
| Locking | `src/broccolidb-mutex.ts` |
|
|
18
|
+
| Prompt compression | `src/TokenCompressionService.ts` |
|
|
19
|
+
| Tests | `test/` |
|
|
20
|
+
| Documentation | `README.md`, `docs/`, `docs/adr/` |
|
|
21
|
+
|
|
22
|
+
## Development workflow
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install
|
|
26
|
+
npm run build
|
|
27
|
+
npm test
|
|
28
|
+
npm run docs:check
|
|
29
|
+
npm pack --dry-run
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Run the smallest relevant test while iterating, then run `npm run check` before
|
|
33
|
+
handoff. Tests should use temporary workspace roots and always clean them in a
|
|
34
|
+
`finally` block.
|
|
35
|
+
|
|
36
|
+
## Contract rules
|
|
37
|
+
|
|
38
|
+
1. Keep the runtime dependency list empty unless a dependency is unavoidable,
|
|
39
|
+
justified in an ADR, and compatible with Node.js `>=18`.
|
|
40
|
+
2. Preserve ESM-compatible relative imports and the public package export.
|
|
41
|
+
3. Treat WAL, checkpoint, CAS, and serialized contract changes as compatibility
|
|
42
|
+
changes even when TypeScript still compiles.
|
|
43
|
+
4. Add tests for startup/replay, checkpoint/rollback, integrity failures, and
|
|
44
|
+
query/index behavior when changing those areas.
|
|
45
|
+
5. Keep `transaction()` callbacks deterministic and avoid network or long-lived
|
|
46
|
+
external work while holding the kernel mutex.
|
|
47
|
+
6. Do not introduce an SQL or native-driver compatibility layer into the core.
|
|
48
|
+
|
|
49
|
+
## Documentation rules
|
|
50
|
+
|
|
51
|
+
Update the relevant layer whenever behavior changes:
|
|
52
|
+
|
|
53
|
+
| Change | Required documentation |
|
|
54
|
+
|---|---|
|
|
55
|
+
| Export or signature | [API reference](API.md) and release notes |
|
|
56
|
+
| Lifecycle, durability, or file format | [Architecture](ARCHITECTURE.md), [Operations](OPERATIONS.md), and an ADR |
|
|
57
|
+
| Error or recovery behavior | [Troubleshooting](TROUBLESHOOTING.md) and operations guide |
|
|
58
|
+
| Terminology | [Glossary](GLOSSARY.md) and affected docs |
|
|
59
|
+
| Supported package/version policy | `README.md` and [release notes](RELEASE_NOTES.md) |
|
|
60
|
+
|
|
61
|
+
Examples must import from `@noorm/broccolidb` and show lifecycle ownership.
|
|
62
|
+
Avoid claims such as “transactional” or “durable” without naming the exact
|
|
63
|
+
boundary (`flush`, `transaction`, `checkpoint`, or `stop`).
|
|
64
|
+
|
|
65
|
+
## Pull request checklist
|
|
66
|
+
|
|
67
|
+
- [ ] The public export surface is intentional.
|
|
68
|
+
- [ ] Runtime dependencies remain portable and native-free.
|
|
69
|
+
- [ ] TypeScript build passes.
|
|
70
|
+
- [ ] Relevant tests pass, including restart/recovery tests where applicable.
|
|
71
|
+
- [ ] `npm run docs:check` passes.
|
|
72
|
+
- [ ] README/API/operations/ADR/release notes are updated as required.
|
|
73
|
+
- [ ] No generated `dist/` or `.broccolidb/` runtime state was added accidentally.
|
|
74
|
+
- [ ] `npm pack --dry-run` contains the intended docs and runtime files.
|
|
75
|
+
|
|
76
|
+
## Release checklist
|
|
77
|
+
|
|
78
|
+
1. Update `version` in `package.json` and the lockfile.
|
|
79
|
+
2. Add a release-note entry describing API and persistence compatibility.
|
|
80
|
+
3. Run `npm run check` and `npm pack --dry-run`.
|
|
81
|
+
4. Inspect the tarball file list for `dist`, `README.md`, `docs`, and `LICENSE`.
|
|
82
|
+
5. Verify a clean consumer can import the compiled package without dev tools.
|
package/docs/GLOSSARY.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Glossary
|
|
2
|
+
|
|
3
|
+
| Term | Meaning |
|
|
4
|
+
|---|---|
|
|
5
|
+
| **Table** | An in-memory collection of records keyed by string IDs and optionally backed by WAL frames. |
|
|
6
|
+
| **Record** | A JSON-compatible application object stored under a table ID. |
|
|
7
|
+
| **Index** | A secondary lookup structure maintained by a table for equality, sorted, composite, or prefix queries. |
|
|
8
|
+
| **WAL** | Write-ahead log: append-only JSONL mutation frames used for durability and restart replay. |
|
|
9
|
+
| **WAL frame** | One `INSERT`, `UPDATE`, `DELETE`, `CLEAR`, `CHECKPOINT`, `ROLLBACK`, or `BRANCH_MERGE` operation record with checksum metadata. |
|
|
10
|
+
| **Flush** | Writing buffered WAL frames to `.broccolidb/wal.log`. |
|
|
11
|
+
| **Checkpoint** | A complete JSON table snapshot plus a named history record that permits faster restart and rollback. |
|
|
12
|
+
| **Rollback** | Restoring table state from a checkpoint held in memory or loaded from checkpoint history on disk. |
|
|
13
|
+
| **CAS** | Content-addressable storage: blobs are named by the SHA-256 hash of their raw content. |
|
|
14
|
+
| **Shard** | The two-character directory prefix used to spread CAS blob files across directories. |
|
|
15
|
+
| **Quarantine** | Moving a corrupted CAS blob into `.broccolidb/cas/corrupt/` and recording an audit entry. |
|
|
16
|
+
| **CDC** | Change-data capture: table subscriptions receive insert, update, delete, clear, and expiration events. |
|
|
17
|
+
| **TTL** | Optional time-to-live on a write; expired records emit an `EXPIRE` change event. |
|
|
18
|
+
| **Natural query** | An offline parser that turns constrained human-readable text into `DbQueryOptions`; it is not an LLM. |
|
|
19
|
+
| **Fluent query** | A chainable `select().where(...).orderBy(...).execute()` builder over one table. |
|
|
20
|
+
| **Transaction** | A kernel mutex scope that runs an async callback and flushes the WAL when the callback completes. |
|
|
21
|
+
| **Health report** | A diagnostic snapshot covering directory writability, CAS metrics, WAL metrics, and table counts. |
|
|
22
|
+
| **Workspace root** | The caller-selected filesystem root below which `.broccolidb/` is created. |
|
|
23
|
+
| **Runtime dependency** | A package required by consumers at runtime. BroccoliDB has none; TypeScript, `tsx`, and Node types are development dependencies. |
|