@orkestrel/scaffold 0.0.66 → 0.0.68

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/bin/main.js +67 -44
  2. package/dist/bin/main.js.map +1 -1
  3. package/dist/host/agents/templates/brief.md +9 -0
  4. package/dist/host/claude/agents/orkestrel.md +8 -8
  5. package/dist/host/claude/rules/names.md +15 -0
  6. package/dist/host/claude/rules/tests.md +33 -4
  7. package/dist/host/claude/rules/workspace.md +14 -2
  8. package/dist/host/dotfiles/prettierignore +3 -0
  9. package/dist/host/guides/README.md +65 -0
  10. package/dist/host/guides/abort.md +169 -0
  11. package/dist/host/guides/agent.md +1509 -0
  12. package/dist/host/guides/brief.md +1266 -0
  13. package/dist/host/guides/browser.md +2200 -0
  14. package/dist/host/guides/budget.md +196 -0
  15. package/dist/host/guides/codec.md +519 -0
  16. package/dist/host/guides/console.md +785 -0
  17. package/dist/host/guides/contract.md +1193 -0
  18. package/dist/host/guides/csv.md +541 -0
  19. package/dist/host/guides/database.md +2518 -0
  20. package/dist/host/guides/emitter.md +233 -0
  21. package/dist/host/guides/form.md +1791 -0
  22. package/dist/host/guides/html.md +717 -0
  23. package/dist/host/guides/indexeddb.md +505 -0
  24. package/dist/host/guides/interpret.md +1029 -0
  25. package/dist/host/guides/lsp.md +515 -0
  26. package/dist/host/guides/markdown.md +964 -0
  27. package/dist/host/guides/mcp.md +5554 -0
  28. package/dist/host/guides/middleware.md +927 -0
  29. package/dist/host/guides/msg.md +440 -0
  30. package/dist/host/guides/ndjson.md +120 -0
  31. package/dist/host/guides/ollama.md +380 -0
  32. package/dist/host/guides/pool.md +280 -0
  33. package/dist/host/guides/probe.md +1210 -0
  34. package/dist/host/guides/process.md +1620 -0
  35. package/dist/host/guides/program.md +1110 -0
  36. package/dist/host/guides/qualifier.md +854 -0
  37. package/dist/host/guides/queue.md +370 -0
  38. package/dist/host/guides/rater.md +330 -0
  39. package/dist/host/guides/reason.md +1122 -0
  40. package/dist/host/guides/relation.md +373 -0
  41. package/dist/host/guides/router.md +753 -0
  42. package/dist/host/guides/scaffold.md +192 -31
  43. package/dist/host/guides/sea.md +383 -0
  44. package/dist/host/guides/server.md +752 -0
  45. package/dist/host/guides/sqlite.md +330 -0
  46. package/dist/host/guides/sse.md +187 -0
  47. package/dist/host/guides/supervisor.md +4890 -0
  48. package/dist/host/guides/table.md +1556 -0
  49. package/dist/host/guides/template.md +280 -0
  50. package/dist/host/guides/terminal.md +1145 -0
  51. package/dist/host/guides/test.md +2969 -0
  52. package/dist/host/guides/timeout.md +252 -0
  53. package/dist/host/guides/tool.md +311 -0
  54. package/dist/host/guides/toolbox.md +1038 -0
  55. package/dist/host/guides/websocket.md +282 -0
  56. package/dist/host/guides/worker.md +615 -0
  57. package/dist/host/guides/workflow.md +1507 -0
  58. package/dist/host/guides/workspace.md +595 -0
  59. package/dist/host/manifest.json +1218 -10
  60. package/dist/host/tests/policy.test.ts +279 -2
  61. package/dist/host/tests/setupPolicy.ts +437 -6
  62. package/dist/src/core/index.cjs +44 -22
  63. package/dist/src/core/index.cjs.map +1 -1
  64. package/dist/src/core/index.d.cts +33 -9
  65. package/dist/src/core/index.d.ts +33 -9
  66. package/dist/src/core/index.js +43 -23
  67. package/dist/src/core/index.js.map +1 -1
  68. package/dist/src/server/index.cjs +1750 -1567
  69. package/dist/src/server/index.cjs.map +1 -1
  70. package/dist/src/server/index.d.cts +106 -24
  71. package/dist/src/server/index.d.ts +106 -24
  72. package/dist/src/server/index.js +1751 -1570
  73. package/dist/src/server/index.js.map +1 -1
  74. package/package.json +9 -9
