@orkestrel/sqlite 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Orkestrel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # @orkestrel/sqlite
2
+
3
+ A typed, synchronous SQLite wrapper for the `@orkestrel` line — a thin,
4
+ zero-dependency skin over Node's built-in `node:sqlite` (`DatabaseSync` /
5
+ `StatementSync`) giving prepared statements, transactions, and pragmas. Built
6
+ on `@orkestrel/contract` for its boundary narrowing.
7
+
8
+ ## Install
9
+
10
+ ```sh
11
+ npm install @orkestrel/sqlite
12
+ ```
13
+
14
+ ## Requirements
15
+
16
+ - Node.js >= 24
17
+ - `node:sqlite` (Node's built-in SQLite module)
18
+ - Server-only — no CommonJS/browser split, single Node-native surface
19
+
20
+ ## Status
21
+
22
+ Pre-release. The public API documented in
23
+ [`guides/src/sqlite.md`](https://github.com/orkestrel/sqlite/blob/main/guides/src/sqlite.md)
24
+ is implemented and covered by tests, but the package has not yet reached a
25
+ stable `1.0` release.
26
+
27
+ ## Package
28
+
29
+ Published as a single Node-only surface per the `exports` field in
30
+ `package.json` — one `.` entry backed by a CommonJS build of `src/server`
31
+ (required by `node:sqlite`'s CJS-only shape at this Node version).
32
+
33
+ ## License
34
+
35
+ MIT © [Orkestrel](https://github.com/orkestrel) — see [LICENSE](./LICENSE).
@@ -0,0 +1,29 @@
1
+ import type { SQLiteDatabaseInterface, SQLiteDatabaseOptions, SQLiteStatementInterface, SQLiteValue } from './types.js';
2
+ /**
3
+ * A synchronous SQLite database over `node:sqlite`'s `DatabaseSync`.
4
+ *
5
+ * @remarks
6
+ * Created by `createSQLiteDatabase`. It connects lazily (`connect` opens the
7
+ * underlying `DatabaseSync`, idempotent) and every operation routes through a
8
+ * private gate that throws a `CLOSED` `SQLiteError` before `connect` or after
9
+ * `close`. `exec` runs result-less SQL; `prepare` compiles a `SQLiteStatement`;
10
+ * `transaction` wraps a scope in `BEGIN` / `COMMIT`, rolling back on a throw;
11
+ * `pragma` reads (or sets then reads) one PRAGMA value — `name` is trusted
12
+ * internal use only, never untrusted input, since pragma names cannot be bound
13
+ * as parameters. A native fault surfaces as a `SQLiteError` mapped at the
14
+ * boundary.
15
+ */
16
+ export declare class SQLiteDatabase implements SQLiteDatabaseInterface {
17
+ #private;
18
+ constructor(options: SQLiteDatabaseOptions);
19
+ get path(): string;
20
+ get connected(): boolean;
21
+ connect(): void;
22
+ close(): void;
23
+ /** Close the connection — enables `using db = createSQLiteDatabase(...)`. */
24
+ [Symbol.dispose](): void;
25
+ exec(sql: string): void;
26
+ prepare(sql: string): SQLiteStatementInterface;
27
+ transaction<R>(scope: () => R): R;
28
+ pragma(name: string, value?: string | number): SQLiteValue | undefined;
29
+ }
@@ -0,0 +1,23 @@
1
+ import type { StatementSync } from 'node:sqlite';
2
+ import type { SQLiteParameters, SQLiteRow, SQLiteRunResult, SQLiteStatementInterface } from './types.js';
3
+ /**
4
+ * A prepared statement over `node:sqlite`'s `StatementSync` — the only way the
5
+ * wrapper runs SQL.
6
+ *
7
+ * @remarks
8
+ * Created by `database.prepare(sql)`. Bare named parameters are enabled on
9
+ * construction so a record's keys bind without the SQL prefix character. Each
10
+ * method binds the optional parameters (an array spread to `?` placeholders, a
11
+ * record passed as a single named object) and executes synchronously, mapping any
12
+ * native fault to a `SQLiteError`. Row values arrive as the native
13
+ * {@link SQLiteRow} types; the typed layer above (the driver) imposes a precise
14
+ * shape through a contract rather than re-narrowing here (AGENTS §14).
15
+ */
16
+ export declare class SQLiteStatement implements SQLiteStatementInterface {
17
+ #private;
18
+ constructor(statement: StatementSync);
19
+ run(parameters?: SQLiteParameters): SQLiteRunResult;
20
+ get(parameters?: SQLiteParameters): SQLiteRow | undefined;
21
+ all(parameters?: SQLiteParameters): readonly SQLiteRow[];
22
+ iterate(parameters?: SQLiteParameters): IterableIterator<SQLiteRow>;
23
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * SQLite result code for a constraint violation (`SQLITE_CONSTRAINT`).
3
+ *
4
+ * @remarks
5
+ * A native `errcode` packs the primary result in its low byte, with extended codes
6
+ * in the high bits (e.g. `SQLITE_CONSTRAINT_UNIQUE`), so the low byte is masked off
7
+ * before comparing. Read only by `wrapError`.
8
+ */
9
+ export declare const SQLITE_CONSTRAINT = 19;
10
+ /**
11
+ * SQLite result code for a locked-database fault (`SQLITE_BUSY`).
12
+ *
13
+ * @remarks
14
+ * A native `errcode` packs the primary result in its low byte, with extended codes
15
+ * in the high bits, so the low byte is masked off before comparing. Read only by
16
+ * `wrapError`.
17
+ */
18
+ export declare const SQLITE_BUSY = 5;
@@ -0,0 +1,34 @@
1
+ import type { SQLiteErrorCode } from './types.js';
2
+ /**
3
+ * An error thrown by the SQLite wrapper.
4
+ *
5
+ * @remarks
6
+ * Carries a {@link SQLiteErrorCode} and an optional `context` record (e.g. the
7
+ * native SQLite `errcode`). Construct it directly for the `CLOSED`
8
+ * wrapper-lifecycle fault; the internal `wrapError` maps a native `node:sqlite`
9
+ * error to the right code at the boundary. Narrow a caught value with
10
+ * {@link isSQLiteError}.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * try {
15
+ * statement.run({ id: 'u1' })
16
+ * } catch (error) {
17
+ * if (isSQLiteError(error) && error.code === 'CONSTRAINT') {
18
+ * // a UNIQUE / PRIMARY KEY violation
19
+ * }
20
+ * }
21
+ * ```
22
+ */
23
+ export declare class SQLiteError extends Error {
24
+ readonly code: SQLiteErrorCode;
25
+ readonly context?: Readonly<Record<string, unknown>>;
26
+ constructor(code: SQLiteErrorCode, message: string, context?: Readonly<Record<string, unknown>>);
27
+ }
28
+ /**
29
+ * Whether a value is a {@link SQLiteError}.
30
+ *
31
+ * @param value - The value to test
32
+ * @returns `true` when `value` is a `SQLiteError`
33
+ */
34
+ export declare function isSQLiteError(value: unknown): value is SQLiteError;
@@ -0,0 +1,25 @@
1
+ import type { SQLiteDatabaseInterface, SQLiteDatabaseOptions } from './types.js';
2
+ /**
3
+ * Create a synchronous SQLite database over `node:sqlite`.
4
+ *
5
+ * @remarks
6
+ * The wrapper connects lazily — call `connect` (or it is required by the first
7
+ * operation, which throws `CLOSED` until then). This is the lower-level native
8
+ * handle a higher-level typed database engine would be built on; here it ships
9
+ * as the standalone, server-native SQLite surface.
10
+ *
11
+ * @param options - The database `path` (a file path, or `':memory:'` by default)
12
+ * @returns A typed {@link SQLiteDatabaseInterface}
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { createSQLiteDatabase } from '@src/server'
17
+ *
18
+ * const db = createSQLiteDatabase({ path: ':memory:' })
19
+ * db.connect()
20
+ * db.exec('CREATE TABLE users (id TEXT PRIMARY KEY, name TEXT)')
21
+ * db.prepare('INSERT INTO users VALUES (?, ?)').run(['u1', 'Ada'])
22
+ * db.prepare('SELECT name FROM users WHERE id = ?').get(['u1']) // { name: 'Ada' }
23
+ * ```
24
+ */
25
+ export declare function createSQLiteDatabase(options?: SQLiteDatabaseOptions): SQLiteDatabaseInterface;
@@ -0,0 +1,38 @@
1
+ import type { SQLiteParameters, SQLiteValue } from './types.js';
2
+ import { SQLiteError } from './errors.js';
3
+ /**
4
+ * Convert a thrown native `node:sqlite` error into a typed {@link SQLiteError}.
5
+ *
6
+ * @remarks
7
+ * The single boundary mapping for the wrapper. The thrown value arrives as
8
+ * `unknown` and is narrowed with `isObject` + the `in` operator (never `as`, and
9
+ * not `isRecord` — a native `node:sqlite` error is an `Error` instance, so its
10
+ * prototype fails the plain-record test) to read its `errcode` — a numeric SQLite
11
+ * result code whose low byte identifies a constraint violation
12
+ * (`SQLITE_CONSTRAINT`), mapped to code `'CONSTRAINT'`; a locked-database fault
13
+ * (`SQLITE_BUSY`) is mapped to `'BUSY'`; anything else is `'UNKNOWN'`. The
14
+ * original message is preserved and `{ errcode }` carried in `context` when
15
+ * present. An already-typed `SQLiteError` is returned unchanged.
16
+ *
17
+ * @param error - The thrown value to convert
18
+ * @returns The equivalent `SQLiteError`
19
+ */
20
+ export declare function wrapError(error: unknown): SQLiteError;
21
+ /**
22
+ * Normalize {@link SQLiteParameters} to the binding shape a native `StatementSync`
23
+ * call expects.
24
+ *
25
+ * @remarks
26
+ * Positional parameters (an array) bind to `?` placeholders and are spread into
27
+ * the call; named parameters (a record) bind to bare `:name` placeholders and are
28
+ * passed as a single leading object. Returning a discriminated result keeps the
29
+ * `SQLiteStatement` dispatch typed against the native overloads without `as`.
30
+ *
31
+ * @param parameters - The wrapper parameters, or `undefined` for none
32
+ * @returns `{ positional }` for an array (empty when omitted) or `{ named }` for a record
33
+ */
34
+ export declare function bindParameters(parameters?: SQLiteParameters): {
35
+ readonly positional: readonly SQLiteValue[];
36
+ } | {
37
+ readonly named: Readonly<Record<string, SQLiteValue>>;
38
+ };
@@ -0,0 +1,294 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _orkestrel_contract = require("@orkestrel/contract");
3
+ let node_sqlite = require("node:sqlite");
4
+ //#region src/server/constants.ts
5
+ /**
6
+ * SQLite result code for a constraint violation (`SQLITE_CONSTRAINT`).
7
+ *
8
+ * @remarks
9
+ * A native `errcode` packs the primary result in its low byte, with extended codes
10
+ * in the high bits (e.g. `SQLITE_CONSTRAINT_UNIQUE`), so the low byte is masked off
11
+ * before comparing. Read only by `wrapError`.
12
+ */
13
+ var SQLITE_CONSTRAINT = 19;
14
+ /**
15
+ * SQLite result code for a locked-database fault (`SQLITE_BUSY`).
16
+ *
17
+ * @remarks
18
+ * A native `errcode` packs the primary result in its low byte, with extended codes
19
+ * in the high bits, so the low byte is masked off before comparing. Read only by
20
+ * `wrapError`.
21
+ */
22
+ var SQLITE_BUSY = 5;
23
+ //#endregion
24
+ //#region src/server/errors.ts
25
+ /**
26
+ * An error thrown by the SQLite wrapper.
27
+ *
28
+ * @remarks
29
+ * Carries a {@link SQLiteErrorCode} and an optional `context` record (e.g. the
30
+ * native SQLite `errcode`). Construct it directly for the `CLOSED`
31
+ * wrapper-lifecycle fault; the internal `wrapError` maps a native `node:sqlite`
32
+ * error to the right code at the boundary. Narrow a caught value with
33
+ * {@link isSQLiteError}.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * try {
38
+ * statement.run({ id: 'u1' })
39
+ * } catch (error) {
40
+ * if (isSQLiteError(error) && error.code === 'CONSTRAINT') {
41
+ * // a UNIQUE / PRIMARY KEY violation
42
+ * }
43
+ * }
44
+ * ```
45
+ */
46
+ var SQLiteError = class extends Error {
47
+ code;
48
+ context;
49
+ constructor(code, message, context) {
50
+ super(message);
51
+ this.name = "SQLiteError";
52
+ this.code = code;
53
+ this.context = context;
54
+ }
55
+ };
56
+ /**
57
+ * Whether a value is a {@link SQLiteError}.
58
+ *
59
+ * @param value - The value to test
60
+ * @returns `true` when `value` is a `SQLiteError`
61
+ */
62
+ function isSQLiteError(value) {
63
+ return value instanceof SQLiteError;
64
+ }
65
+ //#endregion
66
+ //#region src/server/helpers.ts
67
+ /**
68
+ * Convert a thrown native `node:sqlite` error into a typed {@link SQLiteError}.
69
+ *
70
+ * @remarks
71
+ * The single boundary mapping for the wrapper. The thrown value arrives as
72
+ * `unknown` and is narrowed with `isObject` + the `in` operator (never `as`, and
73
+ * not `isRecord` — a native `node:sqlite` error is an `Error` instance, so its
74
+ * prototype fails the plain-record test) to read its `errcode` — a numeric SQLite
75
+ * result code whose low byte identifies a constraint violation
76
+ * (`SQLITE_CONSTRAINT`), mapped to code `'CONSTRAINT'`; a locked-database fault
77
+ * (`SQLITE_BUSY`) is mapped to `'BUSY'`; anything else is `'UNKNOWN'`. The
78
+ * original message is preserved and `{ errcode }` carried in `context` when
79
+ * present. An already-typed `SQLiteError` is returned unchanged.
80
+ *
81
+ * @param error - The thrown value to convert
82
+ * @returns The equivalent `SQLiteError`
83
+ */
84
+ function wrapError(error) {
85
+ if (error instanceof SQLiteError) return error;
86
+ const errcode = (0, _orkestrel_contract.isObject)(error) && "errcode" in error && typeof error.errcode === "number" ? error.errcode : void 0;
87
+ const message = error instanceof Error ? error.message : "Unknown SQLite error";
88
+ 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);
89
+ }
90
+ /**
91
+ * Normalize {@link SQLiteParameters} to the binding shape a native `StatementSync`
92
+ * call expects.
93
+ *
94
+ * @remarks
95
+ * Positional parameters (an array) bind to `?` placeholders and are spread into
96
+ * the call; named parameters (a record) bind to bare `:name` placeholders and are
97
+ * passed as a single leading object. Returning a discriminated result keeps the
98
+ * `SQLiteStatement` dispatch typed against the native overloads without `as`.
99
+ *
100
+ * @param parameters - The wrapper parameters, or `undefined` for none
101
+ * @returns `{ positional }` for an array (empty when omitted) or `{ named }` for a record
102
+ */
103
+ function bindParameters(parameters) {
104
+ if (parameters === void 0) return { positional: [] };
105
+ if ((0, _orkestrel_contract.isArray)(parameters)) return { positional: parameters };
106
+ return { named: parameters };
107
+ }
108
+ //#endregion
109
+ //#region src/server/SQLiteStatement.ts
110
+ /**
111
+ * A prepared statement over `node:sqlite`'s `StatementSync` — the only way the
112
+ * wrapper runs SQL.
113
+ *
114
+ * @remarks
115
+ * Created by `database.prepare(sql)`. Bare named parameters are enabled on
116
+ * construction so a record's keys bind without the SQL prefix character. Each
117
+ * method binds the optional parameters (an array spread to `?` placeholders, a
118
+ * record passed as a single named object) and executes synchronously, mapping any
119
+ * native fault to a `SQLiteError`. Row values arrive as the native
120
+ * {@link SQLiteRow} types; the typed layer above (the driver) imposes a precise
121
+ * shape through a contract rather than re-narrowing here (AGENTS §14).
122
+ */
123
+ var SQLiteStatement = class {
124
+ #statement;
125
+ constructor(statement) {
126
+ this.#statement = statement;
127
+ this.#statement.setAllowBareNamedParameters(true);
128
+ }
129
+ run(parameters) {
130
+ try {
131
+ const bound = bindParameters(parameters);
132
+ const result = "named" in bound ? this.#statement.run(bound.named) : this.#statement.run(...bound.positional);
133
+ return {
134
+ changes: Number(result.changes),
135
+ rowid: Number(result.lastInsertRowid)
136
+ };
137
+ } catch (error) {
138
+ throw wrapError(error);
139
+ }
140
+ }
141
+ get(parameters) {
142
+ try {
143
+ const bound = bindParameters(parameters);
144
+ return "named" in bound ? this.#statement.get(bound.named) : this.#statement.get(...bound.positional);
145
+ } catch (error) {
146
+ throw wrapError(error);
147
+ }
148
+ }
149
+ all(parameters) {
150
+ try {
151
+ const bound = bindParameters(parameters);
152
+ return "named" in bound ? this.#statement.all(bound.named) : this.#statement.all(...bound.positional);
153
+ } catch (error) {
154
+ throw wrapError(error);
155
+ }
156
+ }
157
+ iterate(parameters) {
158
+ try {
159
+ const bound = bindParameters(parameters);
160
+ return "named" in bound ? this.#statement.iterate(bound.named) : this.#statement.iterate(...bound.positional);
161
+ } catch (error) {
162
+ throw wrapError(error);
163
+ }
164
+ }
165
+ };
166
+ //#endregion
167
+ //#region src/server/SQLiteDatabase.ts
168
+ /**
169
+ * A synchronous SQLite database over `node:sqlite`'s `DatabaseSync`.
170
+ *
171
+ * @remarks
172
+ * Created by `createSQLiteDatabase`. It connects lazily (`connect` opens the
173
+ * underlying `DatabaseSync`, idempotent) and every operation routes through a
174
+ * private gate that throws a `CLOSED` `SQLiteError` before `connect` or after
175
+ * `close`. `exec` runs result-less SQL; `prepare` compiles a `SQLiteStatement`;
176
+ * `transaction` wraps a scope in `BEGIN` / `COMMIT`, rolling back on a throw;
177
+ * `pragma` reads (or sets then reads) one PRAGMA value — `name` is trusted
178
+ * internal use only, never untrusted input, since pragma names cannot be bound
179
+ * as parameters. A native fault surfaces as a `SQLiteError` mapped at the
180
+ * boundary.
181
+ */
182
+ var SQLiteDatabase = class {
183
+ #path;
184
+ #readonly;
185
+ #timeout;
186
+ #foreignKeys;
187
+ #bigints;
188
+ #database;
189
+ constructor(options) {
190
+ this.#path = options.path ?? ":memory:";
191
+ this.#readonly = options.readonly;
192
+ this.#timeout = options.timeout;
193
+ this.#foreignKeys = options.foreignKeys;
194
+ this.#bigints = options.bigints;
195
+ }
196
+ get path() {
197
+ return this.#path;
198
+ }
199
+ get connected() {
200
+ return this.#database !== void 0;
201
+ }
202
+ connect() {
203
+ if (this.#database === void 0) this.#database = new node_sqlite.DatabaseSync(this.#path, {
204
+ readOnly: this.#readonly,
205
+ timeout: this.#timeout,
206
+ enableForeignKeyConstraints: this.#foreignKeys,
207
+ readBigInts: this.#bigints
208
+ });
209
+ }
210
+ close() {
211
+ this.#database?.close();
212
+ this.#database = void 0;
213
+ }
214
+ /** Close the connection — enables `using db = createSQLiteDatabase(...)`. */
215
+ [Symbol.dispose]() {
216
+ this.close();
217
+ }
218
+ exec(sql) {
219
+ try {
220
+ this.#require().exec(sql);
221
+ } catch (error) {
222
+ throw wrapError(error);
223
+ }
224
+ }
225
+ prepare(sql) {
226
+ try {
227
+ return new SQLiteStatement(this.#require().prepare(sql));
228
+ } catch (error) {
229
+ throw wrapError(error);
230
+ }
231
+ }
232
+ transaction(scope) {
233
+ this.exec("BEGIN");
234
+ try {
235
+ const result = scope();
236
+ this.exec("COMMIT");
237
+ return result;
238
+ } catch (error) {
239
+ try {
240
+ this.exec("ROLLBACK");
241
+ } catch {}
242
+ throw error;
243
+ }
244
+ }
245
+ pragma(name, value) {
246
+ if (value !== void 0) this.exec("PRAGMA " + name + " = " + value);
247
+ const row = this.prepare("PRAGMA " + name).get();
248
+ return row ? Object.values(row)[0] : void 0;
249
+ }
250
+ #require() {
251
+ if (this.#database === void 0) throw new SQLiteError("CLOSED", "Database is not connected — call connect() first");
252
+ return this.#database;
253
+ }
254
+ };
255
+ //#endregion
256
+ //#region src/server/factories.ts
257
+ /**
258
+ * Create a synchronous SQLite database over `node:sqlite`.
259
+ *
260
+ * @remarks
261
+ * The wrapper connects lazily — call `connect` (or it is required by the first
262
+ * operation, which throws `CLOSED` until then). This is the lower-level native
263
+ * handle a higher-level typed database engine would be built on; here it ships
264
+ * as the standalone, server-native SQLite surface.
265
+ *
266
+ * @param options - The database `path` (a file path, or `':memory:'` by default)
267
+ * @returns A typed {@link SQLiteDatabaseInterface}
268
+ *
269
+ * @example
270
+ * ```ts
271
+ * import { createSQLiteDatabase } from '@src/server'
272
+ *
273
+ * const db = createSQLiteDatabase({ path: ':memory:' })
274
+ * db.connect()
275
+ * db.exec('CREATE TABLE users (id TEXT PRIMARY KEY, name TEXT)')
276
+ * db.prepare('INSERT INTO users VALUES (?, ?)').run(['u1', 'Ada'])
277
+ * db.prepare('SELECT name FROM users WHERE id = ?').get(['u1']) // { name: 'Ada' }
278
+ * ```
279
+ */
280
+ function createSQLiteDatabase(options) {
281
+ return new SQLiteDatabase(options ?? {});
282
+ }
283
+ //#endregion
284
+ exports.SQLITE_BUSY = SQLITE_BUSY;
285
+ exports.SQLITE_CONSTRAINT = SQLITE_CONSTRAINT;
286
+ exports.SQLiteDatabase = SQLiteDatabase;
287
+ exports.SQLiteError = SQLiteError;
288
+ exports.SQLiteStatement = SQLiteStatement;
289
+ exports.bindParameters = bindParameters;
290
+ exports.createSQLiteDatabase = createSQLiteDatabase;
291
+ exports.isSQLiteError = isSQLiteError;
292
+ exports.wrapError = wrapError;
293
+
294
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["#statement","#path","#readonly","#timeout","#foreignKeys","#bigints","#database","#require"],"sources":["../../../src/server/constants.ts","../../../src/server/errors.ts","../../../src/server/helpers.ts","../../../src/server/SQLiteStatement.ts","../../../src/server/SQLiteDatabase.ts","../../../src/server/factories.ts"],"sourcesContent":["// The wrapper's numeric SQLite result codes (AGENTS §5 constants file).\n\n/**\n * SQLite result code for a constraint violation (`SQLITE_CONSTRAINT`).\n *\n * @remarks\n * A native `errcode` packs the primary result in its low byte, with extended codes\n * in the high bits (e.g. `SQLITE_CONSTRAINT_UNIQUE`), so the low byte is masked off\n * before comparing. Read only by `wrapError`.\n */\nexport const SQLITE_CONSTRAINT = 19\n\n/**\n * SQLite result code for a locked-database fault (`SQLITE_BUSY`).\n *\n * @remarks\n * A native `errcode` packs the primary result in its low byte, with extended codes\n * in the high bits, so the low byte is masked off before comparing. Read only by\n * `wrapError`.\n */\nexport const SQLITE_BUSY = 5\n","import type { SQLiteErrorCode } from './types.js'\n\n// Errors for the SQLite wrapper. A single `SQLiteError` carries a\n// machine-readable `code` mapped from the native `node:sqlite` fault at the\n// boundary (`wrapError`), so a `catch` branches on `error.code` rather than\n// parsing a message. Mirrors the IndexedDB wrapper's `IndexedDBError`; its three\n// codes are deliberately lean — the wrapper sits right on the raw SQLite surface,\n// where the constraint fault is the one worth naming, `CLOSED` is the\n// wrapper-lifecycle fault, and everything else is `UNKNOWN` (AGENTS §12).\n\n/**\n * An error thrown by the SQLite wrapper.\n *\n * @remarks\n * Carries a {@link SQLiteErrorCode} and an optional `context` record (e.g. the\n * native SQLite `errcode`). Construct it directly for the `CLOSED`\n * wrapper-lifecycle fault; the internal `wrapError` maps a native `node:sqlite`\n * error to the right code at the boundary. Narrow a caught value with\n * {@link isSQLiteError}.\n *\n * @example\n * ```ts\n * try {\n * \tstatement.run({ id: 'u1' })\n * } catch (error) {\n * \tif (isSQLiteError(error) && error.code === 'CONSTRAINT') {\n * \t\t// a UNIQUE / PRIMARY KEY violation\n * \t}\n * }\n * ```\n */\nexport class SQLiteError extends Error {\n\treadonly code: SQLiteErrorCode\n\treadonly context?: Readonly<Record<string, unknown>>\n\n\tconstructor(code: SQLiteErrorCode, message: string, context?: Readonly<Record<string, unknown>>) {\n\t\tsuper(message)\n\t\tthis.name = 'SQLiteError'\n\t\tthis.code = code\n\t\tthis.context = context\n\t}\n}\n\n/**\n * Whether a value is a {@link SQLiteError}.\n *\n * @param value - The value to test\n * @returns `true` when `value` is a `SQLiteError`\n */\nexport function isSQLiteError(value: unknown): value is SQLiteError {\n\treturn value instanceof SQLiteError\n}\n","import type { SQLiteParameters, SQLiteValue } from './types.js'\nimport { isArray, isObject } from '@orkestrel/contract'\nimport { SQLITE_BUSY, SQLITE_CONSTRAINT } from './constants.js'\nimport { SQLiteError } from './errors.js'\n\n// The wrapper's boundary helpers, shared by `SQLiteDatabase` and `SQLiteStatement`:\n// `wrapError` maps a thrown native `node:sqlite` error to a typed `SQLiteError`\n// (the one honest narrowing point — `isObject` + `in`, never `as`, per AGENTS\n// §14), and `bindParameters` normalizes the wrapper's `SQLiteParameters` to the\n// shape a native `StatementSync` call expects (positional spread vs. a single\n// named record). Both are pure.\n\n/**\n * Convert a thrown native `node:sqlite` error into a typed {@link SQLiteError}.\n *\n * @remarks\n * The single boundary mapping for the wrapper. The thrown value arrives as\n * `unknown` and is narrowed with `isObject` + the `in` operator (never `as`, and\n * not `isRecord` — a native `node:sqlite` error is an `Error` instance, so its\n * prototype fails the plain-record test) to read its `errcode` — a numeric SQLite\n * result code whose low byte identifies a constraint violation\n * (`SQLITE_CONSTRAINT`), mapped to code `'CONSTRAINT'`; a locked-database fault\n * (`SQLITE_BUSY`) is mapped to `'BUSY'`; anything else is `'UNKNOWN'`. The\n * original message is preserved and `{ errcode }` carried in `context` when\n * present. An already-typed `SQLiteError` is returned unchanged.\n *\n * @param error - The thrown value to convert\n * @returns The equivalent `SQLiteError`\n */\nexport function wrapError(error: unknown): SQLiteError {\n\tif (error instanceof SQLiteError) return error\n\tconst errcode =\n\t\tisObject(error) && 'errcode' in error && typeof error.errcode === 'number'\n\t\t\t? error.errcode\n\t\t\t: undefined\n\tconst message = error instanceof Error ? error.message : 'Unknown SQLite error'\n\tconst code =\n\t\terrcode !== undefined && (errcode & 0xff) === SQLITE_CONSTRAINT\n\t\t\t? 'CONSTRAINT'\n\t\t\t: errcode !== undefined && (errcode & 0xff) === SQLITE_BUSY\n\t\t\t\t? 'BUSY'\n\t\t\t\t: 'UNKNOWN'\n\treturn new SQLiteError(code, message, errcode !== undefined ? { errcode } : undefined)\n}\n\n/**\n * Normalize {@link SQLiteParameters} to the binding shape a native `StatementSync`\n * call expects.\n *\n * @remarks\n * Positional parameters (an array) bind to `?` placeholders and are spread into\n * the call; named parameters (a record) bind to bare `:name` placeholders and are\n * passed as a single leading object. Returning a discriminated result keeps the\n * `SQLiteStatement` dispatch typed against the native overloads without `as`.\n *\n * @param parameters - The wrapper parameters, or `undefined` for none\n * @returns `{ positional }` for an array (empty when omitted) or `{ named }` for a record\n */\nexport function bindParameters(\n\tparameters?: SQLiteParameters,\n):\n\t| { readonly positional: readonly SQLiteValue[] }\n\t| { readonly named: Readonly<Record<string, SQLiteValue>> } {\n\tif (parameters === undefined) return { positional: [] }\n\tif (isArray<SQLiteValue>(parameters)) return { positional: parameters }\n\treturn { named: parameters }\n}\n","import type { StatementSync } from 'node:sqlite'\nimport type {\n\tSQLiteParameters,\n\tSQLiteRow,\n\tSQLiteRunResult,\n\tSQLiteStatementInterface,\n} from './types.js'\nimport { bindParameters, wrapError } from './helpers.js'\n\n/**\n * A prepared statement over `node:sqlite`'s `StatementSync` — the only way the\n * wrapper runs SQL.\n *\n * @remarks\n * Created by `database.prepare(sql)`. Bare named parameters are enabled on\n * construction so a record's keys bind without the SQL prefix character. Each\n * method binds the optional parameters (an array spread to `?` placeholders, a\n * record passed as a single named object) and executes synchronously, mapping any\n * native fault to a `SQLiteError`. Row values arrive as the native\n * {@link SQLiteRow} types; the typed layer above (the driver) imposes a precise\n * shape through a contract rather than re-narrowing here (AGENTS §14).\n */\nexport class SQLiteStatement implements SQLiteStatementInterface {\n\treadonly #statement: StatementSync\n\n\tconstructor(statement: StatementSync) {\n\t\tthis.#statement = statement\n\t\tthis.#statement.setAllowBareNamedParameters(true)\n\t}\n\n\trun(parameters?: SQLiteParameters): SQLiteRunResult {\n\t\ttry {\n\t\t\tconst bound = bindParameters(parameters)\n\t\t\tconst result =\n\t\t\t\t'named' in bound\n\t\t\t\t\t? this.#statement.run(bound.named)\n\t\t\t\t\t: this.#statement.run(...bound.positional)\n\t\t\treturn { changes: Number(result.changes), rowid: Number(result.lastInsertRowid) }\n\t\t} catch (error) {\n\t\t\tthrow wrapError(error)\n\t\t}\n\t}\n\n\tget(parameters?: SQLiteParameters): SQLiteRow | undefined {\n\t\ttry {\n\t\t\tconst bound = bindParameters(parameters)\n\t\t\treturn 'named' in bound\n\t\t\t\t? this.#statement.get(bound.named)\n\t\t\t\t: this.#statement.get(...bound.positional)\n\t\t} catch (error) {\n\t\t\tthrow wrapError(error)\n\t\t}\n\t}\n\n\tall(parameters?: SQLiteParameters): readonly SQLiteRow[] {\n\t\ttry {\n\t\t\tconst bound = bindParameters(parameters)\n\t\t\treturn 'named' in bound\n\t\t\t\t? this.#statement.all(bound.named)\n\t\t\t\t: this.#statement.all(...bound.positional)\n\t\t} catch (error) {\n\t\t\tthrow wrapError(error)\n\t\t}\n\t}\n\n\titerate(parameters?: SQLiteParameters): IterableIterator<SQLiteRow> {\n\t\ttry {\n\t\t\tconst bound = bindParameters(parameters)\n\t\t\treturn 'named' in bound\n\t\t\t\t? this.#statement.iterate(bound.named)\n\t\t\t\t: this.#statement.iterate(...bound.positional)\n\t\t} catch (error) {\n\t\t\tthrow wrapError(error)\n\t\t}\n\t}\n}\n","import { DatabaseSync } from 'node:sqlite'\nimport type {\n\tSQLiteDatabaseInterface,\n\tSQLiteDatabaseOptions,\n\tSQLiteStatementInterface,\n\tSQLiteValue,\n} from './types.js'\nimport { SQLiteError } from './errors.js'\nimport { wrapError } from './helpers.js'\nimport { SQLiteStatement } from './SQLiteStatement.js'\n\n/**\n * A synchronous SQLite database over `node:sqlite`'s `DatabaseSync`.\n *\n * @remarks\n * Created by `createSQLiteDatabase`. It connects lazily (`connect` opens the\n * underlying `DatabaseSync`, idempotent) and every operation routes through a\n * private gate that throws a `CLOSED` `SQLiteError` before `connect` or after\n * `close`. `exec` runs result-less SQL; `prepare` compiles a `SQLiteStatement`;\n * `transaction` wraps a scope in `BEGIN` / `COMMIT`, rolling back on a throw;\n * `pragma` reads (or sets then reads) one PRAGMA value — `name` is trusted\n * internal use only, never untrusted input, since pragma names cannot be bound\n * as parameters. A native fault surfaces as a `SQLiteError` mapped at the\n * boundary.\n */\nexport class SQLiteDatabase implements SQLiteDatabaseInterface {\n\treadonly #path: string\n\treadonly #readonly: boolean | undefined\n\treadonly #timeout: number | undefined\n\treadonly #foreignKeys: boolean | undefined\n\treadonly #bigints: boolean | undefined\n\t#database: DatabaseSync | undefined\n\n\tconstructor(options: SQLiteDatabaseOptions) {\n\t\tthis.#path = options.path ?? ':memory:'\n\t\tthis.#readonly = options.readonly\n\t\tthis.#timeout = options.timeout\n\t\tthis.#foreignKeys = options.foreignKeys\n\t\tthis.#bigints = options.bigints\n\t}\n\n\tget path(): string {\n\t\treturn this.#path\n\t}\n\n\tget connected(): boolean {\n\t\treturn this.#database !== undefined\n\t}\n\n\tconnect(): void {\n\t\tif (this.#database === undefined) {\n\t\t\tthis.#database = new DatabaseSync(this.#path, {\n\t\t\t\treadOnly: this.#readonly,\n\t\t\t\ttimeout: this.#timeout,\n\t\t\t\tenableForeignKeyConstraints: this.#foreignKeys,\n\t\t\t\treadBigInts: this.#bigints,\n\t\t\t})\n\t\t}\n\t}\n\n\tclose(): void {\n\t\tthis.#database?.close()\n\t\tthis.#database = undefined\n\t}\n\n\t/** Close the connection — enables `using db = createSQLiteDatabase(...)`. */\n\t[Symbol.dispose](): void {\n\t\tthis.close()\n\t}\n\n\texec(sql: string): void {\n\t\ttry {\n\t\t\tthis.#require().exec(sql)\n\t\t} catch (error) {\n\t\t\tthrow wrapError(error)\n\t\t}\n\t}\n\n\tprepare(sql: string): SQLiteStatementInterface {\n\t\ttry {\n\t\t\treturn new SQLiteStatement(this.#require().prepare(sql))\n\t\t} catch (error) {\n\t\t\tthrow wrapError(error)\n\t\t}\n\t}\n\n\ttransaction<R>(scope: () => R): R {\n\t\tthis.exec('BEGIN')\n\t\ttry {\n\t\t\tconst result = scope()\n\t\t\tthis.exec('COMMIT')\n\t\t\treturn result\n\t\t} catch (error) {\n\t\t\t// A ROLLBACK fault (e.g. the database was closed by the scope) must never\n\t\t\t// mask the scope's own error — the caller needs to see why it failed.\n\t\t\ttry {\n\t\t\t\tthis.exec('ROLLBACK')\n\t\t\t} catch {\n\t\t\t\t// swallowed — the original `error` is what the caller needs to see\n\t\t\t}\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t// Pragmas cannot use bind parameters, so the value is interpolated — safe for\n\t// the trusted internal names this layer is called with.\n\tpragma(name: string, value?: string | number): SQLiteValue | undefined {\n\t\tif (value !== undefined) this.exec('PRAGMA ' + name + ' = ' + value)\n\t\tconst row = this.prepare('PRAGMA ' + name).get()\n\t\treturn row ? Object.values(row)[0] : undefined\n\t}\n\n\t// The single connection gate — every operation requires an open database.\n\t#require(): DatabaseSync {\n\t\tif (this.#database === undefined) {\n\t\t\tthrow new SQLiteError('CLOSED', 'Database is not connected — call connect() first')\n\t\t}\n\t\treturn this.#database\n\t}\n}\n","import type { SQLiteDatabaseInterface, SQLiteDatabaseOptions } from './types.js'\nimport { SQLiteDatabase } from './SQLiteDatabase.js'\n\n/**\n * Create a synchronous SQLite database over `node:sqlite`.\n *\n * @remarks\n * The wrapper connects lazily — call `connect` (or it is required by the first\n * operation, which throws `CLOSED` until then). This is the lower-level native\n * handle a higher-level typed database engine would be built on; here it ships\n * as the standalone, server-native SQLite surface.\n *\n * @param options - The database `path` (a file path, or `':memory:'` by default)\n * @returns A typed {@link SQLiteDatabaseInterface}\n *\n * @example\n * ```ts\n * import { createSQLiteDatabase } from '@src/server'\n *\n * const db = createSQLiteDatabase({ path: ':memory:' })\n * db.connect()\n * db.exec('CREATE TABLE users (id TEXT PRIMARY KEY, name TEXT)')\n * db.prepare('INSERT INTO users VALUES (?, ?)').run(['u1', 'Ada'])\n * db.prepare('SELECT name FROM users WHERE id = ?').get(['u1']) // { name: 'Ada' }\n * ```\n */\nexport function createSQLiteDatabase(options?: SQLiteDatabaseOptions): SQLiteDatabaseInterface {\n\treturn new SQLiteDatabase(options ?? {})\n}\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
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';
@@ -0,0 +1,101 @@
1
+ /**
2
+ * A value SQLite stores and returns natively — the SQL ↔ JS bridge.
3
+ *
4
+ * @remarks
5
+ * `node:sqlite` maps `NULL` / `INTEGER` / `REAL` / `TEXT` / `BLOB` to exactly
6
+ * these JS types (integers arrive as `number`, or `bigint` only past 2^53).
7
+ */
8
+ export type SQLiteValue = null | number | bigint | string | Uint8Array;
9
+ /** A result row — a record of column name to {@link SQLiteValue}. */
10
+ export type SQLiteRow = Record<string, SQLiteValue>;
11
+ /**
12
+ * Bind parameters for a prepared statement — positional (an array, bound to `?`)
13
+ * or named (a record, bound to bare `:name` placeholders).
14
+ */
15
+ export type SQLiteParameters = readonly SQLiteValue[] | Readonly<Record<string, SQLiteValue>>;
16
+ /** The outcome of a non-query statement (`INSERT` / `UPDATE` / `DELETE` / DDL). */
17
+ export interface SQLiteRunResult {
18
+ readonly changes: number;
19
+ readonly rowid: number;
20
+ }
21
+ /**
22
+ * A machine-readable {@link SQLiteError} code.
23
+ *
24
+ * @remarks
25
+ * `'BUSY'` is retryable — it means a locked database was still held by another
26
+ * connection when the `timeout` (see {@link SQLiteDatabaseOptions}) elapsed; a
27
+ * caller may retry the operation, typically after backing off briefly.
28
+ */
29
+ export type SQLiteErrorCode = 'CLOSED' | 'CONSTRAINT' | 'BUSY' | 'UNKNOWN';
30
+ /**
31
+ * Options for `createSQLiteDatabase`.
32
+ *
33
+ * @remarks
34
+ * `path` is the database file path, or the special name `':memory:'` for an
35
+ * in-memory database (the default when omitted). `readonly` opens the
36
+ * connection read-only (native `readOnly`) — an absent file fails to open
37
+ * rather than being created. `timeout` is the busy-timeout in milliseconds
38
+ * (native `timeout`) — how long SQLite retries a locked database before
39
+ * failing with a `BUSY` {@link SQLiteError}; defaults to `0` (fail
40
+ * immediately) when omitted. `foreignKeys` enables foreign-key constraint
41
+ * enforcement (native `enableForeignKeyConstraints`); `node:sqlite` defaults
42
+ * this to `true` when omitted. `bigints` reads `INTEGER` columns back as
43
+ * `bigint` (native `readBigInts`) — writes already accept `bigint` regardless
44
+ * of this option, so a stored integer beyond `Number.MAX_SAFE_INTEGER` throws
45
+ * on read unless `bigints` is enabled; enabling it returns EVERY integer
46
+ * column as `bigint`, not just out-of-range ones, closing that read/write
47
+ * asymmetry at the cost of `bigint` values for ordinary small integers too.
48
+ */
49
+ export interface SQLiteDatabaseOptions {
50
+ readonly path?: string;
51
+ readonly readonly?: boolean;
52
+ readonly timeout?: number;
53
+ readonly foreignKeys?: boolean;
54
+ readonly bigints?: boolean;
55
+ }
56
+ /**
57
+ * A prepared statement — the only way the wrapper runs SQL (no query DSL; the
58
+ * core database layer owns querying, exactly as the IndexedDB wrapper does).
59
+ *
60
+ * @remarks
61
+ * Reached through `database.prepare(sql)`. Each method binds the optional
62
+ * `parameters` (an array spread to positional `?` placeholders, or a record bound
63
+ * to bare named placeholders) and executes synchronously: `run` for a non-query,
64
+ * `get` for the first row, `all` for every row, `iterate` for a lazy stream. A
65
+ * native fault surfaces as a {@link SQLiteError}.
66
+ */
67
+ export interface SQLiteStatementInterface {
68
+ run(parameters?: SQLiteParameters): SQLiteRunResult;
69
+ get(parameters?: SQLiteParameters): SQLiteRow | undefined;
70
+ all(parameters?: SQLiteParameters): readonly SQLiteRow[];
71
+ iterate(parameters?: SQLiteParameters): IterableIterator<SQLiteRow>;
72
+ }
73
+ /**
74
+ * A synchronous SQLite database over `node:sqlite`'s `DatabaseSync` — a lean,
75
+ * typed, zero-dependency layer exposing prepared statements, transactions, and
76
+ * pragmas. Synchronous because `node:sqlite` is; the SQLite *driver* (Chunk 3)
77
+ * adapts it to the async `DriverInterface`.
78
+ *
79
+ * @remarks
80
+ * Connects lazily — `connect` opens the underlying `DatabaseSync` (idempotent),
81
+ * and every operation requires an open connection, throwing a `CLOSED`
82
+ * {@link SQLiteError} before `connect` or after `close`. `exec` runs SQL with no
83
+ * results (DDL, pragmas); `prepare` compiles a {@link SQLiteStatementInterface};
84
+ * `transaction` runs a scope between `BEGIN` and `COMMIT`, rolling back on a
85
+ * throw; `pragma` reads (or sets then reads) a single PRAGMA value — `name` is
86
+ * trusted internal use only, never untrusted input, since pragma names cannot
87
+ * be bound as parameters. `[Symbol.dispose]` closes the connection (same as
88
+ * `close`), enabling `using` to release it deterministically at the end of a
89
+ * block.
90
+ */
91
+ export interface SQLiteDatabaseInterface {
92
+ readonly path: string;
93
+ readonly connected: boolean;
94
+ connect(): void;
95
+ close(): void;
96
+ exec(sql: string): void;
97
+ prepare(sql: string): SQLiteStatementInterface;
98
+ transaction<R>(scope: () => R): R;
99
+ pragma(name: string, value?: string | number): SQLiteValue | undefined;
100
+ [Symbol.dispose](): void;
101
+ }
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@orkestrel/sqlite",
3
+ "version": "0.0.1",
4
+ "description": "A typed, synchronous SQLite wrapper for the @orkestrel line — prepared statements over node:sqlite. Part of the @orkestrel line.",
5
+ "keywords": [
6
+ "database",
7
+ "node",
8
+ "node:sqlite",
9
+ "server",
10
+ "sqlite",
11
+ "typescript"
12
+ ],
13
+ "homepage": "https://github.com/orkestrel/sqlite#readme",
14
+ "bugs": "https://github.com/orkestrel/sqlite/issues",
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/orkestrel/sqlite.git"
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "README.md"
23
+ ],
24
+ "type": "module",
25
+ "sideEffects": false,
26
+ "main": "./dist/src/server/index.cjs",
27
+ "types": "./dist/src/server/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/src/server/index.d.ts",
31
+ "import": "./dist/src/server/index.cjs",
32
+ "require": "./dist/src/server/index.cjs",
33
+ "default": "./dist/src/server/index.cjs"
34
+ },
35
+ "./package.json": "./package.json"
36
+ },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "scripts": {
41
+ "clean": "node -e \"try{require('node:fs').rmSync('dist',{recursive:true,force:true})}catch{}\"",
42
+ "copy": "node -e \"const fs=require('node:fs'),p=require('node:path'),a=process.argv[1],b=process.argv[2];fs.mkdirSync(p.dirname(b),{recursive:true});fs.cpSync(a,b,{force:true});console.log('Copied: '+a+' to '+b)\"",
43
+ "tmp:txt": "node -e \"const fs=require('node:fs'),p=require('node:path');function walk(d){for(const e of fs.readdirSync(d,{withFileTypes:true})){const f=p.join(d,e.name);if(e.isDirectory()){walk(f)}else if(!e.name.endsWith('.md')&&!e.name.endsWith('.txt')){const t=f+'.txt';if(!fs.existsSync(t)){fs.renameSync(f,t)}else{console.warn('Skipping '+f+' — target exists: '+t)}}}}try{walk('tmp')}catch(e){if(e.code!=='ENOENT')throw e}\"",
44
+ "lint": "oxlint --config .oxlintrc.json --fix .",
45
+ "check": "tsc --noEmit --project tsconfig.json",
46
+ "check:src": "npm run check:src:server",
47
+ "check:src:server": "tsc --noEmit -p configs/src/tsconfig.server.json",
48
+ "format": "oxfmt --config .oxfmtrc.json --write .",
49
+ "format:check": "oxfmt --config .oxfmtrc.json --check .",
50
+ "lint:check": "oxlint --config .oxlintrc.json .",
51
+ "test": "npm run test:src && npm run test:guides",
52
+ "test:src": "vitest run --config vite.config.ts --no-cache --reporter=dot --project src:server",
53
+ "test:src:server": "vitest run --config vite.config.ts --no-cache --reporter=dot --project src:server",
54
+ "test:guides": "vitest run --config vite.config.ts --reporter=dot --project guides",
55
+ "build": "npm run clean && npm run build:src",
56
+ "build:src": "npm run build:src:server",
57
+ "build:src:server": "vite build --config configs/src/vite.server.config.ts && tsc -p configs/src/tsconfig.server.json",
58
+ "prepublishOnly": "npm run format:check && npm run lint:check && npm run check && npm run check:src && npm run build && npm test"
59
+ },
60
+ "dependencies": {
61
+ "@orkestrel/contract": "^0.0.1"
62
+ },
63
+ "devDependencies": {
64
+ "@orkestrel/guide": "^0.0.1",
65
+ "@types/node": "^26.1.1",
66
+ "oxfmt": "^0.58.0",
67
+ "oxlint": "^1.73.0",
68
+ "typescript": "^6.0.3",
69
+ "vite": "^8.1.4",
70
+ "vitest": "^4.1.10"
71
+ },
72
+ "engines": {
73
+ "node": ">=24"
74
+ }
75
+ }