routeflow-api 1.0.0 → 1.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.
- package/dist/sqlite.cjs +273 -0
- package/dist/sqlite.cjs.map +1 -0
- package/dist/sqlite.d.cts +197 -0
- package/dist/sqlite.d.ts +197 -0
- package/dist/sqlite.js +245 -0
- package/dist/sqlite.js.map +1 -0
- package/package.json +8 -3
package/dist/sqlite.cjs
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/sqlite.ts
|
|
21
|
+
var sqlite_exports = {};
|
|
22
|
+
__export(sqlite_exports, {
|
|
23
|
+
RouteStore: () => RouteStore,
|
|
24
|
+
SQLiteStore: () => SQLiteStore
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(sqlite_exports);
|
|
27
|
+
|
|
28
|
+
// src/core/adapter/sqlite-store.ts
|
|
29
|
+
var import_node_sqlite = require("node:sqlite");
|
|
30
|
+
var import_node_fs = require("fs");
|
|
31
|
+
var import_node_path = require("path");
|
|
32
|
+
var SQLiteStore = class {
|
|
33
|
+
db;
|
|
34
|
+
path;
|
|
35
|
+
/**
|
|
36
|
+
* @param dbPath - Path to the SQLite file. Parent directories are created automatically.
|
|
37
|
+
* Relative paths are resolved from `process.cwd()`.
|
|
38
|
+
*/
|
|
39
|
+
constructor(dbPath) {
|
|
40
|
+
this.path = (0, import_node_path.resolve)(dbPath);
|
|
41
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(this.path), { recursive: true });
|
|
42
|
+
this.db = new import_node_sqlite.DatabaseSync(this.path);
|
|
43
|
+
}
|
|
44
|
+
/** Execute one or more SQL statements (no return value). */
|
|
45
|
+
exec(sql) {
|
|
46
|
+
this.db.exec(sql);
|
|
47
|
+
}
|
|
48
|
+
/** Prepare a statement for repeated use. */
|
|
49
|
+
prepare(sql) {
|
|
50
|
+
return this.db.prepare(sql);
|
|
51
|
+
}
|
|
52
|
+
/** Close the database connection. */
|
|
53
|
+
close() {
|
|
54
|
+
this.db.close();
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// src/core/adapter/route-store.ts
|
|
59
|
+
var RouteTable = class {
|
|
60
|
+
constructor(db, tableName, schema, emit) {
|
|
61
|
+
this.db = db;
|
|
62
|
+
this.tableName = tableName;
|
|
63
|
+
this.schema = schema;
|
|
64
|
+
this.emit = emit;
|
|
65
|
+
this.jsonCols = new Set(
|
|
66
|
+
Object.entries(schema).filter(([, t]) => t === "json").map(([k]) => k)
|
|
67
|
+
);
|
|
68
|
+
this.createTable();
|
|
69
|
+
}
|
|
70
|
+
db;
|
|
71
|
+
tableName;
|
|
72
|
+
schema;
|
|
73
|
+
emit;
|
|
74
|
+
jsonCols;
|
|
75
|
+
// ── Schema bootstrap ──────────────────────────────────────────────────────
|
|
76
|
+
createTable() {
|
|
77
|
+
const cols = Object.entries(this.schema).map(([col, type]) => {
|
|
78
|
+
const sqlType = type === "json" ? "TEXT" : type.toUpperCase();
|
|
79
|
+
return `${col} ${sqlType}`;
|
|
80
|
+
}).join(", ");
|
|
81
|
+
this.db.exec(
|
|
82
|
+
`CREATE TABLE IF NOT EXISTS "${this.tableName}" (id INTEGER PRIMARY KEY AUTOINCREMENT, ${cols})`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
// ── Serialization helpers ─────────────────────────────────────────────────
|
|
86
|
+
serialize(data) {
|
|
87
|
+
const out = {};
|
|
88
|
+
for (const [k, v] of Object.entries(data)) {
|
|
89
|
+
out[k] = this.jsonCols.has(k) ? JSON.stringify(v) : v;
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
deserialize(row) {
|
|
94
|
+
const out = {};
|
|
95
|
+
for (const [k, v] of Object.entries(row)) {
|
|
96
|
+
out[k] = this.jsonCols.has(k) && typeof v === "string" ? JSON.parse(v) : v;
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
// ── CRUD ──────────────────────────────────────────────────────────────────
|
|
101
|
+
/**
|
|
102
|
+
* Return all rows, with optional filtering, sorting, and limiting.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* await items.list()
|
|
107
|
+
* await items.list({ where: { status: 'active' }, orderBy: 'createdAt', order: 'desc', limit: 10 })
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
async list(options = {}) {
|
|
111
|
+
const { where, orderBy = "id", order = "asc", limit } = options;
|
|
112
|
+
const parts = [];
|
|
113
|
+
const values = [];
|
|
114
|
+
if (where) {
|
|
115
|
+
for (const [k, v] of Object.entries(where)) {
|
|
116
|
+
parts.push(`${k} = ?`);
|
|
117
|
+
values.push(this.jsonCols.has(k) ? JSON.stringify(v) : v);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const whereClause = parts.length ? `WHERE ${parts.join(" AND ")}` : "";
|
|
121
|
+
const orderClause = `ORDER BY ${String(orderBy)} ${order.toUpperCase()}`;
|
|
122
|
+
const limitClause = limit != null ? `LIMIT ${limit}` : "";
|
|
123
|
+
const sql = `SELECT * FROM "${this.tableName}" ${whereClause} ${orderClause} ${limitClause}`.trim();
|
|
124
|
+
const rows = this.db.prepare(sql).all(...values);
|
|
125
|
+
return rows.map((r) => this.deserialize(r));
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Return a single row by `id`, or `null` if not found.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```ts
|
|
132
|
+
* const item = await items.get(1)
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
async get(id) {
|
|
136
|
+
const row = this.db.prepare(`SELECT * FROM "${this.tableName}" WHERE id = ?`).get(id);
|
|
137
|
+
return row ? this.deserialize(row) : null;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Insert a new row and emit an `INSERT` change event.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* const item = await items.create({ name: 'Apple', createdAt: new Date().toISOString() })
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
async create(data) {
|
|
148
|
+
const serialized = this.serialize(data);
|
|
149
|
+
const cols = Object.keys(serialized).join(", ");
|
|
150
|
+
const placeholders = Object.keys(serialized).map(() => "?").join(", ");
|
|
151
|
+
const result = this.db.prepare(`INSERT INTO "${this.tableName}" (${cols}) VALUES (${placeholders})`).run(
|
|
152
|
+
...Object.values(serialized)
|
|
153
|
+
);
|
|
154
|
+
const created = await this.get(result.lastInsertRowid);
|
|
155
|
+
this.emit({ table: this.tableName, operation: "INSERT", newRow: created, oldRow: null, timestamp: Date.now() });
|
|
156
|
+
return created;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Update columns on an existing row and emit an `UPDATE` change event.
|
|
160
|
+
* Returns the updated row, or `null` if the `id` does not exist.
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* ```ts
|
|
164
|
+
* const updated = await items.update(1, { name: 'Mango' })
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
async update(id, data) {
|
|
168
|
+
const old = await this.get(id);
|
|
169
|
+
if (!old) return null;
|
|
170
|
+
const serialized = this.serialize(data);
|
|
171
|
+
const setClauses = Object.keys(serialized).map((k) => `${k} = ?`).join(", ");
|
|
172
|
+
this.db.prepare(`UPDATE "${this.tableName}" SET ${setClauses} WHERE id = ?`).run(
|
|
173
|
+
...Object.values(serialized),
|
|
174
|
+
id
|
|
175
|
+
);
|
|
176
|
+
const updated = await this.get(id);
|
|
177
|
+
this.emit({ table: this.tableName, operation: "UPDATE", newRow: updated, oldRow: old, timestamp: Date.now() });
|
|
178
|
+
return updated;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Delete a row by `id` and emit a `DELETE` change event.
|
|
182
|
+
* Returns `true` if a row was deleted, `false` if `id` was not found.
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```ts
|
|
186
|
+
* await items.delete(1)
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
async delete(id) {
|
|
190
|
+
const old = await this.get(id);
|
|
191
|
+
if (!old) return false;
|
|
192
|
+
this.db.prepare(`DELETE FROM "${this.tableName}" WHERE id = ?`).run(id);
|
|
193
|
+
this.emit({ table: this.tableName, operation: "DELETE", newRow: null, oldRow: old, timestamp: Date.now() });
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Seed the table with initial rows if it is empty.
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```ts
|
|
201
|
+
* await items.seed([
|
|
202
|
+
* { name: 'Apple', createdAt: '2026-01-01T00:00:00.000Z' },
|
|
203
|
+
* ])
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
async seed(rows) {
|
|
207
|
+
const count = this.db.prepare(`SELECT COUNT(*) as n FROM "${this.tableName}"`).get().n;
|
|
208
|
+
if (count > 0) return;
|
|
209
|
+
for (const row of rows) {
|
|
210
|
+
await this.create(row);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
var RouteStore = class {
|
|
215
|
+
sql;
|
|
216
|
+
listeners = /* @__PURE__ */ new Map();
|
|
217
|
+
connected = false;
|
|
218
|
+
/**
|
|
219
|
+
* @param dbPath - Path to the SQLite file.
|
|
220
|
+
* Parent directories are created automatically.
|
|
221
|
+
* Relative paths resolve from `process.cwd()`.
|
|
222
|
+
*/
|
|
223
|
+
constructor(dbPath) {
|
|
224
|
+
this.sql = new SQLiteStore(dbPath);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Define a table and return a typed CRUD handle.
|
|
228
|
+
* The table is created in SQLite automatically if it does not exist.
|
|
229
|
+
*
|
|
230
|
+
* @param name - Table name
|
|
231
|
+
* @param schema - Column definitions: `{ columnName: 'integer' | 'text' | 'real' | 'json' }`
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```ts
|
|
235
|
+
* const tasks = db.table('tasks', {
|
|
236
|
+
* title: 'text',
|
|
237
|
+
* done: 'integer', // 0 | 1
|
|
238
|
+
* metadata: 'json', // auto-serialised JS object
|
|
239
|
+
* })
|
|
240
|
+
* ```
|
|
241
|
+
*/
|
|
242
|
+
table(name, schema) {
|
|
243
|
+
return new RouteTable(this.sql, name, schema, (evt) => this.dispatchEvent(evt));
|
|
244
|
+
}
|
|
245
|
+
// ── DatabaseAdapter ───────────────────────────────────────────────────────
|
|
246
|
+
async connect() {
|
|
247
|
+
this.connected = true;
|
|
248
|
+
}
|
|
249
|
+
async disconnect() {
|
|
250
|
+
this.sql.close();
|
|
251
|
+
this.connected = false;
|
|
252
|
+
}
|
|
253
|
+
onChange(table, callback) {
|
|
254
|
+
if (!this.listeners.has(table)) this.listeners.set(table, /* @__PURE__ */ new Set());
|
|
255
|
+
this.listeners.get(table).add(callback);
|
|
256
|
+
return () => this.listeners.get(table)?.delete(callback);
|
|
257
|
+
}
|
|
258
|
+
get isConnected() {
|
|
259
|
+
return this.connected;
|
|
260
|
+
}
|
|
261
|
+
// ── Internal ──────────────────────────────────────────────────────────────
|
|
262
|
+
dispatchEvent(event) {
|
|
263
|
+
const cbs = this.listeners.get(event.table);
|
|
264
|
+
if (!cbs) return;
|
|
265
|
+
for (const cb of cbs) cb(event);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
269
|
+
0 && (module.exports = {
|
|
270
|
+
RouteStore,
|
|
271
|
+
SQLiteStore
|
|
272
|
+
});
|
|
273
|
+
//# sourceMappingURL=sqlite.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sqlite.ts","../src/core/adapter/sqlite-store.ts","../src/core/adapter/route-store.ts"],"sourcesContent":["export { RouteStore } from './core/adapter/route-store.js'\nexport type { SchemaDefinition, ColumnType, InferRow, ListOptions } from './core/adapter/route-store.js'\n\n// Low-level SQL access (advanced use)\nexport { SQLiteStore } from './core/adapter/sqlite-store.js'\nexport type { SqliteRow } from './core/adapter/sqlite-store.js'\n","import { DatabaseSync } from 'node:sqlite'\nimport { mkdirSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\n\n/**\n * A generic record type for SQLite rows.\n * Keys map to column names; values are SQLite-compatible primitives.\n */\nexport type SqliteRow = Record<string, string | number | null>\n\n/**\n * File-based key-value store backed by Node's built-in `node:sqlite`.\n * Requires Node.js 22.5+.\n *\n * Data survives server restarts because it is written to a file on disk.\n * The path can be set directly in code — no environment variable required.\n *\n * @example\n * ```ts\n * const store = new SQLiteStore('./data/app.db')\n * store.exec(`CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY, name TEXT)`)\n * store.prepare('INSERT INTO items (name) VALUES (?)').run('Apple')\n * const rows = store.prepare('SELECT * FROM items').all()\n * ```\n */\nexport class SQLiteStore {\n readonly db: DatabaseSync\n readonly path: string\n\n /**\n * @param dbPath - Path to the SQLite file. Parent directories are created automatically.\n * Relative paths are resolved from `process.cwd()`.\n */\n constructor(dbPath: string) {\n this.path = resolve(dbPath)\n mkdirSync(dirname(this.path), { recursive: true })\n this.db = new DatabaseSync(this.path)\n }\n\n /** Execute one or more SQL statements (no return value). */\n exec(sql: string): void {\n this.db.exec(sql)\n }\n\n /** Prepare a statement for repeated use. */\n prepare(sql: string): ReturnType<DatabaseSync['prepare']> {\n return this.db.prepare(sql)\n }\n\n /** Close the database connection. */\n close(): void {\n this.db.close()\n }\n}\n","import type { ChangeEvent, DatabaseAdapter } from '../types.js'\nimport { SQLiteStore } from './sqlite-store.js'\n\n// ────────────────────────────────────────────────────────────────────────────\n// Schema definition types\n// ────────────────────────────────────────────────────────────────────────────\n\n/** Supported column types for RouteStore tables. */\nexport type ColumnType = 'integer' | 'text' | 'real' | 'json'\n\n/** Schema definition: maps column names to their types. */\nexport type SchemaDefinition = Record<string, ColumnType>\n\n/** TypeScript row type inferred from a schema definition. */\nexport type InferRow<S extends SchemaDefinition> = {\n id: number\n} & {\n [K in keyof S]: S[K] extends 'integer'\n ? number\n : S[K] extends 'real'\n ? number\n : S[K] extends 'json'\n ? unknown\n : string\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// RouteTable — high-level table API\n// ────────────────────────────────────────────────────────────────────────────\n\nexport interface ListOptions<S extends SchemaDefinition> {\n /** Filter rows by matching column values. */\n where?: Partial<InferRow<S>>\n /** Sort by a column. Defaults to `'id'`. */\n orderBy?: keyof InferRow<S>\n /** Sort direction. Defaults to `'asc'`. */\n order?: 'asc' | 'desc'\n /** Max rows to return. */\n limit?: number\n}\n\n/**\n * High-level CRUD interface for a single SQLite table.\n *\n * Created via `RouteStore.table()` — do not instantiate directly.\n */\nexport class RouteTable<S extends SchemaDefinition> {\n private readonly jsonCols: Set<string>\n\n constructor(\n private readonly db: SQLiteStore,\n readonly tableName: string,\n private readonly schema: S,\n private readonly emit: (event: ChangeEvent) => void,\n ) {\n this.jsonCols = new Set(\n Object.entries(schema)\n .filter(([, t]) => t === 'json')\n .map(([k]) => k),\n )\n this.createTable()\n }\n\n // ── Schema bootstrap ──────────────────────────────────────────────────────\n\n private createTable(): void {\n const cols = Object.entries(this.schema)\n .map(([col, type]) => {\n const sqlType = type === 'json' ? 'TEXT' : type.toUpperCase()\n return `${col} ${sqlType}`\n })\n .join(', ')\n this.db.exec(\n `CREATE TABLE IF NOT EXISTS \"${this.tableName}\" (id INTEGER PRIMARY KEY AUTOINCREMENT, ${cols})`,\n )\n }\n\n // ── Serialization helpers ─────────────────────────────────────────────────\n\n private serialize(data: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(data)) {\n out[k] = this.jsonCols.has(k) ? JSON.stringify(v) : v\n }\n return out\n }\n\n private deserialize(row: Record<string, unknown>): InferRow<S> {\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(row)) {\n out[k] = this.jsonCols.has(k) && typeof v === 'string' ? JSON.parse(v) : v\n }\n return out as InferRow<S>\n }\n\n // ── CRUD ──────────────────────────────────────────────────────────────────\n\n /**\n * Return all rows, with optional filtering, sorting, and limiting.\n *\n * @example\n * ```ts\n * await items.list()\n * await items.list({ where: { status: 'active' }, orderBy: 'createdAt', order: 'desc', limit: 10 })\n * ```\n */\n async list(options: ListOptions<S> = {}): Promise<InferRow<S>[]> {\n const { where, orderBy = 'id', order = 'asc', limit } = options\n const parts: string[] = []\n const values: unknown[] = []\n\n if (where) {\n for (const [k, v] of Object.entries(where)) {\n parts.push(`${k} = ?`)\n values.push(this.jsonCols.has(k) ? JSON.stringify(v) : v)\n }\n }\n\n const whereClause = parts.length ? `WHERE ${parts.join(' AND ')}` : ''\n const orderClause = `ORDER BY ${String(orderBy)} ${order.toUpperCase()}`\n const limitClause = limit != null ? `LIMIT ${limit}` : ''\n\n const sql = `SELECT * FROM \"${this.tableName}\" ${whereClause} ${orderClause} ${limitClause}`.trim()\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const rows = (this.db.prepare(sql) as any).all(...values) as Record<string, unknown>[]\n return rows.map((r) => this.deserialize(r))\n }\n\n /**\n * Return a single row by `id`, or `null` if not found.\n *\n * @example\n * ```ts\n * const item = await items.get(1)\n * ```\n */\n async get(id: number): Promise<InferRow<S> | null> {\n const row = this.db\n .prepare(`SELECT * FROM \"${this.tableName}\" WHERE id = ?`)\n .get(id) as Record<string, unknown> | undefined\n return row ? this.deserialize(row) : null\n }\n\n /**\n * Insert a new row and emit an `INSERT` change event.\n *\n * @example\n * ```ts\n * const item = await items.create({ name: 'Apple', createdAt: new Date().toISOString() })\n * ```\n */\n async create(data: Omit<InferRow<S>, 'id'>): Promise<InferRow<S>> {\n const serialized = this.serialize(data as Record<string, unknown>)\n const cols = Object.keys(serialized).join(', ')\n const placeholders = Object.keys(serialized)\n .map(() => '?')\n .join(', ')\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = (this.db.prepare(`INSERT INTO \"${this.tableName}\" (${cols}) VALUES (${placeholders})`) as any).run(\n ...Object.values(serialized),\n ) as { lastInsertRowid: number }\n\n const created = (await this.get(result.lastInsertRowid))!\n this.emit({ table: this.tableName, operation: 'INSERT', newRow: created, oldRow: null, timestamp: Date.now() })\n return created\n }\n\n /**\n * Update columns on an existing row and emit an `UPDATE` change event.\n * Returns the updated row, or `null` if the `id` does not exist.\n *\n * @example\n * ```ts\n * const updated = await items.update(1, { name: 'Mango' })\n * ```\n */\n async update(id: number, data: Partial<Omit<InferRow<S>, 'id'>>): Promise<InferRow<S> | null> {\n const old = await this.get(id)\n if (!old) return null\n\n const serialized = this.serialize(data as Record<string, unknown>)\n const setClauses = Object.keys(serialized)\n .map((k) => `${k} = ?`)\n .join(', ')\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ;(this.db.prepare(`UPDATE \"${this.tableName}\" SET ${setClauses} WHERE id = ?`) as any).run(\n ...Object.values(serialized),\n id,\n )\n\n const updated = (await this.get(id))!\n this.emit({ table: this.tableName, operation: 'UPDATE', newRow: updated, oldRow: old, timestamp: Date.now() })\n return updated\n }\n\n /**\n * Delete a row by `id` and emit a `DELETE` change event.\n * Returns `true` if a row was deleted, `false` if `id` was not found.\n *\n * @example\n * ```ts\n * await items.delete(1)\n * ```\n */\n async delete(id: number): Promise<boolean> {\n const old = await this.get(id)\n if (!old) return false\n\n this.db.prepare(`DELETE FROM \"${this.tableName}\" WHERE id = ?`).run(id)\n this.emit({ table: this.tableName, operation: 'DELETE', newRow: null, oldRow: old, timestamp: Date.now() })\n return true\n }\n\n /**\n * Seed the table with initial rows if it is empty.\n *\n * @example\n * ```ts\n * await items.seed([\n * { name: 'Apple', createdAt: '2026-01-01T00:00:00.000Z' },\n * ])\n * ```\n */\n async seed(rows: Omit<InferRow<S>, 'id'>[]): Promise<void> {\n const count = (\n this.db.prepare(`SELECT COUNT(*) as n FROM \"${this.tableName}\"`).get() as { n: number }\n ).n\n if (count > 0) return\n for (const row of rows) {\n await this.create(row)\n }\n }\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// RouteStore — DatabaseAdapter + table factory\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * File-based SQLite store that implements `DatabaseAdapter`.\n *\n * Use it as the RouteFlow adapter **and** as your data access layer —\n * no separate adapter or manual `emit()` calls required.\n * Any mutation (`create`, `update`, `delete`) automatically fires\n * a `ChangeEvent` and triggers all `@Reactive` WebSocket pushes.\n *\n * Requires Node.js 22.5+ (built-in `node:sqlite`).\n *\n * @example\n * ```ts\n * import { RouteStore } from 'routeflow-api/sqlite'\n * import { createApp } from 'routeflow-api'\n *\n * const db = new RouteStore('./data/app.db')\n *\n * const items = db.table('items', {\n * name: 'text',\n * createdAt: 'text',\n * })\n *\n * await items.seed([{ name: 'Apple', createdAt: new Date().toISOString() }])\n *\n * // db IS the adapter — pass directly to createApp\n * const app = createApp({ adapter: db, port: 3000 })\n * ```\n */\nexport class RouteStore implements DatabaseAdapter {\n private readonly sql: SQLiteStore\n private readonly listeners = new Map<string, Set<(event: ChangeEvent) => void>>()\n private connected = false\n\n /**\n * @param dbPath - Path to the SQLite file.\n * Parent directories are created automatically.\n * Relative paths resolve from `process.cwd()`.\n */\n constructor(dbPath: string) {\n this.sql = new SQLiteStore(dbPath)\n }\n\n /**\n * Define a table and return a typed CRUD handle.\n * The table is created in SQLite automatically if it does not exist.\n *\n * @param name - Table name\n * @param schema - Column definitions: `{ columnName: 'integer' | 'text' | 'real' | 'json' }`\n *\n * @example\n * ```ts\n * const tasks = db.table('tasks', {\n * title: 'text',\n * done: 'integer', // 0 | 1\n * metadata: 'json', // auto-serialised JS object\n * })\n * ```\n */\n table<S extends SchemaDefinition>(name: string, schema: S): RouteTable<S> {\n return new RouteTable(this.sql, name, schema, (evt) => this.dispatchEvent(evt))\n }\n\n // ── DatabaseAdapter ───────────────────────────────────────────────────────\n\n async connect(): Promise<void> {\n this.connected = true\n }\n\n async disconnect(): Promise<void> {\n this.sql.close()\n this.connected = false\n }\n\n onChange(table: string, callback: (event: ChangeEvent) => void): () => void {\n if (!this.listeners.has(table)) this.listeners.set(table, new Set())\n this.listeners.get(table)!.add(callback)\n return () => this.listeners.get(table)?.delete(callback)\n }\n\n get isConnected(): boolean {\n return this.connected\n }\n\n // ── Internal ──────────────────────────────────────────────────────────────\n\n private dispatchEvent(event: ChangeEvent): void {\n const cbs = this.listeners.get(event.table)\n if (!cbs) return\n for (const cb of cbs) cb(event)\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { D as DatabaseAdapter, C as ChangeEvent } from './types-tPDla8AE.cjs';
|
|
2
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A generic record type for SQLite rows.
|
|
6
|
+
* Keys map to column names; values are SQLite-compatible primitives.
|
|
7
|
+
*/
|
|
8
|
+
type SqliteRow = Record<string, string | number | null>;
|
|
9
|
+
/**
|
|
10
|
+
* File-based key-value store backed by Node's built-in `node:sqlite`.
|
|
11
|
+
* Requires Node.js 22.5+.
|
|
12
|
+
*
|
|
13
|
+
* Data survives server restarts because it is written to a file on disk.
|
|
14
|
+
* The path can be set directly in code — no environment variable required.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const store = new SQLiteStore('./data/app.db')
|
|
19
|
+
* store.exec(`CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY, name TEXT)`)
|
|
20
|
+
* store.prepare('INSERT INTO items (name) VALUES (?)').run('Apple')
|
|
21
|
+
* const rows = store.prepare('SELECT * FROM items').all()
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
declare class SQLiteStore {
|
|
25
|
+
readonly db: DatabaseSync;
|
|
26
|
+
readonly path: string;
|
|
27
|
+
/**
|
|
28
|
+
* @param dbPath - Path to the SQLite file. Parent directories are created automatically.
|
|
29
|
+
* Relative paths are resolved from `process.cwd()`.
|
|
30
|
+
*/
|
|
31
|
+
constructor(dbPath: string);
|
|
32
|
+
/** Execute one or more SQL statements (no return value). */
|
|
33
|
+
exec(sql: string): void;
|
|
34
|
+
/** Prepare a statement for repeated use. */
|
|
35
|
+
prepare(sql: string): ReturnType<DatabaseSync['prepare']>;
|
|
36
|
+
/** Close the database connection. */
|
|
37
|
+
close(): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Supported column types for RouteStore tables. */
|
|
41
|
+
type ColumnType = 'integer' | 'text' | 'real' | 'json';
|
|
42
|
+
/** Schema definition: maps column names to their types. */
|
|
43
|
+
type SchemaDefinition = Record<string, ColumnType>;
|
|
44
|
+
/** TypeScript row type inferred from a schema definition. */
|
|
45
|
+
type InferRow<S extends SchemaDefinition> = {
|
|
46
|
+
id: number;
|
|
47
|
+
} & {
|
|
48
|
+
[K in keyof S]: S[K] extends 'integer' ? number : S[K] extends 'real' ? number : S[K] extends 'json' ? unknown : string;
|
|
49
|
+
};
|
|
50
|
+
interface ListOptions<S extends SchemaDefinition> {
|
|
51
|
+
/** Filter rows by matching column values. */
|
|
52
|
+
where?: Partial<InferRow<S>>;
|
|
53
|
+
/** Sort by a column. Defaults to `'id'`. */
|
|
54
|
+
orderBy?: keyof InferRow<S>;
|
|
55
|
+
/** Sort direction. Defaults to `'asc'`. */
|
|
56
|
+
order?: 'asc' | 'desc';
|
|
57
|
+
/** Max rows to return. */
|
|
58
|
+
limit?: number;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* High-level CRUD interface for a single SQLite table.
|
|
62
|
+
*
|
|
63
|
+
* Created via `RouteStore.table()` — do not instantiate directly.
|
|
64
|
+
*/
|
|
65
|
+
declare class RouteTable<S extends SchemaDefinition> {
|
|
66
|
+
private readonly db;
|
|
67
|
+
readonly tableName: string;
|
|
68
|
+
private readonly schema;
|
|
69
|
+
private readonly emit;
|
|
70
|
+
private readonly jsonCols;
|
|
71
|
+
constructor(db: SQLiteStore, tableName: string, schema: S, emit: (event: ChangeEvent) => void);
|
|
72
|
+
private createTable;
|
|
73
|
+
private serialize;
|
|
74
|
+
private deserialize;
|
|
75
|
+
/**
|
|
76
|
+
* Return all rows, with optional filtering, sorting, and limiting.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* await items.list()
|
|
81
|
+
* await items.list({ where: { status: 'active' }, orderBy: 'createdAt', order: 'desc', limit: 10 })
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
list(options?: ListOptions<S>): Promise<InferRow<S>[]>;
|
|
85
|
+
/**
|
|
86
|
+
* Return a single row by `id`, or `null` if not found.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* const item = await items.get(1)
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
get(id: number): Promise<InferRow<S> | null>;
|
|
94
|
+
/**
|
|
95
|
+
* Insert a new row and emit an `INSERT` change event.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* const item = await items.create({ name: 'Apple', createdAt: new Date().toISOString() })
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
create(data: Omit<InferRow<S>, 'id'>): Promise<InferRow<S>>;
|
|
103
|
+
/**
|
|
104
|
+
* Update columns on an existing row and emit an `UPDATE` change event.
|
|
105
|
+
* Returns the updated row, or `null` if the `id` does not exist.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```ts
|
|
109
|
+
* const updated = await items.update(1, { name: 'Mango' })
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
update(id: number, data: Partial<Omit<InferRow<S>, 'id'>>): Promise<InferRow<S> | null>;
|
|
113
|
+
/**
|
|
114
|
+
* Delete a row by `id` and emit a `DELETE` change event.
|
|
115
|
+
* Returns `true` if a row was deleted, `false` if `id` was not found.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* await items.delete(1)
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
delete(id: number): Promise<boolean>;
|
|
123
|
+
/**
|
|
124
|
+
* Seed the table with initial rows if it is empty.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* await items.seed([
|
|
129
|
+
* { name: 'Apple', createdAt: '2026-01-01T00:00:00.000Z' },
|
|
130
|
+
* ])
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
seed(rows: Omit<InferRow<S>, 'id'>[]): Promise<void>;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* File-based SQLite store that implements `DatabaseAdapter`.
|
|
137
|
+
*
|
|
138
|
+
* Use it as the RouteFlow adapter **and** as your data access layer —
|
|
139
|
+
* no separate adapter or manual `emit()` calls required.
|
|
140
|
+
* Any mutation (`create`, `update`, `delete`) automatically fires
|
|
141
|
+
* a `ChangeEvent` and triggers all `@Reactive` WebSocket pushes.
|
|
142
|
+
*
|
|
143
|
+
* Requires Node.js 22.5+ (built-in `node:sqlite`).
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```ts
|
|
147
|
+
* import { RouteStore } from 'routeflow-api/sqlite'
|
|
148
|
+
* import { createApp } from 'routeflow-api'
|
|
149
|
+
*
|
|
150
|
+
* const db = new RouteStore('./data/app.db')
|
|
151
|
+
*
|
|
152
|
+
* const items = db.table('items', {
|
|
153
|
+
* name: 'text',
|
|
154
|
+
* createdAt: 'text',
|
|
155
|
+
* })
|
|
156
|
+
*
|
|
157
|
+
* await items.seed([{ name: 'Apple', createdAt: new Date().toISOString() }])
|
|
158
|
+
*
|
|
159
|
+
* // db IS the adapter — pass directly to createApp
|
|
160
|
+
* const app = createApp({ adapter: db, port: 3000 })
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
declare class RouteStore implements DatabaseAdapter {
|
|
164
|
+
private readonly sql;
|
|
165
|
+
private readonly listeners;
|
|
166
|
+
private connected;
|
|
167
|
+
/**
|
|
168
|
+
* @param dbPath - Path to the SQLite file.
|
|
169
|
+
* Parent directories are created automatically.
|
|
170
|
+
* Relative paths resolve from `process.cwd()`.
|
|
171
|
+
*/
|
|
172
|
+
constructor(dbPath: string);
|
|
173
|
+
/**
|
|
174
|
+
* Define a table and return a typed CRUD handle.
|
|
175
|
+
* The table is created in SQLite automatically if it does not exist.
|
|
176
|
+
*
|
|
177
|
+
* @param name - Table name
|
|
178
|
+
* @param schema - Column definitions: `{ columnName: 'integer' | 'text' | 'real' | 'json' }`
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```ts
|
|
182
|
+
* const tasks = db.table('tasks', {
|
|
183
|
+
* title: 'text',
|
|
184
|
+
* done: 'integer', // 0 | 1
|
|
185
|
+
* metadata: 'json', // auto-serialised JS object
|
|
186
|
+
* })
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
table<S extends SchemaDefinition>(name: string, schema: S): RouteTable<S>;
|
|
190
|
+
connect(): Promise<void>;
|
|
191
|
+
disconnect(): Promise<void>;
|
|
192
|
+
onChange(table: string, callback: (event: ChangeEvent) => void): () => void;
|
|
193
|
+
get isConnected(): boolean;
|
|
194
|
+
private dispatchEvent;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export { type ColumnType, type InferRow, type ListOptions, RouteStore, SQLiteStore, type SchemaDefinition, type SqliteRow };
|
package/dist/sqlite.d.ts
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { D as DatabaseAdapter, C as ChangeEvent } from './types-tPDla8AE.js';
|
|
2
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A generic record type for SQLite rows.
|
|
6
|
+
* Keys map to column names; values are SQLite-compatible primitives.
|
|
7
|
+
*/
|
|
8
|
+
type SqliteRow = Record<string, string | number | null>;
|
|
9
|
+
/**
|
|
10
|
+
* File-based key-value store backed by Node's built-in `node:sqlite`.
|
|
11
|
+
* Requires Node.js 22.5+.
|
|
12
|
+
*
|
|
13
|
+
* Data survives server restarts because it is written to a file on disk.
|
|
14
|
+
* The path can be set directly in code — no environment variable required.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const store = new SQLiteStore('./data/app.db')
|
|
19
|
+
* store.exec(`CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY, name TEXT)`)
|
|
20
|
+
* store.prepare('INSERT INTO items (name) VALUES (?)').run('Apple')
|
|
21
|
+
* const rows = store.prepare('SELECT * FROM items').all()
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
declare class SQLiteStore {
|
|
25
|
+
readonly db: DatabaseSync;
|
|
26
|
+
readonly path: string;
|
|
27
|
+
/**
|
|
28
|
+
* @param dbPath - Path to the SQLite file. Parent directories are created automatically.
|
|
29
|
+
* Relative paths are resolved from `process.cwd()`.
|
|
30
|
+
*/
|
|
31
|
+
constructor(dbPath: string);
|
|
32
|
+
/** Execute one or more SQL statements (no return value). */
|
|
33
|
+
exec(sql: string): void;
|
|
34
|
+
/** Prepare a statement for repeated use. */
|
|
35
|
+
prepare(sql: string): ReturnType<DatabaseSync['prepare']>;
|
|
36
|
+
/** Close the database connection. */
|
|
37
|
+
close(): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Supported column types for RouteStore tables. */
|
|
41
|
+
type ColumnType = 'integer' | 'text' | 'real' | 'json';
|
|
42
|
+
/** Schema definition: maps column names to their types. */
|
|
43
|
+
type SchemaDefinition = Record<string, ColumnType>;
|
|
44
|
+
/** TypeScript row type inferred from a schema definition. */
|
|
45
|
+
type InferRow<S extends SchemaDefinition> = {
|
|
46
|
+
id: number;
|
|
47
|
+
} & {
|
|
48
|
+
[K in keyof S]: S[K] extends 'integer' ? number : S[K] extends 'real' ? number : S[K] extends 'json' ? unknown : string;
|
|
49
|
+
};
|
|
50
|
+
interface ListOptions<S extends SchemaDefinition> {
|
|
51
|
+
/** Filter rows by matching column values. */
|
|
52
|
+
where?: Partial<InferRow<S>>;
|
|
53
|
+
/** Sort by a column. Defaults to `'id'`. */
|
|
54
|
+
orderBy?: keyof InferRow<S>;
|
|
55
|
+
/** Sort direction. Defaults to `'asc'`. */
|
|
56
|
+
order?: 'asc' | 'desc';
|
|
57
|
+
/** Max rows to return. */
|
|
58
|
+
limit?: number;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* High-level CRUD interface for a single SQLite table.
|
|
62
|
+
*
|
|
63
|
+
* Created via `RouteStore.table()` — do not instantiate directly.
|
|
64
|
+
*/
|
|
65
|
+
declare class RouteTable<S extends SchemaDefinition> {
|
|
66
|
+
private readonly db;
|
|
67
|
+
readonly tableName: string;
|
|
68
|
+
private readonly schema;
|
|
69
|
+
private readonly emit;
|
|
70
|
+
private readonly jsonCols;
|
|
71
|
+
constructor(db: SQLiteStore, tableName: string, schema: S, emit: (event: ChangeEvent) => void);
|
|
72
|
+
private createTable;
|
|
73
|
+
private serialize;
|
|
74
|
+
private deserialize;
|
|
75
|
+
/**
|
|
76
|
+
* Return all rows, with optional filtering, sorting, and limiting.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* await items.list()
|
|
81
|
+
* await items.list({ where: { status: 'active' }, orderBy: 'createdAt', order: 'desc', limit: 10 })
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
list(options?: ListOptions<S>): Promise<InferRow<S>[]>;
|
|
85
|
+
/**
|
|
86
|
+
* Return a single row by `id`, or `null` if not found.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* const item = await items.get(1)
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
get(id: number): Promise<InferRow<S> | null>;
|
|
94
|
+
/**
|
|
95
|
+
* Insert a new row and emit an `INSERT` change event.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* const item = await items.create({ name: 'Apple', createdAt: new Date().toISOString() })
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
create(data: Omit<InferRow<S>, 'id'>): Promise<InferRow<S>>;
|
|
103
|
+
/**
|
|
104
|
+
* Update columns on an existing row and emit an `UPDATE` change event.
|
|
105
|
+
* Returns the updated row, or `null` if the `id` does not exist.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```ts
|
|
109
|
+
* const updated = await items.update(1, { name: 'Mango' })
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
update(id: number, data: Partial<Omit<InferRow<S>, 'id'>>): Promise<InferRow<S> | null>;
|
|
113
|
+
/**
|
|
114
|
+
* Delete a row by `id` and emit a `DELETE` change event.
|
|
115
|
+
* Returns `true` if a row was deleted, `false` if `id` was not found.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* await items.delete(1)
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
delete(id: number): Promise<boolean>;
|
|
123
|
+
/**
|
|
124
|
+
* Seed the table with initial rows if it is empty.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* await items.seed([
|
|
129
|
+
* { name: 'Apple', createdAt: '2026-01-01T00:00:00.000Z' },
|
|
130
|
+
* ])
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
seed(rows: Omit<InferRow<S>, 'id'>[]): Promise<void>;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* File-based SQLite store that implements `DatabaseAdapter`.
|
|
137
|
+
*
|
|
138
|
+
* Use it as the RouteFlow adapter **and** as your data access layer —
|
|
139
|
+
* no separate adapter or manual `emit()` calls required.
|
|
140
|
+
* Any mutation (`create`, `update`, `delete`) automatically fires
|
|
141
|
+
* a `ChangeEvent` and triggers all `@Reactive` WebSocket pushes.
|
|
142
|
+
*
|
|
143
|
+
* Requires Node.js 22.5+ (built-in `node:sqlite`).
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```ts
|
|
147
|
+
* import { RouteStore } from 'routeflow-api/sqlite'
|
|
148
|
+
* import { createApp } from 'routeflow-api'
|
|
149
|
+
*
|
|
150
|
+
* const db = new RouteStore('./data/app.db')
|
|
151
|
+
*
|
|
152
|
+
* const items = db.table('items', {
|
|
153
|
+
* name: 'text',
|
|
154
|
+
* createdAt: 'text',
|
|
155
|
+
* })
|
|
156
|
+
*
|
|
157
|
+
* await items.seed([{ name: 'Apple', createdAt: new Date().toISOString() }])
|
|
158
|
+
*
|
|
159
|
+
* // db IS the adapter — pass directly to createApp
|
|
160
|
+
* const app = createApp({ adapter: db, port: 3000 })
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
declare class RouteStore implements DatabaseAdapter {
|
|
164
|
+
private readonly sql;
|
|
165
|
+
private readonly listeners;
|
|
166
|
+
private connected;
|
|
167
|
+
/**
|
|
168
|
+
* @param dbPath - Path to the SQLite file.
|
|
169
|
+
* Parent directories are created automatically.
|
|
170
|
+
* Relative paths resolve from `process.cwd()`.
|
|
171
|
+
*/
|
|
172
|
+
constructor(dbPath: string);
|
|
173
|
+
/**
|
|
174
|
+
* Define a table and return a typed CRUD handle.
|
|
175
|
+
* The table is created in SQLite automatically if it does not exist.
|
|
176
|
+
*
|
|
177
|
+
* @param name - Table name
|
|
178
|
+
* @param schema - Column definitions: `{ columnName: 'integer' | 'text' | 'real' | 'json' }`
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```ts
|
|
182
|
+
* const tasks = db.table('tasks', {
|
|
183
|
+
* title: 'text',
|
|
184
|
+
* done: 'integer', // 0 | 1
|
|
185
|
+
* metadata: 'json', // auto-serialised JS object
|
|
186
|
+
* })
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
table<S extends SchemaDefinition>(name: string, schema: S): RouteTable<S>;
|
|
190
|
+
connect(): Promise<void>;
|
|
191
|
+
disconnect(): Promise<void>;
|
|
192
|
+
onChange(table: string, callback: (event: ChangeEvent) => void): () => void;
|
|
193
|
+
get isConnected(): boolean;
|
|
194
|
+
private dispatchEvent;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export { type ColumnType, type InferRow, type ListOptions, RouteStore, SQLiteStore, type SchemaDefinition, type SqliteRow };
|
package/dist/sqlite.js
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// src/core/adapter/sqlite-store.ts
|
|
2
|
+
import { DatabaseSync } from "node:sqlite";
|
|
3
|
+
import { mkdirSync } from "fs";
|
|
4
|
+
import { dirname, resolve } from "path";
|
|
5
|
+
var SQLiteStore = class {
|
|
6
|
+
db;
|
|
7
|
+
path;
|
|
8
|
+
/**
|
|
9
|
+
* @param dbPath - Path to the SQLite file. Parent directories are created automatically.
|
|
10
|
+
* Relative paths are resolved from `process.cwd()`.
|
|
11
|
+
*/
|
|
12
|
+
constructor(dbPath) {
|
|
13
|
+
this.path = resolve(dbPath);
|
|
14
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
15
|
+
this.db = new DatabaseSync(this.path);
|
|
16
|
+
}
|
|
17
|
+
/** Execute one or more SQL statements (no return value). */
|
|
18
|
+
exec(sql) {
|
|
19
|
+
this.db.exec(sql);
|
|
20
|
+
}
|
|
21
|
+
/** Prepare a statement for repeated use. */
|
|
22
|
+
prepare(sql) {
|
|
23
|
+
return this.db.prepare(sql);
|
|
24
|
+
}
|
|
25
|
+
/** Close the database connection. */
|
|
26
|
+
close() {
|
|
27
|
+
this.db.close();
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// src/core/adapter/route-store.ts
|
|
32
|
+
var RouteTable = class {
|
|
33
|
+
constructor(db, tableName, schema, emit) {
|
|
34
|
+
this.db = db;
|
|
35
|
+
this.tableName = tableName;
|
|
36
|
+
this.schema = schema;
|
|
37
|
+
this.emit = emit;
|
|
38
|
+
this.jsonCols = new Set(
|
|
39
|
+
Object.entries(schema).filter(([, t]) => t === "json").map(([k]) => k)
|
|
40
|
+
);
|
|
41
|
+
this.createTable();
|
|
42
|
+
}
|
|
43
|
+
db;
|
|
44
|
+
tableName;
|
|
45
|
+
schema;
|
|
46
|
+
emit;
|
|
47
|
+
jsonCols;
|
|
48
|
+
// ── Schema bootstrap ──────────────────────────────────────────────────────
|
|
49
|
+
createTable() {
|
|
50
|
+
const cols = Object.entries(this.schema).map(([col, type]) => {
|
|
51
|
+
const sqlType = type === "json" ? "TEXT" : type.toUpperCase();
|
|
52
|
+
return `${col} ${sqlType}`;
|
|
53
|
+
}).join(", ");
|
|
54
|
+
this.db.exec(
|
|
55
|
+
`CREATE TABLE IF NOT EXISTS "${this.tableName}" (id INTEGER PRIMARY KEY AUTOINCREMENT, ${cols})`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
// ── Serialization helpers ─────────────────────────────────────────────────
|
|
59
|
+
serialize(data) {
|
|
60
|
+
const out = {};
|
|
61
|
+
for (const [k, v] of Object.entries(data)) {
|
|
62
|
+
out[k] = this.jsonCols.has(k) ? JSON.stringify(v) : v;
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
deserialize(row) {
|
|
67
|
+
const out = {};
|
|
68
|
+
for (const [k, v] of Object.entries(row)) {
|
|
69
|
+
out[k] = this.jsonCols.has(k) && typeof v === "string" ? JSON.parse(v) : v;
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
// ── CRUD ──────────────────────────────────────────────────────────────────
|
|
74
|
+
/**
|
|
75
|
+
* Return all rows, with optional filtering, sorting, and limiting.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* await items.list()
|
|
80
|
+
* await items.list({ where: { status: 'active' }, orderBy: 'createdAt', order: 'desc', limit: 10 })
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
async list(options = {}) {
|
|
84
|
+
const { where, orderBy = "id", order = "asc", limit } = options;
|
|
85
|
+
const parts = [];
|
|
86
|
+
const values = [];
|
|
87
|
+
if (where) {
|
|
88
|
+
for (const [k, v] of Object.entries(where)) {
|
|
89
|
+
parts.push(`${k} = ?`);
|
|
90
|
+
values.push(this.jsonCols.has(k) ? JSON.stringify(v) : v);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const whereClause = parts.length ? `WHERE ${parts.join(" AND ")}` : "";
|
|
94
|
+
const orderClause = `ORDER BY ${String(orderBy)} ${order.toUpperCase()}`;
|
|
95
|
+
const limitClause = limit != null ? `LIMIT ${limit}` : "";
|
|
96
|
+
const sql = `SELECT * FROM "${this.tableName}" ${whereClause} ${orderClause} ${limitClause}`.trim();
|
|
97
|
+
const rows = this.db.prepare(sql).all(...values);
|
|
98
|
+
return rows.map((r) => this.deserialize(r));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Return a single row by `id`, or `null` if not found.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* const item = await items.get(1)
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
async get(id) {
|
|
109
|
+
const row = this.db.prepare(`SELECT * FROM "${this.tableName}" WHERE id = ?`).get(id);
|
|
110
|
+
return row ? this.deserialize(row) : null;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Insert a new row and emit an `INSERT` change event.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```ts
|
|
117
|
+
* const item = await items.create({ name: 'Apple', createdAt: new Date().toISOString() })
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
async create(data) {
|
|
121
|
+
const serialized = this.serialize(data);
|
|
122
|
+
const cols = Object.keys(serialized).join(", ");
|
|
123
|
+
const placeholders = Object.keys(serialized).map(() => "?").join(", ");
|
|
124
|
+
const result = this.db.prepare(`INSERT INTO "${this.tableName}" (${cols}) VALUES (${placeholders})`).run(
|
|
125
|
+
...Object.values(serialized)
|
|
126
|
+
);
|
|
127
|
+
const created = await this.get(result.lastInsertRowid);
|
|
128
|
+
this.emit({ table: this.tableName, operation: "INSERT", newRow: created, oldRow: null, timestamp: Date.now() });
|
|
129
|
+
return created;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Update columns on an existing row and emit an `UPDATE` change event.
|
|
133
|
+
* Returns the updated row, or `null` if the `id` does not exist.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* const updated = await items.update(1, { name: 'Mango' })
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
async update(id, data) {
|
|
141
|
+
const old = await this.get(id);
|
|
142
|
+
if (!old) return null;
|
|
143
|
+
const serialized = this.serialize(data);
|
|
144
|
+
const setClauses = Object.keys(serialized).map((k) => `${k} = ?`).join(", ");
|
|
145
|
+
this.db.prepare(`UPDATE "${this.tableName}" SET ${setClauses} WHERE id = ?`).run(
|
|
146
|
+
...Object.values(serialized),
|
|
147
|
+
id
|
|
148
|
+
);
|
|
149
|
+
const updated = await this.get(id);
|
|
150
|
+
this.emit({ table: this.tableName, operation: "UPDATE", newRow: updated, oldRow: old, timestamp: Date.now() });
|
|
151
|
+
return updated;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Delete a row by `id` and emit a `DELETE` change event.
|
|
155
|
+
* Returns `true` if a row was deleted, `false` if `id` was not found.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```ts
|
|
159
|
+
* await items.delete(1)
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
async delete(id) {
|
|
163
|
+
const old = await this.get(id);
|
|
164
|
+
if (!old) return false;
|
|
165
|
+
this.db.prepare(`DELETE FROM "${this.tableName}" WHERE id = ?`).run(id);
|
|
166
|
+
this.emit({ table: this.tableName, operation: "DELETE", newRow: null, oldRow: old, timestamp: Date.now() });
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Seed the table with initial rows if it is empty.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```ts
|
|
174
|
+
* await items.seed([
|
|
175
|
+
* { name: 'Apple', createdAt: '2026-01-01T00:00:00.000Z' },
|
|
176
|
+
* ])
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
async seed(rows) {
|
|
180
|
+
const count = this.db.prepare(`SELECT COUNT(*) as n FROM "${this.tableName}"`).get().n;
|
|
181
|
+
if (count > 0) return;
|
|
182
|
+
for (const row of rows) {
|
|
183
|
+
await this.create(row);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
var RouteStore = class {
|
|
188
|
+
sql;
|
|
189
|
+
listeners = /* @__PURE__ */ new Map();
|
|
190
|
+
connected = false;
|
|
191
|
+
/**
|
|
192
|
+
* @param dbPath - Path to the SQLite file.
|
|
193
|
+
* Parent directories are created automatically.
|
|
194
|
+
* Relative paths resolve from `process.cwd()`.
|
|
195
|
+
*/
|
|
196
|
+
constructor(dbPath) {
|
|
197
|
+
this.sql = new SQLiteStore(dbPath);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Define a table and return a typed CRUD handle.
|
|
201
|
+
* The table is created in SQLite automatically if it does not exist.
|
|
202
|
+
*
|
|
203
|
+
* @param name - Table name
|
|
204
|
+
* @param schema - Column definitions: `{ columnName: 'integer' | 'text' | 'real' | 'json' }`
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```ts
|
|
208
|
+
* const tasks = db.table('tasks', {
|
|
209
|
+
* title: 'text',
|
|
210
|
+
* done: 'integer', // 0 | 1
|
|
211
|
+
* metadata: 'json', // auto-serialised JS object
|
|
212
|
+
* })
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
table(name, schema) {
|
|
216
|
+
return new RouteTable(this.sql, name, schema, (evt) => this.dispatchEvent(evt));
|
|
217
|
+
}
|
|
218
|
+
// ── DatabaseAdapter ───────────────────────────────────────────────────────
|
|
219
|
+
async connect() {
|
|
220
|
+
this.connected = true;
|
|
221
|
+
}
|
|
222
|
+
async disconnect() {
|
|
223
|
+
this.sql.close();
|
|
224
|
+
this.connected = false;
|
|
225
|
+
}
|
|
226
|
+
onChange(table, callback) {
|
|
227
|
+
if (!this.listeners.has(table)) this.listeners.set(table, /* @__PURE__ */ new Set());
|
|
228
|
+
this.listeners.get(table).add(callback);
|
|
229
|
+
return () => this.listeners.get(table)?.delete(callback);
|
|
230
|
+
}
|
|
231
|
+
get isConnected() {
|
|
232
|
+
return this.connected;
|
|
233
|
+
}
|
|
234
|
+
// ── Internal ──────────────────────────────────────────────────────────────
|
|
235
|
+
dispatchEvent(event) {
|
|
236
|
+
const cbs = this.listeners.get(event.table);
|
|
237
|
+
if (!cbs) return;
|
|
238
|
+
for (const cb of cbs) cb(event);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
export {
|
|
242
|
+
RouteStore,
|
|
243
|
+
SQLiteStore
|
|
244
|
+
};
|
|
245
|
+
//# sourceMappingURL=sqlite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/adapter/sqlite-store.ts","../src/core/adapter/route-store.ts"],"sourcesContent":["import { DatabaseSync } from 'node:sqlite'\nimport { mkdirSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\n\n/**\n * A generic record type for SQLite rows.\n * Keys map to column names; values are SQLite-compatible primitives.\n */\nexport type SqliteRow = Record<string, string | number | null>\n\n/**\n * File-based key-value store backed by Node's built-in `node:sqlite`.\n * Requires Node.js 22.5+.\n *\n * Data survives server restarts because it is written to a file on disk.\n * The path can be set directly in code — no environment variable required.\n *\n * @example\n * ```ts\n * const store = new SQLiteStore('./data/app.db')\n * store.exec(`CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY, name TEXT)`)\n * store.prepare('INSERT INTO items (name) VALUES (?)').run('Apple')\n * const rows = store.prepare('SELECT * FROM items').all()\n * ```\n */\nexport class SQLiteStore {\n readonly db: DatabaseSync\n readonly path: string\n\n /**\n * @param dbPath - Path to the SQLite file. Parent directories are created automatically.\n * Relative paths are resolved from `process.cwd()`.\n */\n constructor(dbPath: string) {\n this.path = resolve(dbPath)\n mkdirSync(dirname(this.path), { recursive: true })\n this.db = new DatabaseSync(this.path)\n }\n\n /** Execute one or more SQL statements (no return value). */\n exec(sql: string): void {\n this.db.exec(sql)\n }\n\n /** Prepare a statement for repeated use. */\n prepare(sql: string): ReturnType<DatabaseSync['prepare']> {\n return this.db.prepare(sql)\n }\n\n /** Close the database connection. */\n close(): void {\n this.db.close()\n }\n}\n","import type { ChangeEvent, DatabaseAdapter } from '../types.js'\nimport { SQLiteStore } from './sqlite-store.js'\n\n// ────────────────────────────────────────────────────────────────────────────\n// Schema definition types\n// ────────────────────────────────────────────────────────────────────────────\n\n/** Supported column types for RouteStore tables. */\nexport type ColumnType = 'integer' | 'text' | 'real' | 'json'\n\n/** Schema definition: maps column names to their types. */\nexport type SchemaDefinition = Record<string, ColumnType>\n\n/** TypeScript row type inferred from a schema definition. */\nexport type InferRow<S extends SchemaDefinition> = {\n id: number\n} & {\n [K in keyof S]: S[K] extends 'integer'\n ? number\n : S[K] extends 'real'\n ? number\n : S[K] extends 'json'\n ? unknown\n : string\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// RouteTable — high-level table API\n// ────────────────────────────────────────────────────────────────────────────\n\nexport interface ListOptions<S extends SchemaDefinition> {\n /** Filter rows by matching column values. */\n where?: Partial<InferRow<S>>\n /** Sort by a column. Defaults to `'id'`. */\n orderBy?: keyof InferRow<S>\n /** Sort direction. Defaults to `'asc'`. */\n order?: 'asc' | 'desc'\n /** Max rows to return. */\n limit?: number\n}\n\n/**\n * High-level CRUD interface for a single SQLite table.\n *\n * Created via `RouteStore.table()` — do not instantiate directly.\n */\nexport class RouteTable<S extends SchemaDefinition> {\n private readonly jsonCols: Set<string>\n\n constructor(\n private readonly db: SQLiteStore,\n readonly tableName: string,\n private readonly schema: S,\n private readonly emit: (event: ChangeEvent) => void,\n ) {\n this.jsonCols = new Set(\n Object.entries(schema)\n .filter(([, t]) => t === 'json')\n .map(([k]) => k),\n )\n this.createTable()\n }\n\n // ── Schema bootstrap ──────────────────────────────────────────────────────\n\n private createTable(): void {\n const cols = Object.entries(this.schema)\n .map(([col, type]) => {\n const sqlType = type === 'json' ? 'TEXT' : type.toUpperCase()\n return `${col} ${sqlType}`\n })\n .join(', ')\n this.db.exec(\n `CREATE TABLE IF NOT EXISTS \"${this.tableName}\" (id INTEGER PRIMARY KEY AUTOINCREMENT, ${cols})`,\n )\n }\n\n // ── Serialization helpers ─────────────────────────────────────────────────\n\n private serialize(data: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(data)) {\n out[k] = this.jsonCols.has(k) ? JSON.stringify(v) : v\n }\n return out\n }\n\n private deserialize(row: Record<string, unknown>): InferRow<S> {\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(row)) {\n out[k] = this.jsonCols.has(k) && typeof v === 'string' ? JSON.parse(v) : v\n }\n return out as InferRow<S>\n }\n\n // ── CRUD ──────────────────────────────────────────────────────────────────\n\n /**\n * Return all rows, with optional filtering, sorting, and limiting.\n *\n * @example\n * ```ts\n * await items.list()\n * await items.list({ where: { status: 'active' }, orderBy: 'createdAt', order: 'desc', limit: 10 })\n * ```\n */\n async list(options: ListOptions<S> = {}): Promise<InferRow<S>[]> {\n const { where, orderBy = 'id', order = 'asc', limit } = options\n const parts: string[] = []\n const values: unknown[] = []\n\n if (where) {\n for (const [k, v] of Object.entries(where)) {\n parts.push(`${k} = ?`)\n values.push(this.jsonCols.has(k) ? JSON.stringify(v) : v)\n }\n }\n\n const whereClause = parts.length ? `WHERE ${parts.join(' AND ')}` : ''\n const orderClause = `ORDER BY ${String(orderBy)} ${order.toUpperCase()}`\n const limitClause = limit != null ? `LIMIT ${limit}` : ''\n\n const sql = `SELECT * FROM \"${this.tableName}\" ${whereClause} ${orderClause} ${limitClause}`.trim()\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const rows = (this.db.prepare(sql) as any).all(...values) as Record<string, unknown>[]\n return rows.map((r) => this.deserialize(r))\n }\n\n /**\n * Return a single row by `id`, or `null` if not found.\n *\n * @example\n * ```ts\n * const item = await items.get(1)\n * ```\n */\n async get(id: number): Promise<InferRow<S> | null> {\n const row = this.db\n .prepare(`SELECT * FROM \"${this.tableName}\" WHERE id = ?`)\n .get(id) as Record<string, unknown> | undefined\n return row ? this.deserialize(row) : null\n }\n\n /**\n * Insert a new row and emit an `INSERT` change event.\n *\n * @example\n * ```ts\n * const item = await items.create({ name: 'Apple', createdAt: new Date().toISOString() })\n * ```\n */\n async create(data: Omit<InferRow<S>, 'id'>): Promise<InferRow<S>> {\n const serialized = this.serialize(data as Record<string, unknown>)\n const cols = Object.keys(serialized).join(', ')\n const placeholders = Object.keys(serialized)\n .map(() => '?')\n .join(', ')\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = (this.db.prepare(`INSERT INTO \"${this.tableName}\" (${cols}) VALUES (${placeholders})`) as any).run(\n ...Object.values(serialized),\n ) as { lastInsertRowid: number }\n\n const created = (await this.get(result.lastInsertRowid))!\n this.emit({ table: this.tableName, operation: 'INSERT', newRow: created, oldRow: null, timestamp: Date.now() })\n return created\n }\n\n /**\n * Update columns on an existing row and emit an `UPDATE` change event.\n * Returns the updated row, or `null` if the `id` does not exist.\n *\n * @example\n * ```ts\n * const updated = await items.update(1, { name: 'Mango' })\n * ```\n */\n async update(id: number, data: Partial<Omit<InferRow<S>, 'id'>>): Promise<InferRow<S> | null> {\n const old = await this.get(id)\n if (!old) return null\n\n const serialized = this.serialize(data as Record<string, unknown>)\n const setClauses = Object.keys(serialized)\n .map((k) => `${k} = ?`)\n .join(', ')\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ;(this.db.prepare(`UPDATE \"${this.tableName}\" SET ${setClauses} WHERE id = ?`) as any).run(\n ...Object.values(serialized),\n id,\n )\n\n const updated = (await this.get(id))!\n this.emit({ table: this.tableName, operation: 'UPDATE', newRow: updated, oldRow: old, timestamp: Date.now() })\n return updated\n }\n\n /**\n * Delete a row by `id` and emit a `DELETE` change event.\n * Returns `true` if a row was deleted, `false` if `id` was not found.\n *\n * @example\n * ```ts\n * await items.delete(1)\n * ```\n */\n async delete(id: number): Promise<boolean> {\n const old = await this.get(id)\n if (!old) return false\n\n this.db.prepare(`DELETE FROM \"${this.tableName}\" WHERE id = ?`).run(id)\n this.emit({ table: this.tableName, operation: 'DELETE', newRow: null, oldRow: old, timestamp: Date.now() })\n return true\n }\n\n /**\n * Seed the table with initial rows if it is empty.\n *\n * @example\n * ```ts\n * await items.seed([\n * { name: 'Apple', createdAt: '2026-01-01T00:00:00.000Z' },\n * ])\n * ```\n */\n async seed(rows: Omit<InferRow<S>, 'id'>[]): Promise<void> {\n const count = (\n this.db.prepare(`SELECT COUNT(*) as n FROM \"${this.tableName}\"`).get() as { n: number }\n ).n\n if (count > 0) return\n for (const row of rows) {\n await this.create(row)\n }\n }\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// RouteStore — DatabaseAdapter + table factory\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * File-based SQLite store that implements `DatabaseAdapter`.\n *\n * Use it as the RouteFlow adapter **and** as your data access layer —\n * no separate adapter or manual `emit()` calls required.\n * Any mutation (`create`, `update`, `delete`) automatically fires\n * a `ChangeEvent` and triggers all `@Reactive` WebSocket pushes.\n *\n * Requires Node.js 22.5+ (built-in `node:sqlite`).\n *\n * @example\n * ```ts\n * import { RouteStore } from 'routeflow-api/sqlite'\n * import { createApp } from 'routeflow-api'\n *\n * const db = new RouteStore('./data/app.db')\n *\n * const items = db.table('items', {\n * name: 'text',\n * createdAt: 'text',\n * })\n *\n * await items.seed([{ name: 'Apple', createdAt: new Date().toISOString() }])\n *\n * // db IS the adapter — pass directly to createApp\n * const app = createApp({ adapter: db, port: 3000 })\n * ```\n */\nexport class RouteStore implements DatabaseAdapter {\n private readonly sql: SQLiteStore\n private readonly listeners = new Map<string, Set<(event: ChangeEvent) => void>>()\n private connected = false\n\n /**\n * @param dbPath - Path to the SQLite file.\n * Parent directories are created automatically.\n * Relative paths resolve from `process.cwd()`.\n */\n constructor(dbPath: string) {\n this.sql = new SQLiteStore(dbPath)\n }\n\n /**\n * Define a table and return a typed CRUD handle.\n * The table is created in SQLite automatically if it does not exist.\n *\n * @param name - Table name\n * @param schema - Column definitions: `{ columnName: 'integer' | 'text' | 'real' | 'json' }`\n *\n * @example\n * ```ts\n * const tasks = db.table('tasks', {\n * title: 'text',\n * done: 'integer', // 0 | 1\n * metadata: 'json', // auto-serialised JS object\n * })\n * ```\n */\n table<S extends SchemaDefinition>(name: string, schema: S): RouteTable<S> {\n return new RouteTable(this.sql, name, schema, (evt) => this.dispatchEvent(evt))\n }\n\n // ── DatabaseAdapter ───────────────────────────────────────────────────────\n\n async connect(): Promise<void> {\n this.connected = true\n }\n\n async disconnect(): Promise<void> {\n this.sql.close()\n this.connected = false\n }\n\n onChange(table: string, callback: (event: ChangeEvent) => void): () => void {\n if (!this.listeners.has(table)) this.listeners.set(table, new Set())\n this.listeners.get(table)!.add(callback)\n return () => this.listeners.get(table)?.delete(callback)\n }\n\n get isConnected(): boolean {\n return this.connected\n }\n\n // ── Internal ──────────────────────────────────────────────────────────────\n\n private dispatchEvent(event: ChangeEvent): void {\n const cbs = this.listeners.get(event.table)\n if (!cbs) return\n for (const cb of cbs) cb(event)\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "routeflow-api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "RouteFlow — REST API with real-time database push subscriptions",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -42,6 +42,11 @@
|
|
|
42
42
|
"import": "./dist/index.js",
|
|
43
43
|
"require": "./dist/index.cjs"
|
|
44
44
|
},
|
|
45
|
+
"./sqlite": {
|
|
46
|
+
"types": "./dist/sqlite.d.ts",
|
|
47
|
+
"import": "./dist/sqlite.js",
|
|
48
|
+
"require": "./dist/sqlite.cjs"
|
|
49
|
+
},
|
|
45
50
|
"./client": {
|
|
46
51
|
"types": "./dist/client/index.d.ts",
|
|
47
52
|
"import": "./dist/client/index.js",
|
|
@@ -94,7 +99,7 @@
|
|
|
94
99
|
}
|
|
95
100
|
},
|
|
96
101
|
"scripts": {
|
|
97
|
-
"build": "tsup",
|
|
102
|
+
"build": "tsup && sed -i '' 's/from \"sqlite\"/from \"node:sqlite\"/g; s/require(\"sqlite\")/require(\"node:sqlite\")/g' dist/sqlite.js dist/sqlite.cjs",
|
|
98
103
|
"test": "vitest run",
|
|
99
104
|
"dev": "tsup --watch"
|
|
100
105
|
},
|
|
@@ -147,7 +152,7 @@
|
|
|
147
152
|
}
|
|
148
153
|
},
|
|
149
154
|
"devDependencies": {
|
|
150
|
-
"@types/node": "^
|
|
155
|
+
"@types/node": "^22.0.0",
|
|
151
156
|
"@types/pg": "^8.11.10",
|
|
152
157
|
"@types/ws": "^8.5.13",
|
|
153
158
|
"tsup": "^8.0.0",
|