@@ -0,0 +1,330 @@
1
+ # SQLite
2
+
3
+ > A lean, typed, synchronous wrapper over Node's built-in `node:sqlite` — a thin skin on
4
+ > `DatabaseSync` / `StatementSync` that exposes prepared statements, transactions, and pragmas,
5
+ > with one runtime dependency, `@orkestrel/contract`, for boundary narrowing.
6
+
7
+ The wrapper is the raw native handle rather than an ORM: it carries no query, filter, sort, or
8
+ aggregate builder, so a caller reaching for typed querying builds that layer on top.
9
+ `@orkestrel/database`'s SQLite driver is that layer, adapting these synchronous calls to its own
10
+ asynchronous driver contract. Source: [`src/server`](../src/server). Surfaced through the
11
+ `@src/server` barrel. Requires Node.js ^22.18 || >=24.4 for
12
+ [`node:sqlite`](https://nodejs.org/api/sqlite.html) (the releases carrying the `timeout`,
13
+ `isTransaction`, and `readBigInts` options and `StatementSync.iterate`).
14
+
15
+ ## Surface
16
+
17
+ Creates the database, connects, creates a table, then inserts and queries a row:
18
+
19
+ ```ts
20
+ import { createSQLiteDatabase } from '@orkestrel/sqlite'
21
+
22
+ const db = createSQLiteDatabase({ path: ':memory:' }) // omit `path` for the same in-memory default
23
+ db.connect() // open the handle (lazy + idempotent); calls before this throw a CLOSED SQLiteError
24
+ db.execute('CREATE TABLE users (id TEXT PRIMARY KEY, name TEXT, age INTEGER)')
25
+
26
+ // `readonly`, `timeout`, and `foreignKeys` thread straight to node:sqlite's native
27
+ // options; the database itself also implements `[Symbol.dispose]` (same as
28
+ // `close`), so `using db = createSQLiteDatabase()` releases it automatically.
29
+
30
+ db.prepare('INSERT INTO users VALUES (?, ?, ?)').execute(['u1', 'Ada', 36]) // → { changes: 1, rowid: 1 }
31
+ db.prepare('SELECT name FROM users WHERE age >= ?').all([18]) // → [{ name: 'Ada' }] — every adult
32
+ ```
33
+
34
+ ### Factories
35
+
36
+ | API | Kind | Summary |
37
+ | ---------------------- | -------- | -------------------------------------------------------------------------------------------- |
38
+ | `createSQLiteDatabase` | function | Creates a synchronous SQLite database over `node:sqlite`, defaulting its path to `:memory:`. |
39
+
40
+ ### Classes
41
+
42
+ | API | Kind | Summary |
43
+ | ----------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
44
+ | `SQLiteDatabase` | class | Implements `SQLiteDatabaseInterface` over a lazily opened `DatabaseSync` the instance owns, gating every operation on that connection and mapping each native fault to a `SQLiteError`. |
45
+ | `SQLiteStatement` | class | Implements `SQLiteStatementInterface` over one compiled `StatementSync`, gating each call on its owning connection still being open and mapping every native fault, a mid-stream one included, to a `SQLiteError`. |
46
+
47
+ ### Constants
48
+
49
+ A `Shape` cell holds the constant's declared type.
50
+
51
+ | API | Kind | Shape | Summary |
52
+ | ------------------- | ----- | -------- | -------------------------------------------------------------------------------- |
53
+ | `SQLITE_CONSTRAINT` | const | `number` | Names the SQLite result code whose low byte, `19`, flags a constraint violation. |
54
+ | `SQLITE_BUSY` | const | `number` | Names the SQLite result code whose low byte, `5`, flags a locked-database fault. |
55
+
56
+ ### Helpers and errors
57
+
58
+ | API | Kind | Summary |
59
+ | ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
60
+ | `wrapError` | function | Converts a thrown native `node:sqlite` error into a typed `SQLiteError` at the wrapper's one boundary. |
61
+ | `bindParameters` | function | Normalizes `SQLiteParameters` to the binding shape a native `StatementSync` call expects — a positional spread, or a single named record. |
62
+ | `SQLiteError` | class | Represents an error thrown by the SQLite wrapper, carrying a machine-readable `code` — `CLOSED`, `CONSTRAINT`, `BUSY`, `INVALID`, or `UNKNOWN`. |
63
+ | `isSQLiteError` | function | Checks whether a value is a `SQLiteError`. |
64
+
65
+ ### Types
66
+
67
+ A `Shape` cell holds an interface's data members as bare names in braces, `?` marking an optional member and `plus` introducing its call-signature members, and a type alias's own type literal with a union's arms escaped as `\|`.
68
+
69
+ | API | Kind | Shape | Summary |
70
+ | -------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
71
+ | `SQLiteValue` | type | `null \| number \| bigint \| string \| Uint8Array` | Represents a value SQLite stores and returns natively — the bridge between SQLite's storage classes and their JS types. |
72
+ | `SQLiteRow` | type | `Record<string, SQLiteValue>` | Represents a result row — a record of column name to `SQLiteValue`. |
73
+ | `SQLiteParameters` | type | `readonly SQLiteValue[] \| Readonly<Record<string, SQLiteValue>>` | Represents the bind parameters for a prepared statement — positional (an array, bound to `?`) or named (a record, bound to bare `:name` placeholders). |
74
+ | `SQLiteBinding` | type | `{ positional } \| { named }` | Represents the normalized binding shape a native `StatementSync` call expects — what `SQLiteParameters` become on the way into `node:sqlite`. |
75
+ | `SQLiteExecuteResult` | interface | `{ changes, rowid }` | Represents the outcome of a non-query statement (`INSERT` / `UPDATE` / `DELETE` / DDL). |
76
+ | `SQLiteErrorCode` | type | `'CLOSED' \| 'CONSTRAINT' \| 'BUSY' \| 'INVALID' \| 'UNKNOWN'` | Represents a machine-readable `SQLiteError` code. |
77
+ | `SQLiteDatabaseOptions` | interface | `{ path?, readonly?, timeout?, foreignKeys?, bigints? }` | Represents the options for opening a SQLite connection, accepted by the `createSQLiteDatabase` function and the `SQLiteDatabase` constructor. |
78
+ | `SQLiteStatementInterface` | interface | `{} plus execute, get, all, iterate` | Represents a prepared statement — the only way the wrapper runs SQL. |
79
+ | `SQLiteDatabaseInterface` | interface | `{ path, connected, transacting } plus connect, close, execute, prepare, transact, begin, commit, rollback, pragma, [Symbol.dispose]` | Represents the contract a synchronous SQLite database fulfills over `node:sqlite`'s `DatabaseSync` — prepared statements, transactions, and pragmas, every call returning a plain value rather than a `Promise`. |
80
+
81
+ The `path`, `connected`, and `transacting` members of `SQLiteDatabaseInterface` are `readonly` data members (the preceding Surface row) — its call-signature methods are documented under [Methods](#methods).
82
+
83
+ Row values arrive as the native `SQLiteValue` types and are handed back as-is — the precise per-row shape is imposed one layer up, by `@orkestrel/database`'s SQLite driver, through a contract, never re-narrowed here. Keys and columns are plain SQL: this layer moves `SQLiteValue`s in and out, and typing each row is the job of the layer above.
84
+
85
+ ## Methods
86
+
87
+ Each class implements its interface exactly — no extra public method. Every one of these calls is **synchronous** and returns a plain value, never a `Promise`.
88
+
89
+ The `transacting` member of `SQLiteDatabaseInterface` reports whether a transaction is open on this connection (node:sqlite's `isTransaction`, wrapping `sqlite3_get_autocommit()`), and reads `false` when not connected. `transact(scope)` sets it for the scope's duration; `begin()` / `commit()` / `rollback()` set and clear it identically, because `transact` is itself built on those same primitives. `transact(scope)` remains the right tool whenever the whole transaction fits in one synchronous scope; `begin` / `commit` / `rollback` exist for a long-lived or externally-driven transaction that spans async caller code and so cannot be expressed as a single synchronous scope.
90
+
91
+ `SQLiteDatabaseInterface` also declares `[Symbol.dispose](): void` — a symbol-keyed member, so it is documented here in prose rather than as a `Methods` table row (the guide-parity tooling keys method rows by identifier name). It closes the connection exactly like `close`, letting `using db = createSQLiteDatabase(...)` release it deterministically at the end of a block.
92
+
93
+ #### `SQLiteDatabaseInterface`
94
+
95
+ | Method | Returns | Summary |
96
+ | ---------- | -------------------------- | ----------------------------------------------------------------------------------------------------------- |
97
+ | `connect` | `void` | Opens the underlying connection — lazy and idempotent, so a second call is a no-op. |
98
+ | `close` | `void` | Releases the connection; afterward every operation gates `CLOSED` until reconnect. |
99
+ | `execute` | `void` | Runs one or more result-less SQL statements (DDL, pragmas) in a single call. |
100
+ | `prepare` | `SQLiteStatementInterface` | Compiles SQL into a reusable prepared statement — the only path that runs queries. |
101
+ | `transact` | `R` | Runs `scope` between `BEGIN` and `COMMIT`, rolling the whole scope back and rethrowing on a throw. |
102
+ | `begin` | `void` | Opens a transaction (`BEGIN`); throws the native fault, a nested `BEGIN` included, as a `SQLiteError`. |
103
+ | `commit` | `void` | Commits the open transaction (`COMMIT`); throws the native fault as a `SQLiteError` when none is open. |
104
+ | `rollback` | `void` | Rolls back the open transaction (`ROLLBACK`); throws the native fault as a `SQLiteError` when none is open. |
105
+ | `pragma` | `SQLiteValue \| undefined` | Reads a single PRAGMA, or sets then reads it when a `value` is passed. |
106
+
107
+ #### `SQLiteStatementInterface`
108
+
109
+ | Method | Returns | Summary |
110
+ | --------- | ----------------------------- | ---------------------------------------------------------------------------------------------- |
111
+ | `execute` | `SQLiteExecuteResult` | Runs a non-query (`INSERT` / `UPDATE` / `DELETE` / DDL) and returns its `changes` and `rowid`. |
112
+ | `get` | `SQLiteRow \| undefined` | Runs the statement and returns its first row, or `undefined` when none matched. |
113
+ | `all` | `readonly SQLiteRow[]` | Runs the statement and returns every matching row eagerly, as an array. |
114
+ | `iterate` | `IterableIterator<SQLiteRow>` | Streams the matching rows lazily, one row materialized at a time, for a large result set. |
115
+
116
+ ## Contract
117
+
118
+ These invariants hold across `src/server` ↔ `sqlite.md`:
119
+
120
+ 1. **One barrel, one surface.** Every name a consumer imports comes from `src/server/index.ts`, published as the `.` entry of the `exports` field. `.` is the only code entry; `./package.json` is the manifest.
121
+ 2. **Synchronous.** Every operation runs synchronously, because `node:sqlite` does — no Promises. `@orkestrel/database`'s SQLite driver adapts it to that package's asynchronous driver contract, one layer up.
122
+ 3. **Native, not a second query engine.** The wrapper exposes only what `node:sqlite` offers natively — prepared statements, transactions, and pragmas. It has **no** `where` / `filter` / `order` / aggregate builder; that is the core database engine over `scan`, the same discipline as the IndexedDB wrapper.
123
+ 4. **`SQLiteValue` values, plain SQL.** Reads return `SQLiteRow`s of native `SQLiteValue`s; writes bind `SQLiteValue`s. Per-row typing belongs above this layer, in the core database's contracts.
124
+ 5. **Native faults become `SQLiteError`.** Every native `node:sqlite` throw is mapped at the boundary to a `SQLiteError` carrying a machine-readable `code` — a constraint violation (a UNIQUE / PRIMARY KEY conflict) maps to `'CONSTRAINT'`, a locked-database fault to `'BUSY'`, a wrapper-lifecycle fault to `'CLOSED'`, the wrapper's own invalid-argument refusal to `'INVALID'`, and every unclassified native fault to `'UNKNOWN'`. This holds for `iterate` too: its lazy native iterator is stepped inside its own try/catch, so a fault surfacing mid-stream (for example an out-of-range integer on a later row) maps to a `SQLiteError` exactly like an eager fault, never escaping raw from the caller's `for...of`. Finalizing is the exception to that mapping: on every exit after the first step the wrapper finalizes the native iterator, and a fault from that finalize call is discarded rather than mapped, so leaving the loop never throws. Narrow a caught value with `isSQLiteError`.
125
+ 6. **`CLOSED` before connect, and after close for any statement too.** The database connects lazily; an operation before `connect` (or after `close`) throws a `CLOSED` `SQLiteError`. `connect` is idempotent. A `SQLiteStatementInterface` retains a liveness check to its owning connection: after that connection is closed, every one of the statement's methods (`execute` / `get` / `all` / `iterate`) also throws `CLOSED` — even a statement prepared before the close and still held by the caller. Reconnecting afterward does not revive it: a statement prepared on the earlier connection stays `CLOSED` permanently; prepare a fresh statement on the new connection.
126
+ 7. **`BUSY` on lock contention — `SQLITE_LOCKED` is not `BUSY`.** A write that finds the database locked by another connection retries for `timeout` milliseconds (default `0` — fail immediately), then throws a `BUSY` `SQLiteError` — retryable, unlike the other codes. A `SQLITE_LOCKED` fault (result code `6`, a same-connection table-lock conflict, distinct from `SQLITE_BUSY`'s cross-connection database lock) is **not** mapped to `'BUSY'` — `wrapError` only recognizes `SQLITE_BUSY`, so a `SQLITE_LOCKED` fault surfaces as `'UNKNOWN'` and is not retryable the same way.
127
+ 8. **`transacting` mirrors native autocommit state.** `db.transacting` is `true` exactly while a transaction is open (inside `transact(scope)`, or between a manual `BEGIN` and its `COMMIT` / `ROLLBACK`) and `false` otherwise, including when disconnected.
128
+ 9. **`transact(scope)` requires a synchronous scope.** If `scope` returns a thenable (an `async` function or a function returning a `Promise`), the transaction is rolled back immediately and a `SQLiteError` with code `'INVALID'` is thrown — an async scope would otherwise return before its awaited work runs, letting `transact` commit prematurely. A transaction that must span async caller code uses `begin()` / `commit()` / `rollback()` directly instead.
129
+
130
+ ## Patterns
131
+
132
+ ### Connect, execute, and round-trip a row
133
+
134
+ Connects, creates a table, inserts a row, and reads it back by id:
135
+
136
+ ```ts
137
+ import { createSQLiteDatabase } from '@orkestrel/sqlite'
138
+
139
+ const db = createSQLiteDatabase() // path defaults to ':memory:'
140
+ db.connect()
141
+ db.execute('CREATE TABLE users (id TEXT PRIMARY KEY, name TEXT, age INTEGER)')
142
+ const result = db.prepare('INSERT INTO users VALUES (?, ?, ?)').execute(['u1', 'Ada', 36])
143
+ result.changes // 1
144
+ db.prepare('SELECT * FROM users WHERE id = ?').get(['u1']) // { id: 'u1', name: 'Ada', age: 36 }
145
+ ```
146
+
147
+ ### Positional and named parameters
148
+
149
+ Binds parameters positionally or by name:
150
+
151
+ ```ts
152
+ // Positional — an array bound to `?` placeholders:
153
+ db.prepare('INSERT INTO users VALUES (?, ?, ?)').execute(['u2', 'Lin', 29])
154
+
155
+ // Named — a record bound to bare `:name` placeholders (no prefix needed in JS):
156
+ db.prepare('INSERT INTO users VALUES (:id, :name, :age)').execute({
157
+ id: 'u3',
158
+ name: 'Max',
159
+ age: 41,
160
+ })
161
+ ```
162
+
163
+ ### Reading: get, all, iterate
164
+
165
+ Reads a single row, every row, or a lazy stream of rows:
166
+
167
+ ```ts
168
+ db.prepare('SELECT name FROM users WHERE id = ?').get(['u1']) // first row or undefined
169
+ db.prepare('SELECT * FROM users ORDER BY age').all() // every row
170
+ for (const row of db.prepare('SELECT id FROM users').iterate()) handle(row) // lazy stream
171
+ ```
172
+
173
+ ### Atomic transactions
174
+
175
+ Runs several statements inside one committed-or-rolled-back scope:
176
+
177
+ ```ts
178
+ db.transact(() => {
179
+ db.prepare('INSERT INTO users VALUES (?, ?, ?)').execute(['u4', 'Sam', 22])
180
+ db.prepare('UPDATE users SET age = age + 1 WHERE id = ?').execute(['u1'])
181
+ }) // commits together; a throw rolls the whole scope back and rethrows
182
+ ```
183
+
184
+ ### Long-lived transactions with begin / commit / rollback
185
+
186
+ Opens, holds across awaited caller code, then commits or rolls back a transaction with the primitives directly:
187
+
188
+ ```ts
189
+ // A transaction that must span async caller code (a request handle held open
190
+ // across awaits) can't fit in one synchronous transact(scope) — use the
191
+ // primitives directly instead.
192
+ db.begin()
193
+ try {
194
+ db.prepare('INSERT INTO users VALUES (?, ?, ?)').execute(['u5', 'Kai', 19])
195
+ await doSomethingAsync() // caller-driven work between begin and commit
196
+ db.commit()
197
+ } catch (error) {
198
+ db.rollback()
199
+ throw error
200
+ }
201
+
202
+ // Branch on `transacting` instead of catching a nested-BEGIN fault:
203
+ if (!db.transacting) db.begin()
204
+ ```
205
+
206
+ ### Branching on a typed fault
207
+
208
+ Catches a native fault and branches on its `code`:
209
+
210
+ ```ts
211
+ import { createSQLiteDatabase, isSQLiteError } from '@orkestrel/sqlite'
212
+
213
+ try {
214
+ db.prepare('INSERT INTO users VALUES (?, ?, ?)').execute(['u1', 'Dup', 30]) // 'u1' already exists
215
+ } catch (error) {
216
+ if (isSQLiteError(error) && error.code === 'CONSTRAINT') {
217
+ // a UNIQUE / PRIMARY KEY conflict — distinguished by `code`, not a parsed message
218
+ }
219
+ }
220
+ ```
221
+
222
+ ### Pragmas
223
+
224
+ Reads a PRAGMA, then sets and reads it:
225
+
226
+ ```ts
227
+ db.pragma('user_version') // read → 0
228
+ db.pragma('user_version', 7) // set then read → 7 (a cheap on-disk schema-version counter)
229
+ db.pragma('journal_mode', 'WAL') // set then read → 'wal' — durable write-ahead logging for a file db
230
+ ```
231
+
232
+ ### Closing a connection
233
+
234
+ Closes the connection and reads `connected` afterward:
235
+
236
+ ```ts
237
+ db.close() // releases the connection; every operation gates CLOSED until reconnect
238
+ db.connected // false
239
+ ```
240
+
241
+ ### Production options: readonly, timeout, foreignKeys
242
+
243
+ Opens a connection with `readonly`, `timeout`, `foreignKeys`, and `bigints`:
244
+
245
+ ```ts
246
+ // Open an existing file read-only — a write throws (the file must already exist):
247
+ const reader = createSQLiteDatabase({ path: '/data/app.db', readonly: true })
248
+
249
+ // A busy timeout retries a locked database before failing BUSY:
250
+ const writer = createSQLiteDatabase({ path: '/data/app.db', timeout: 2000 })
251
+
252
+ // Foreign-key enforcement (node:sqlite defaults this to true when omitted):
253
+ const enforced = createSQLiteDatabase({ foreignKeys: true })
254
+
255
+ // Writes always accept a bigint; a stored integer beyond Number.MAX_SAFE_INTEGER
256
+ // throws on read unless `bigints` is enabled — enabling it returns every integer
257
+ // column as bigint, not the out-of-range ones alone:
258
+ const exact = createSQLiteDatabase({ bigints: true })
259
+ ```
260
+
261
+ ### Disposing with `using`
262
+
263
+ Releases the connection automatically at the end of a `using` block:
264
+
265
+ ```ts
266
+ {
267
+ using db = createSQLiteDatabase()
268
+ db.connect()
269
+ db.execute('CREATE TABLE t (id INTEGER)')
270
+ } // db.close() runs automatically at the end of the block
271
+ ```
272
+
273
+ ### Retrying on BUSY
274
+
275
+ Catches a `BUSY` fault from a locked database to retry:
276
+
277
+ ```ts
278
+ import { isSQLiteError } from '@orkestrel/sqlite'
279
+
280
+ try {
281
+ db.prepare('INSERT INTO t VALUES (?)').execute([1]) // another connection holds the lock
282
+ } catch (error) {
283
+ if (isSQLiteError(error) && error.code === 'BUSY') {
284
+ // retryable — back off briefly and retry, or raise the `timeout` option
285
+ }
286
+ }
287
+ ```
288
+
289
+ ### The boundary helpers directly
290
+
291
+ Calls the boundary helpers directly to normalize parameters and wrap a native throw:
292
+
293
+ ```ts
294
+ import { bindParameters, wrapError } from '@orkestrel/sqlite'
295
+
296
+ bindParameters(['u1', 'Ada']) // → { positional: ['u1', 'Ada'] }
297
+ bindParameters({ id: 'u1' }) // → { named: { id: 'u1' } }
298
+
299
+ try {
300
+ db.execute('not sql')
301
+ } catch (error) {
302
+ wrapError(error) // a typed SQLiteError, mapped from the native throw
303
+ }
304
+ ```
305
+
306
+ ### Practices
307
+
308
+ - **Use prepared statements with bound parameters**, never string-interpolated values — binding is the SQL-injection-safe path (pragmas, which can't bind, take trusted internal names only).
309
+ - **Keep a transaction scope synchronous and tight** — the wrapper is synchronous, so a scope is a plain function body that commits on return and rolls back on a throw.
310
+ - **Branch on `error.code`** (through `isSQLiteError`) rather than parsing a message — `'CONSTRAINT'` distinguishes a key conflict from any other fault, and `'INVALID'` distinguishes the wrapper's own refusal from an unclassified native fault.
311
+ - **Retry `'BUSY'`, not the others** — it is the one retryable code; back off briefly (or raise `timeout`) before retrying the same operation.
312
+ - **Prefer `using`** over a manual `try` / `finally close()` when a database's lifetime matches one block scope.
313
+ - **Enable `bigints` when integers may exceed `Number.MAX_SAFE_INTEGER`** — writes already accept `bigint`, but a read of an out-of-range stored integer throws unless `bigints` is set; note the option applies to every integer column, not selectively.
314
+ - **Branch on `transacting` instead of catching a nested-`BEGIN` error** — a consumer composing its own `begin()` (for example a migration step joining an enclosing transaction) checks `db.transacting` first and skips its own `begin()` / `commit()` when one is already open, rather than issuing `begin()` unconditionally and handling the "cannot start a transaction within a transaction" fault.
315
+ - **Use `begin()` / `commit()` / `rollback()` only for a long-lived or externally-driven transaction** that spans async caller code — `transact(scope)` stays the right tool whenever the whole transaction fits in one synchronous scope.
316
+ - **Never pass an `async` scope to `transact(scope)`** — a thenable return is rejected (rolled back, then thrown as `'INVALID'`) rather than silently committing before the awaited work runs; reach for `begin()` / `commit()` / `rollback()` for anything that must `await`.
317
+ - **`'BUSY'` is the only retryable code from lock contention** — a `SQLITE_LOCKED` fault (a same-connection conflict) is not mapped to `'BUSY'` and surfaces as `'UNKNOWN'`, so do not treat every lock-shaped fault as retryable; branch on `error.code === 'BUSY'` specifically.
318
+
319
+ ## Tests
320
+
321
+ - [`tests/guides.test.ts`](../tests/guides.test.ts) — the `## Surface` ↔ `src/server` bijection, the `## Methods` ↔ interface/class method parity, and the equality gate: every `Summary` cell against its declaration's description paragraph, the titled `Connect, execute, and round-trip a row` fence against the `@example` block of that title (pinned so the titled pair cannot be retired silently), and the README pitch against this guide's tagline. It also runs the flagship fences and asserts the values their comments claim.
322
+ - [`tests/src/server/SQLiteDatabase.test.ts`](../tests/src/server/SQLiteDatabase.test.ts) — the database in a real `:memory:` SQLite: connect / close lifecycle, the `CLOSED` gate, execute DDL, prepare round-trip, transact commit and rollback, pragma get + set, and the production options — `readonly` rejecting a write, `foreignKeys` enforcing a real FK violation, `timeout` surfacing `BUSY` from a genuinely locked second connection, and `[Symbol.dispose]` closing inside a `using` block.
323
+ - [`tests/src/server/SQLiteStatement.test.ts`](../tests/src/server/SQLiteStatement.test.ts) — prepared statements: `execute`'s result, positional and named binding, `get` / `all` / `iterate`, an abandoned `iterate` releasing its read lock and finalizing without throwing, and a `CONSTRAINT` violation.
324
+ - [`tests/src/server/helpers.test.ts`](../tests/src/server/helpers.test.ts) — the wrapper's boundary helpers as pure units: `wrapError` mapping a thrown value to a typed `SQLiteError` (real constraint fault → `CONSTRAINT`, real locked-database fault → `BUSY`, non-error → `UNKNOWN`, pass-through) and `bindParameters` normalizing parameters to the native binding shape (array → positional, record → named).
325
+ - [`tests/src/server/factories.test.ts`](../tests/src/server/factories.test.ts) — `createSQLiteDatabase` returns a working `SQLiteDatabaseInterface` and defaults its path to `:memory:`.
326
+
327
+ ## See also
328
+
329
+ - [`AGENTS.md`](../AGENTS.md) — the coding law this package is written against.
330
+ - [`README.md`](README.md) — the guides index.
@@ -0,0 +1,187 @@
1
+ # SSE
2
+
3
+ > A stateful Server-Sent-Events (SSE) stream parser: a handle that turns string chunks into
4
+ > the complete events a blank line has dispatched, buffering a partial line or in-progress
5
+ > event until the rest arrives and persisting the sticky `id` / `retry` connection state.
6
+
7
+ SSE is a UTF-8 text stream of events separated by a blank line. Within an event each
8
+ `field: value` line accumulates onto an in-progress event — multiple `data:` lines concatenate
9
+ with `\n`, and `event:` / `id:` / `retry:` are last-wins — and the blank line dispatches the
10
+ accumulated event, but only when its data buffer is non-empty. The `id` and `retry` fields
11
+ follow WHATWG last-event-id semantics: each survives dispatch, is read through the getter of
12
+ its own name, and is dropped only by `clear()`. An optional `limit` bounds the total buffered
13
+ characters, throwing a typed `SSEError('OVERFLOW')` rather than growing unbounded, and
14
+ `flush()` forces out a trailing unterminated event at end-of-stream. A pure functional
15
+ primitive — no Emitter, no server / HTTP / agent coupling; it never throws on malformed input,
16
+ only `SSEError('OVERFLOW')` when a configured `limit` is exceeded.
17
+ Source: [`src/core`](../src/core). Surfaced through the `@src/core` barrel.
18
+
19
+ ## Surface
20
+
21
+ Create a parser and feed it chunks as they arrive; each `parse(chunk)`
22
+ returns the events a blank line has dispatched so far, and an in-progress
23
+ event / trailing partial line is held for the next call:
24
+
25
+ ```ts
26
+ import { createSSEParser } from '@orkestrel/sse'
27
+
28
+ const parser = createSSEParser()
29
+ parser.parse('data: a\ndata: b\n\n') // [{ data: 'a\nb' }] - the two data lines joined
30
+ parser.parse('event: ping\ndata: 1') // [] - the event is buffered until its blank line
31
+ parser.parse('\n\n') // [{ data: '1', event: 'ping' }]
32
+ parser.clear() // drop any buffered partial line / event - ready for a fresh stream
33
+ ```
34
+
35
+ ### Types
36
+
37
+ A `Shape` cell holds an interface's data members as bare names in braces, `?` marking an optional member and `plus` introducing its call-signature members, and a type alias's own type literal with a union's arms escaped as `\|`.
38
+
39
+ | Type | Kind | Shape | Summary |
40
+ | -------------------- | --------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
41
+ | `SSEEvent` | interface | `{ data, event?, id?, retry? }` | Represents one dispatched Server-Sent Event — the value a blank line flushes from an `SSEParserInterface`. Its `data` holds every `data:` field of that event joined by `\n` with no trailing newline, and `event` / `id` / `retry` hold the last field of each name the event carried. |
42
+ | `SSEParserInterface` | interface | `{ id, retry } plus parse, flush, clear` | Represents a stateful Server-Sent-Events (SSE) stream parser: feed it string chunks, get back the complete events dispatched so far. A trailing partial line / in-progress event is buffered until the rest arrives, and the sticky `id` / `retry` getters carry the connection state a dispatch leaves in place. |
43
+ | `SSEParserOptions` | interface | `{ limit? }` | Configures the parser `createSSEParser` builds and the `SSEParser` constructor accepts — `limit` caps the total buffered characters held at once, and leaving it unset keeps the buffering unbounded. |
44
+ | `SSEErrorCode` | type | `'OVERFLOW'` | Names the machine-readable code an `SSEError` carries — `'OVERFLOW'` alone, thrown when a `parse(chunk)` call would push the buffered total over a configured `limit`. |
45
+
46
+ ```ts
47
+ import type { SSEParserOptions } from '@orkestrel/sse'
48
+
49
+ const options: SSEParserOptions = { limit: 1_000_000 }
50
+ ```
51
+
52
+ ### Constants
53
+
54
+ A `Shape` cell holds the constant's declared type.
55
+
56
+ | API | Kind | Shape | Summary |
57
+ | ----- | ----- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
58
+ | `NUL` | const | string | Names the null byte (`U+0000`). The SSE spec voids an `id:` field whose value contains it, so an `id` carrying a NUL is never surfaced. |
59
+ | `BOM` | const | string | Names the byte-order mark (`U+FEFF`), stripped from the first non-empty chunk of an SSE stream (a leading mark on later chunks is ordinary content). |
60
+
61
+ ```ts
62
+ import { BOM, NUL } from '@orkestrel/sse'
63
+
64
+ NUL.charCodeAt(0) // 0
65
+ BOM.charCodeAt(0) // 0xfeff
66
+ ```
67
+
68
+ ### Errors
69
+
70
+ | API | Kind | Summary |
71
+ | ------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
72
+ | `SSEError` | class | Represents an error the SSE parser throws, carrying the machine-readable `SSEErrorCode` a `catch` branches on and an optional `context` of diagnostic detail. |
73
+ | `isSSEError` | function | Narrows an unknown caught value to an `SSEError`. |
74
+
75
+ ```ts
76
+ import { isSSEError, SSEError } from '@orkestrel/sse'
77
+
78
+ try {
79
+ throw new SSEError('OVERFLOW', 'SSE parser buffer would exceed the configured limit', {
80
+ limit: 100,
81
+ size: 150,
82
+ })
83
+ } catch (error) {
84
+ if (isSSEError(error)) error.code // 'OVERFLOW'
85
+ }
86
+ ```
87
+
88
+ ### Factories
89
+
90
+ | API | Kind | Summary |
91
+ | ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
92
+ | `createSSEParser` | function | Creates a Server-Sent-Events (SSE) stream parser — a stateful `SSEParserInterface` handle, backed by `SSEParser`, that turns string chunks into the complete events dispatched so far. |
93
+
94
+ #### Create a bounded parser and feed it chunks
95
+
96
+ The following builds a parser bounded by `limit` and feeds it chunks as they arrive:
97
+
98
+ ```ts
99
+ import { createSSEParser } from '@orkestrel/sse'
100
+
101
+ const parser = createSSEParser({ limit: 1_000_000 })
102
+ parser.parse('data: a\ndata: b\n\n') // [{ data: 'a\nb' }] - the two data lines joined
103
+ parser.parse('event: ping\ndata: 1') // [] - buffered until its blank line
104
+ parser.parse('\n\n') // [{ data: '1', event: 'ping' }]
105
+ ```
106
+
107
+ ### Classes
108
+
109
+ | API | Kind | Summary |
110
+ | ----------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
111
+ | `SSEParser` | class | Implements `SSEParserInterface` over one internal line buffer, reassembling an event split across chunk boundaries once its blank line arrives and holding the sticky `id` / `retry` connection state until `clear()` drops it. |
112
+
113
+ ## Methods
114
+
115
+ The public methods of `SSEParserInterface` — the class's full method surface
116
+ (AGENTS.md, Documentation contract). The `readonly` data members `id` / `retry`
117
+ (sticky connection state) stay off the following method table and are documented
118
+ after it.
119
+
120
+ #### `SSEParserInterface`
121
+
122
+ | Method | Returns | Summary |
123
+ | ------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
124
+ | `parse` | `readonly SSEEvent[]` | Appends `chunk`, then returns every event a blank line has dispatched — each event's `data:` fields concatenated with `\n`, plus the last `event:` / `id:` / `retry:` field it carried. An in-progress event and a trailing partial line are retained for the next call, and a call that would exceed a configured `limit` throws instead, leaving parser state unchanged. |
125
+ | `flush` | `readonly SSEEvent[]` | Treats any remaining buffered partial line as if it had been terminated, then dispatches the in-progress event when its data buffer is non-empty. |
126
+ | `clear` | `void` | Drops any buffered partial line, in-progress event, and persisted `id` / `retry`, leaving the parser ready for a fresh stream. |
127
+
128
+ ```ts
129
+ import { SSEParser } from '@orkestrel/sse'
130
+
131
+ const parser = new SSEParser()
132
+ parser.parse('data: a\ndata: b\n\n') // [{ data: 'a\nb' }] - the two data lines joined
133
+ parser.parse('event: ping\ndata: 1') // [] - the event is buffered until its blank line
134
+ parser.parse('\n\n') // [{ data: '1', event: 'ping' }]
135
+ parser.clear() // drop any buffered partial line / event / persisted id/retry - ready for a fresh stream
136
+ parser.parse('data: fresh\n\n') // [{ data: 'fresh' }]
137
+ ```
138
+
139
+ `flush()` is a convenience beyond the WHATWG algorithm, which discards an
140
+ unterminated final event at end-of-stream — without calling `flush()`, that
141
+ spec-faithful discard is the parser's default behavior:
142
+
143
+ ```ts
144
+ import { SSEParser } from '@orkestrel/sse'
145
+
146
+ const parser = new SSEParser()
147
+ parser.parse('data: incomplete') // [] - no blank line yet, buffered
148
+ parser.flush() // [{ data: 'incomplete' }] - forced out at end-of-stream
149
+ ```
150
+
151
+ `id` / `retry` are sticky connection state (WHATWG last-event-id semantics):
152
+ each valid `id:` / `retry:` field updates them, dispatch does not clear them,
153
+ and only `clear()` does — useful for reconnection (`Last-Event-ID` header):
154
+
155
+ ```ts
156
+ import { SSEParser } from '@orkestrel/sse'
157
+
158
+ const parser = new SSEParser()
159
+ parser.id // undefined - no id: field seen yet
160
+ parser.parse('id: 42\nretry: 3000\ndata: x\n\n') // [{ data: 'x', id: '42', retry: 3000 }]
161
+ parser.id // '42' - persisted, survives dispatch
162
+ parser.retry // 3000 - persisted, survives dispatch
163
+ parser.clear()
164
+ parser.id // undefined - clear() drops sticky state
165
+ ```
166
+
167
+ A configured `limit` throws a typed `SSEError` instead of growing the buffer
168
+ unbounded:
169
+
170
+ ```ts
171
+ import { isSSEError, SSEParser } from '@orkestrel/sse'
172
+
173
+ const parser = new SSEParser({ limit: 10 })
174
+ try {
175
+ parser.parse('x'.repeat(20))
176
+ } catch (error) {
177
+ if (isSSEError(error) && error.code === 'OVERFLOW') parser.clear()
178
+ }
179
+ ```
180
+
181
+ ## Tests
182
+
183
+ - [`tests/guides.test.ts`](../tests/guides.test.ts) — the `## Surface` ↔ `src/core` bijection (value and type exports), the `SSEParserInterface` ↔ `SSEParser` method bijection, and the equality gate: every `Summary` cell against its declaration's description paragraph, the titled `Create a bounded parser and feed it chunks` fence against the `@example` block of that title (pinned so the titled pair cannot be retired silently), and the README pitch against this guide's tagline. It also runs the flagship fences and asserts the values their comments claim.
184
+ - [`tests/src/core/SSEParser.test.ts`](../tests/src/core/SSEParser.test.ts) — the parser against the WHATWG algorithm: dispatch on the blank line, stripping only the first space after a colon, multi-line `data:` concatenation with no trailing newline, every field together with last-wins repeats, comment and unknown-field handling, the empty-data rule that emits no spurious event, cross-chunk reassembly, LF / CRLF / bare-CR terminators including a CRLF split across chunks, first-chunk BOM stripping, integer-only `retry:`, a NUL-voided `id:`, sticky `id` / `retry` surviving dispatch until `clear()` drops them, `clear()` dropping the buffered line and the carriage hold and re-arming BOM stripping, returned arrays never aliased across calls, a configured `limit` throwing `SSEError('OVERFLOW')` with parser state unchanged, unicode and adversarial input, volume runs, and the invariant suites that feed one corpus through every fixed-size and two-way-split chunking and require the whole-string parse back.
185
+ - [`tests/src/core/factories.test.ts`](../tests/src/core/factories.test.ts) — `createSSEParser` returns a working `SSEParserInterface`, hands back handles that share no buffer state, matches `new SSEParser()` over a shared corpus, and threads `limit` through to the same overflow.
186
+ - [`tests/policy.test.ts`](../tests/policy.test.ts) — this repository's own conventions: the scratch containment guard, the mirror register pairing each test with its module, the population controls with their negative controls, the skill family and bridge rules, the rule map, portability, the prose denylist and its currency, and the policy configuration wiring.
187
+ - [`tests/config.test.ts`](../tests/config.test.ts) — the root configuration read from the real config files, the Oxlint policy plugin's rules driven through `RuleTester`, and the configuration helpers.