@orkestrel/sqlite 0.0.1 → 0.0.2

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.
@@ -1,7 +1,316 @@
1
- export type * from './types.js';
2
- export * from './constants.js';
3
- export * from './errors.js';
4
- export * from './helpers.js';
5
- export * from './factories.js';
6
- export * from './SQLiteDatabase.js';
7
- export * from './SQLiteStatement.js';
1
+ import { StatementSync } from 'node:sqlite';
2
+
3
+ /**
4
+ * Normalize {@link SQLiteParameters} to the binding shape a native `StatementSync`
5
+ * call expects.
6
+ *
7
+ * @remarks
8
+ * Positional parameters (an array) bind to `?` placeholders and are spread into
9
+ * the call; named parameters (a record) bind to bare `:name` placeholders and are
10
+ * passed as a single leading object. Returning a discriminated result keeps the
11
+ * `SQLiteStatement` dispatch typed against the native overloads without `as`.
12
+ *
13
+ * @param parameters - The wrapper parameters, or `undefined` for none
14
+ * @returns `{ positional }` for an array (empty when omitted) or `{ named }` for a record
15
+ */
16
+ export declare function bindParameters(parameters?: SQLiteParameters): {
17
+ readonly positional: readonly SQLiteValue[];
18
+ } | {
19
+ readonly named: Readonly<Record<string, SQLiteValue>>;
20
+ };
21
+
22
+ /**
23
+ * Create a synchronous SQLite database over `node:sqlite`.
24
+ *
25
+ * @remarks
26
+ * The wrapper connects lazily — call `connect` (or it is required by the first
27
+ * operation, which throws `CLOSED` until then). This is the lower-level native
28
+ * handle a higher-level typed database engine would be built on; here it ships
29
+ * as the standalone, server-native SQLite surface.
30
+ *
31
+ * @param options - The database `path` (a file path, or `':memory:'` by default)
32
+ * @returns A typed {@link SQLiteDatabaseInterface}
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * import { createSQLiteDatabase } from '@orkestrel/sqlite'
37
+ *
38
+ * const db = createSQLiteDatabase({ path: ':memory:' })
39
+ * db.connect()
40
+ * db.exec('CREATE TABLE users (id TEXT PRIMARY KEY, name TEXT)')
41
+ * db.prepare('INSERT INTO users VALUES (?, ?)').run(['u1', 'Ada'])
42
+ * db.prepare('SELECT name FROM users WHERE id = ?').get(['u1']) // { name: 'Ada' }
43
+ * ```
44
+ */
45
+ export declare function createSQLiteDatabase(options?: SQLiteDatabaseOptions): SQLiteDatabaseInterface;
46
+
47
+ /**
48
+ * Whether a value is a {@link SQLiteError}.
49
+ *
50
+ * @param value - The value to test
51
+ * @returns `true` when `value` is a `SQLiteError`
52
+ */
53
+ export declare function isSQLiteError(value: unknown): value is SQLiteError;
54
+
55
+ /**
56
+ * SQLite result code for a locked-database fault (`SQLITE_BUSY`).
57
+ *
58
+ * @remarks
59
+ * A native `errcode` packs the primary result in its low byte, with extended codes
60
+ * in the high bits, so the low byte is masked off before comparing. Read only by
61
+ * `wrapError`.
62
+ */
63
+ export declare const SQLITE_BUSY = 5;
64
+
65
+ /**
66
+ * SQLite result code for a constraint violation (`SQLITE_CONSTRAINT`).
67
+ *
68
+ * @remarks
69
+ * A native `errcode` packs the primary result in its low byte, with extended codes
70
+ * in the high bits (e.g. `SQLITE_CONSTRAINT_UNIQUE`), so the low byte is masked off
71
+ * before comparing. Read only by `wrapError`.
72
+ */
73
+ export declare const SQLITE_CONSTRAINT = 19;
74
+
75
+ /**
76
+ * A synchronous SQLite database over `node:sqlite`'s `DatabaseSync`.
77
+ *
78
+ * @remarks
79
+ * Created by `createSQLiteDatabase`. It connects lazily (`connect` opens the
80
+ * underlying `DatabaseSync`, idempotent) and every operation routes through a
81
+ * private gate that throws a `CLOSED` `SQLiteError` before `connect` or after
82
+ * `close`. `exec` runs result-less SQL; `prepare` compiles a `SQLiteStatement`;
83
+ * `transaction` wraps a scope in `BEGIN` / `COMMIT`, rolling back on a throw;
84
+ * `begin` / `commit` / `rollback` expose those same primitives directly for a
85
+ * long-lived or externally-driven transaction; `pragma` reads (or sets then
86
+ * reads) one PRAGMA value — `name` is trusted
87
+ * internal use only, never untrusted input, since pragma names cannot be bound
88
+ * as parameters. `transacting` reports whether a transaction is currently open
89
+ * (node:sqlite's `isTransaction`), `false` when not connected. A native fault
90
+ * surfaces as a `SQLiteError` mapped at the boundary.
91
+ */
92
+ export declare class SQLiteDatabase implements SQLiteDatabaseInterface {
93
+ #private;
94
+ constructor(options: SQLiteDatabaseOptions);
95
+ get path(): string;
96
+ get connected(): boolean;
97
+ get transacting(): boolean;
98
+ connect(): void;
99
+ close(): void;
100
+ /** Close the connection — enables `using db = createSQLiteDatabase(...)`. */
101
+ [Symbol.dispose](): void;
102
+ exec(sql: string): void;
103
+ prepare(sql: string): SQLiteStatementInterface;
104
+ transaction<R>(scope: () => R): R;
105
+ begin(): void;
106
+ commit(): void;
107
+ rollback(): void;
108
+ pragma(name: string, value?: string | number): SQLiteValue | undefined;
109
+ }
110
+
111
+ /**
112
+ * A synchronous SQLite database over `node:sqlite`'s `DatabaseSync` — a lean,
113
+ * typed, zero-dependency layer exposing prepared statements, transactions, and
114
+ * pragmas. Synchronous because `node:sqlite` is; the SQLite *driver* (Chunk 3)
115
+ * adapts it to the async `DriverInterface`.
116
+ *
117
+ * @remarks
118
+ * Connects lazily — `connect` opens the underlying `DatabaseSync` (idempotent),
119
+ * and every operation requires an open connection, throwing a `CLOSED`
120
+ * {@link SQLiteError} before `connect` or after `close`. `exec` runs SQL with no
121
+ * results (DDL, pragmas); `prepare` compiles a {@link SQLiteStatementInterface};
122
+ * `transaction` runs a scope between `BEGIN` and `COMMIT`, rolling back on a
123
+ * throw; `pragma` reads (or sets then reads) a single PRAGMA value — `name` is
124
+ * trusted internal use only, never untrusted input, since pragma names cannot
125
+ * be bound as parameters. `transacting` reports whether a transaction is
126
+ * currently open on this connection — node:sqlite's `isTransaction` (wraps
127
+ * `sqlite3_get_autocommit()`); `false` when not connected. `begin` / `commit` /
128
+ * `rollback` are the same `BEGIN` / `COMMIT` / `ROLLBACK` primitives
129
+ * `transaction` composes internally, exposed directly for a long-lived or
130
+ * externally-driven transaction that a single synchronous scope cannot
131
+ * express — `transaction(scope)` remains the right tool whenever the whole
132
+ * transaction fits in one synchronous scope. `[Symbol.dispose]` closes the
133
+ * connection (same as `close`), enabling `using` to release it
134
+ * deterministically at the end of a block.
135
+ */
136
+ export declare interface SQLiteDatabaseInterface {
137
+ readonly path: string;
138
+ readonly connected: boolean;
139
+ readonly transacting: boolean;
140
+ connect(): void;
141
+ close(): void;
142
+ exec(sql: string): void;
143
+ prepare(sql: string): SQLiteStatementInterface;
144
+ transaction<R>(scope: () => R): R;
145
+ /**
146
+ * Open a transaction (`BEGIN`). Throws the native fault — including a
147
+ * nested `BEGIN` while one is already open — as a {@link SQLiteError}; a
148
+ * caller composing its own transaction alongside others should branch on
149
+ * {@link SQLiteDatabaseInterface.transacting} first rather than catch this
150
+ * (see the Practices section in `guides/src/sqlite.md`).
151
+ */
152
+ begin(): void;
153
+ /** Commit the currently open transaction (`COMMIT`); throws the native fault as a {@link SQLiteError} when none is open. */
154
+ commit(): void;
155
+ /** Roll back the currently open transaction (`ROLLBACK`); throws the native fault as a {@link SQLiteError} when none is open. */
156
+ rollback(): void;
157
+ pragma(name: string, value?: string | number): SQLiteValue | undefined;
158
+ [Symbol.dispose](): void;
159
+ }
160
+
161
+ /**
162
+ * Options for `createSQLiteDatabase`.
163
+ *
164
+ * @remarks
165
+ * `path` is the database file path, or the special name `':memory:'` for an
166
+ * in-memory database (the default when omitted). `readonly` opens the
167
+ * connection read-only (native `readOnly`) — an absent file fails to open
168
+ * rather than being created. `timeout` is the busy-timeout in milliseconds
169
+ * (native `timeout`) — how long SQLite retries a locked database before
170
+ * failing with a `BUSY` {@link SQLiteError}; defaults to `0` (fail
171
+ * immediately) when omitted. `foreignKeys` enables foreign-key constraint
172
+ * enforcement (native `enableForeignKeyConstraints`); `node:sqlite` defaults
173
+ * this to `true` when omitted. `bigints` reads `INTEGER` columns back as
174
+ * `bigint` (native `readBigInts`) — writes already accept `bigint` regardless
175
+ * of this option, so a stored integer beyond `Number.MAX_SAFE_INTEGER` throws
176
+ * on read unless `bigints` is enabled; enabling it returns EVERY integer
177
+ * column as `bigint`, not just out-of-range ones, closing that read/write
178
+ * asymmetry at the cost of `bigint` values for ordinary small integers too.
179
+ */
180
+ export declare interface SQLiteDatabaseOptions {
181
+ readonly path?: string;
182
+ readonly readonly?: boolean;
183
+ readonly timeout?: number;
184
+ readonly foreignKeys?: boolean;
185
+ readonly bigints?: boolean;
186
+ }
187
+
188
+ /**
189
+ * An error thrown by the SQLite wrapper.
190
+ *
191
+ * @remarks
192
+ * Carries a {@link SQLiteErrorCode} and an optional `context` record (e.g. the
193
+ * native SQLite `errcode`). Construct it directly for the `CLOSED`
194
+ * wrapper-lifecycle fault; the internal `wrapError` maps a native `node:sqlite`
195
+ * error to the right code at the boundary. Narrow a caught value with
196
+ * {@link isSQLiteError}.
197
+ *
198
+ * @example
199
+ * ```ts
200
+ * try {
201
+ * statement.run({ id: 'u1' })
202
+ * } catch (error) {
203
+ * if (isSQLiteError(error) && error.code === 'CONSTRAINT') {
204
+ * // a UNIQUE / PRIMARY KEY violation
205
+ * }
206
+ * }
207
+ * ```
208
+ */
209
+ export declare class SQLiteError extends Error {
210
+ readonly code: SQLiteErrorCode;
211
+ readonly context?: Readonly<Record<string, unknown>>;
212
+ constructor(code: SQLiteErrorCode, message: string, context?: Readonly<Record<string, unknown>>);
213
+ }
214
+
215
+ /**
216
+ * A machine-readable {@link SQLiteError} code.
217
+ *
218
+ * @remarks
219
+ * `'BUSY'` is retryable — it means a locked database was still held by another
220
+ * connection when the `timeout` (see {@link SQLiteDatabaseOptions}) elapsed; a
221
+ * caller may retry the operation, typically after backing off briefly.
222
+ */
223
+ export declare type SQLiteErrorCode = 'CLOSED' | 'CONSTRAINT' | 'BUSY' | 'UNKNOWN';
224
+
225
+ /**
226
+ * Bind parameters for a prepared statement — positional (an array, bound to `?`)
227
+ * or named (a record, bound to bare `:name` placeholders).
228
+ */
229
+ export declare type SQLiteParameters = readonly SQLiteValue[] | Readonly<Record<string, SQLiteValue>>;
230
+
231
+ /** A result row — a record of column name to {@link SQLiteValue}. */
232
+ export declare type SQLiteRow = Record<string, SQLiteValue>;
233
+
234
+ /** The outcome of a non-query statement (`INSERT` / `UPDATE` / `DELETE` / DDL). */
235
+ export declare interface SQLiteRunResult {
236
+ readonly changes: number;
237
+ readonly rowid: number;
238
+ }
239
+
240
+ /**
241
+ * A prepared statement over `node:sqlite`'s `StatementSync` — the only way the
242
+ * wrapper runs SQL.
243
+ *
244
+ * @remarks
245
+ * Created by `database.prepare(sql)`, which threads a liveness check (internal —
246
+ * this constructor's second parameter is not part of the documented surface).
247
+ * Bare named parameters are enabled on construction so a record's keys bind
248
+ * without the SQL prefix character. Each method first gates on that liveness
249
+ * check, throwing a `CLOSED` `SQLiteError` once the owning connection has been
250
+ * closed — a statement prepared on a connection that is later closed and then
251
+ * reconnected stays `CLOSED`; a fresh statement must be prepared on the new
252
+ * connection. Each method then binds the optional parameters (an array spread
253
+ * to `?` placeholders, a record passed as a single named object) and executes
254
+ * synchronously, mapping any native fault to a `SQLiteError` — including a
255
+ * mid-stream fault from `iterate`'s lazy native iterator, stepped inside its own
256
+ * try/catch so a fault on a later row is mapped exactly like an eager one. Row
257
+ * values arrive as the native {@link SQLiteRow} types; the typed layer above
258
+ * (the driver) imposes a precise shape through a contract rather than
259
+ * re-narrowing here (AGENTS §14).
260
+ */
261
+ export declare class SQLiteStatement implements SQLiteStatementInterface {
262
+ #private;
263
+ constructor(statement: StatementSync, closed: () => boolean);
264
+ run(parameters?: SQLiteParameters): SQLiteRunResult;
265
+ get(parameters?: SQLiteParameters): SQLiteRow | undefined;
266
+ all(parameters?: SQLiteParameters): readonly SQLiteRow[];
267
+ iterate(parameters?: SQLiteParameters): IterableIterator<SQLiteRow>;
268
+ }
269
+
270
+ /**
271
+ * A prepared statement — the only way the wrapper runs SQL (no query DSL; the
272
+ * core database layer owns querying, exactly as the IndexedDB wrapper does).
273
+ *
274
+ * @remarks
275
+ * Reached through `database.prepare(sql)`. Each method binds the optional
276
+ * `parameters` (an array spread to positional `?` placeholders, or a record bound
277
+ * to bare named placeholders) and executes synchronously: `run` for a non-query,
278
+ * `get` for the first row, `all` for every row, `iterate` for a lazy stream. A
279
+ * native fault surfaces as a {@link SQLiteError}.
280
+ */
281
+ export declare interface SQLiteStatementInterface {
282
+ run(parameters?: SQLiteParameters): SQLiteRunResult;
283
+ get(parameters?: SQLiteParameters): SQLiteRow | undefined;
284
+ all(parameters?: SQLiteParameters): readonly SQLiteRow[];
285
+ iterate(parameters?: SQLiteParameters): IterableIterator<SQLiteRow>;
286
+ }
287
+
288
+ /**
289
+ * A value SQLite stores and returns natively — the SQL ↔ JS bridge.
290
+ *
291
+ * @remarks
292
+ * `node:sqlite` maps `NULL` / `INTEGER` / `REAL` / `TEXT` / `BLOB` to exactly
293
+ * these JS types (integers arrive as `number`, or `bigint` only past 2^53).
294
+ */
295
+ export declare type SQLiteValue = null | number | bigint | string | Uint8Array;
296
+
297
+ /**
298
+ * Convert a thrown native `node:sqlite` error into a typed {@link SQLiteError}.
299
+ *
300
+ * @remarks
301
+ * The single boundary mapping for the wrapper. The thrown value arrives as
302
+ * `unknown` and is narrowed with `isObject` + the `in` operator (never `as`, and
303
+ * not `isRecord` — a native `node:sqlite` error is an `Error` instance, so its
304
+ * prototype fails the plain-record test) to read its `errcode` — a numeric SQLite
305
+ * result code whose low byte identifies a constraint violation
306
+ * (`SQLITE_CONSTRAINT`), mapped to code `'CONSTRAINT'`; a locked-database fault
307
+ * (`SQLITE_BUSY`) is mapped to `'BUSY'`; anything else is `'UNKNOWN'`. The
308
+ * original message is preserved and `{ errcode }` carried in `context` when
309
+ * present. An already-typed `SQLiteError` is returned unchanged.
310
+ *
311
+ * @param error - The thrown value to convert
312
+ * @returns The equivalent `SQLiteError`
313
+ */
314
+ export declare function wrapError(error: unknown): SQLiteError;
315
+
316
+ export { }
@@ -0,0 +1,339 @@
1
+ import { isArray, isObject, isPromiseLike } from "@orkestrel/contract";
2
+ import { DatabaseSync } from "node:sqlite";
3
+ //#region src/server/constants.ts
4
+ /**
5
+ * SQLite result code for a constraint violation (`SQLITE_CONSTRAINT`).
6
+ *
7
+ * @remarks
8
+ * A native `errcode` packs the primary result in its low byte, with extended codes
9
+ * in the high bits (e.g. `SQLITE_CONSTRAINT_UNIQUE`), so the low byte is masked off
10
+ * before comparing. Read only by `wrapError`.
11
+ */
12
+ var SQLITE_CONSTRAINT = 19;
13
+ /**
14
+ * SQLite result code for a locked-database fault (`SQLITE_BUSY`).
15
+ *
16
+ * @remarks
17
+ * A native `errcode` packs the primary result in its low byte, with extended codes
18
+ * in the high bits, so the low byte is masked off before comparing. Read only by
19
+ * `wrapError`.
20
+ */
21
+ var SQLITE_BUSY = 5;
22
+ //#endregion
23
+ //#region src/server/errors.ts
24
+ /**
25
+ * An error thrown by the SQLite wrapper.
26
+ *
27
+ * @remarks
28
+ * Carries a {@link SQLiteErrorCode} and an optional `context` record (e.g. the
29
+ * native SQLite `errcode`). Construct it directly for the `CLOSED`
30
+ * wrapper-lifecycle fault; the internal `wrapError` maps a native `node:sqlite`
31
+ * error to the right code at the boundary. Narrow a caught value with
32
+ * {@link isSQLiteError}.
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * try {
37
+ * statement.run({ id: 'u1' })
38
+ * } catch (error) {
39
+ * if (isSQLiteError(error) && error.code === 'CONSTRAINT') {
40
+ * // a UNIQUE / PRIMARY KEY violation
41
+ * }
42
+ * }
43
+ * ```
44
+ */
45
+ var SQLiteError = class extends Error {
46
+ code;
47
+ context;
48
+ constructor(code, message, context) {
49
+ super(message);
50
+ this.name = "SQLiteError";
51
+ this.code = code;
52
+ this.context = context;
53
+ }
54
+ };
55
+ /**
56
+ * Whether a value is a {@link SQLiteError}.
57
+ *
58
+ * @param value - The value to test
59
+ * @returns `true` when `value` is a `SQLiteError`
60
+ */
61
+ function isSQLiteError(value) {
62
+ return value instanceof SQLiteError;
63
+ }
64
+ //#endregion
65
+ //#region src/server/helpers.ts
66
+ /**
67
+ * Convert a thrown native `node:sqlite` error into a typed {@link SQLiteError}.
68
+ *
69
+ * @remarks
70
+ * The single boundary mapping for the wrapper. The thrown value arrives as
71
+ * `unknown` and is narrowed with `isObject` + the `in` operator (never `as`, and
72
+ * not `isRecord` — a native `node:sqlite` error is an `Error` instance, so its
73
+ * prototype fails the plain-record test) to read its `errcode` — a numeric SQLite
74
+ * result code whose low byte identifies a constraint violation
75
+ * (`SQLITE_CONSTRAINT`), mapped to code `'CONSTRAINT'`; a locked-database fault
76
+ * (`SQLITE_BUSY`) is mapped to `'BUSY'`; anything else is `'UNKNOWN'`. The
77
+ * original message is preserved and `{ errcode }` carried in `context` when
78
+ * present. An already-typed `SQLiteError` is returned unchanged.
79
+ *
80
+ * @param error - The thrown value to convert
81
+ * @returns The equivalent `SQLiteError`
82
+ */
83
+ function wrapError(error) {
84
+ if (error instanceof SQLiteError) return error;
85
+ const errcode = isObject(error) && "errcode" in error && typeof error.errcode === "number" ? error.errcode : void 0;
86
+ const message = error instanceof Error ? error.message : "Unknown SQLite error";
87
+ return new SQLiteError(errcode !== void 0 && (errcode & 255) === 19 ? "CONSTRAINT" : errcode !== void 0 && (errcode & 255) === 5 ? "BUSY" : "UNKNOWN", message, errcode !== void 0 ? { errcode } : void 0);
88
+ }
89
+ /**
90
+ * Normalize {@link SQLiteParameters} to the binding shape a native `StatementSync`
91
+ * call expects.
92
+ *
93
+ * @remarks
94
+ * Positional parameters (an array) bind to `?` placeholders and are spread into
95
+ * the call; named parameters (a record) bind to bare `:name` placeholders and are
96
+ * passed as a single leading object. Returning a discriminated result keeps the
97
+ * `SQLiteStatement` dispatch typed against the native overloads without `as`.
98
+ *
99
+ * @param parameters - The wrapper parameters, or `undefined` for none
100
+ * @returns `{ positional }` for an array (empty when omitted) or `{ named }` for a record
101
+ */
102
+ function bindParameters(parameters) {
103
+ if (parameters === void 0) return { positional: [] };
104
+ if (isArray(parameters)) return { positional: parameters };
105
+ return { named: parameters };
106
+ }
107
+ //#endregion
108
+ //#region src/server/SQLiteStatement.ts
109
+ /**
110
+ * A prepared statement over `node:sqlite`'s `StatementSync` — the only way the
111
+ * wrapper runs SQL.
112
+ *
113
+ * @remarks
114
+ * Created by `database.prepare(sql)`, which threads a liveness check (internal —
115
+ * this constructor's second parameter is not part of the documented surface).
116
+ * Bare named parameters are enabled on construction so a record's keys bind
117
+ * without the SQL prefix character. Each method first gates on that liveness
118
+ * check, throwing a `CLOSED` `SQLiteError` once the owning connection has been
119
+ * closed — a statement prepared on a connection that is later closed and then
120
+ * reconnected stays `CLOSED`; a fresh statement must be prepared on the new
121
+ * connection. Each method then binds the optional parameters (an array spread
122
+ * to `?` placeholders, a record passed as a single named object) and executes
123
+ * synchronously, mapping any native fault to a `SQLiteError` — including a
124
+ * mid-stream fault from `iterate`'s lazy native iterator, stepped inside its own
125
+ * try/catch so a fault on a later row is mapped exactly like an eager one. Row
126
+ * values arrive as the native {@link SQLiteRow} types; the typed layer above
127
+ * (the driver) imposes a precise shape through a contract rather than
128
+ * re-narrowing here (AGENTS §14).
129
+ */
130
+ var SQLiteStatement = class {
131
+ #statement;
132
+ #closed;
133
+ constructor(statement, closed) {
134
+ this.#statement = statement;
135
+ this.#closed = closed;
136
+ this.#statement.setAllowBareNamedParameters(true);
137
+ }
138
+ run(parameters) {
139
+ this.#require();
140
+ try {
141
+ const bound = bindParameters(parameters);
142
+ const result = "named" in bound ? this.#statement.run(bound.named) : this.#statement.run(...bound.positional);
143
+ return {
144
+ changes: Number(result.changes),
145
+ rowid: Number(result.lastInsertRowid)
146
+ };
147
+ } catch (error) {
148
+ throw wrapError(error);
149
+ }
150
+ }
151
+ get(parameters) {
152
+ this.#require();
153
+ try {
154
+ const bound = bindParameters(parameters);
155
+ return "named" in bound ? this.#statement.get(bound.named) : this.#statement.get(...bound.positional);
156
+ } catch (error) {
157
+ throw wrapError(error);
158
+ }
159
+ }
160
+ all(parameters) {
161
+ this.#require();
162
+ try {
163
+ const bound = bindParameters(parameters);
164
+ return "named" in bound ? this.#statement.all(bound.named) : this.#statement.all(...bound.positional);
165
+ } catch (error) {
166
+ throw wrapError(error);
167
+ }
168
+ }
169
+ iterate(parameters) {
170
+ this.#require();
171
+ let native;
172
+ try {
173
+ const bound = bindParameters(parameters);
174
+ native = "named" in bound ? this.#statement.iterate(bound.named) : this.#statement.iterate(...bound.positional);
175
+ } catch (error) {
176
+ throw wrapError(error);
177
+ }
178
+ return this.#stream(native);
179
+ }
180
+ #require() {
181
+ if (this.#closed()) throw new SQLiteError("CLOSED", "Statement is closed — its connection was closed; prepare a new statement");
182
+ }
183
+ *#stream(native) {
184
+ for (;;) {
185
+ let result;
186
+ try {
187
+ result = native.next();
188
+ } catch (error) {
189
+ throw wrapError(error);
190
+ }
191
+ if (result.done === true) return;
192
+ yield result.value;
193
+ }
194
+ }
195
+ };
196
+ //#endregion
197
+ //#region src/server/SQLiteDatabase.ts
198
+ /**
199
+ * A synchronous SQLite database over `node:sqlite`'s `DatabaseSync`.
200
+ *
201
+ * @remarks
202
+ * Created by `createSQLiteDatabase`. It connects lazily (`connect` opens the
203
+ * underlying `DatabaseSync`, idempotent) and every operation routes through a
204
+ * private gate that throws a `CLOSED` `SQLiteError` before `connect` or after
205
+ * `close`. `exec` runs result-less SQL; `prepare` compiles a `SQLiteStatement`;
206
+ * `transaction` wraps a scope in `BEGIN` / `COMMIT`, rolling back on a throw;
207
+ * `begin` / `commit` / `rollback` expose those same primitives directly for a
208
+ * long-lived or externally-driven transaction; `pragma` reads (or sets then
209
+ * reads) one PRAGMA value — `name` is trusted
210
+ * internal use only, never untrusted input, since pragma names cannot be bound
211
+ * as parameters. `transacting` reports whether a transaction is currently open
212
+ * (node:sqlite's `isTransaction`), `false` when not connected. A native fault
213
+ * surfaces as a `SQLiteError` mapped at the boundary.
214
+ */
215
+ var SQLiteDatabase = class {
216
+ #path;
217
+ #readonly;
218
+ #timeout;
219
+ #foreignKeys;
220
+ #bigints;
221
+ #database;
222
+ constructor(options) {
223
+ this.#path = options.path ?? ":memory:";
224
+ this.#readonly = options.readonly;
225
+ this.#timeout = options.timeout;
226
+ this.#foreignKeys = options.foreignKeys;
227
+ this.#bigints = options.bigints;
228
+ }
229
+ get path() {
230
+ return this.#path;
231
+ }
232
+ get connected() {
233
+ return this.#database !== void 0;
234
+ }
235
+ get transacting() {
236
+ return this.#database?.isTransaction ?? false;
237
+ }
238
+ connect() {
239
+ if (this.#database === void 0) this.#database = new DatabaseSync(this.#path, {
240
+ readOnly: this.#readonly,
241
+ timeout: this.#timeout,
242
+ enableForeignKeyConstraints: this.#foreignKeys,
243
+ readBigInts: this.#bigints
244
+ });
245
+ }
246
+ close() {
247
+ this.#database?.close();
248
+ this.#database = void 0;
249
+ }
250
+ /** Close the connection — enables `using db = createSQLiteDatabase(...)`. */
251
+ [Symbol.dispose]() {
252
+ this.close();
253
+ }
254
+ exec(sql) {
255
+ try {
256
+ this.#require().exec(sql);
257
+ } catch (error) {
258
+ throw wrapError(error);
259
+ }
260
+ }
261
+ prepare(sql) {
262
+ try {
263
+ const database = this.#require();
264
+ return new SQLiteStatement(database.prepare(sql), () => this.#database !== database);
265
+ } catch (error) {
266
+ throw wrapError(error);
267
+ }
268
+ }
269
+ transaction(scope) {
270
+ this.begin();
271
+ let result;
272
+ try {
273
+ result = scope();
274
+ } catch (error) {
275
+ try {
276
+ this.rollback();
277
+ } catch {}
278
+ throw error;
279
+ }
280
+ if (isPromiseLike(result)) {
281
+ try {
282
+ this.rollback();
283
+ } catch {}
284
+ throw new SQLiteError("UNKNOWN", "transaction(scope) requires a synchronous scope — an async or Promise-returning scope cannot commit or roll back before its awaited work runs; use begin() / commit() / rollback() directly for a transaction that spans async caller code");
285
+ }
286
+ this.commit();
287
+ return result;
288
+ }
289
+ begin() {
290
+ this.exec("BEGIN");
291
+ }
292
+ commit() {
293
+ this.exec("COMMIT");
294
+ }
295
+ rollback() {
296
+ this.exec("ROLLBACK");
297
+ }
298
+ pragma(name, value) {
299
+ if (value !== void 0) this.exec("PRAGMA " + name + " = " + value);
300
+ const row = this.prepare("PRAGMA " + name).get();
301
+ return row ? Object.values(row)[0] : void 0;
302
+ }
303
+ #require() {
304
+ if (this.#database === void 0) throw new SQLiteError("CLOSED", "Database is not connected — call connect() first");
305
+ return this.#database;
306
+ }
307
+ };
308
+ //#endregion
309
+ //#region src/server/factories.ts
310
+ /**
311
+ * Create a synchronous SQLite database over `node:sqlite`.
312
+ *
313
+ * @remarks
314
+ * The wrapper connects lazily — call `connect` (or it is required by the first
315
+ * operation, which throws `CLOSED` until then). This is the lower-level native
316
+ * handle a higher-level typed database engine would be built on; here it ships
317
+ * as the standalone, server-native SQLite surface.
318
+ *
319
+ * @param options - The database `path` (a file path, or `':memory:'` by default)
320
+ * @returns A typed {@link SQLiteDatabaseInterface}
321
+ *
322
+ * @example
323
+ * ```ts
324
+ * import { createSQLiteDatabase } from '@orkestrel/sqlite'
325
+ *
326
+ * const db = createSQLiteDatabase({ path: ':memory:' })
327
+ * db.connect()
328
+ * db.exec('CREATE TABLE users (id TEXT PRIMARY KEY, name TEXT)')
329
+ * db.prepare('INSERT INTO users VALUES (?, ?)').run(['u1', 'Ada'])
330
+ * db.prepare('SELECT name FROM users WHERE id = ?').get(['u1']) // { name: 'Ada' }
331
+ * ```
332
+ */
333
+ function createSQLiteDatabase(options) {
334
+ return new SQLiteDatabase(options ?? {});
335
+ }
336
+ //#endregion
337
+ export { SQLITE_BUSY, SQLITE_CONSTRAINT, SQLiteDatabase, SQLiteError, SQLiteStatement, bindParameters, createSQLiteDatabase, isSQLiteError, wrapError };
338
+
339
+ //# sourceMappingURL=index.js.map