@atscript/db-sqlite 0.1.39 → 0.1.41
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/README.md +9 -9
- package/dist/index.cjs +189 -151
- package/dist/index.d.cts +197 -0
- package/dist/index.d.mts +197 -0
- package/dist/index.mjs +163 -102
- package/package.json +23 -14
- package/LICENSE +0 -21
- package/dist/index.d.ts +0 -195
package/dist/index.mjs
CHANGED
|
@@ -1,19 +1,39 @@
|
|
|
1
|
-
import { AtscriptDbView, BaseDbAdapter, DbError, DbSpace } from "@atscript/db";
|
|
2
1
|
import { createRequire } from "node:module";
|
|
2
|
+
import { AtscriptDbView, BaseDbAdapter, DbError, DbSpace } from "@atscript/db";
|
|
3
3
|
import { buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildInsert, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, refActionToSql, toSqlValue } from "@atscript/db-sql-tools";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
4
|
+
//#region src/better-sqlite3-driver.ts
|
|
5
|
+
/**
|
|
6
|
+
* {@link TSqliteDriver} implementation backed by `better-sqlite3`.
|
|
7
|
+
*
|
|
8
|
+
* Accepts either a file path (opens a new database) or a pre-created
|
|
9
|
+
* `Database` instance from `better-sqlite3`.
|
|
10
|
+
*
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { BetterSqlite3Driver } from '@atscript/db-sqlite'
|
|
13
|
+
*
|
|
14
|
+
* // In-memory database
|
|
15
|
+
* const driver = new BetterSqlite3Driver(':memory:')
|
|
16
|
+
*
|
|
17
|
+
* // File-based database
|
|
18
|
+
* const driver = new BetterSqlite3Driver('./my-data.db')
|
|
19
|
+
*
|
|
20
|
+
* // Pre-created instance
|
|
21
|
+
* import Database from 'better-sqlite3'
|
|
22
|
+
* const db = new Database(':memory:', { verbose: console.log })
|
|
23
|
+
* const driver = new BetterSqlite3Driver(db)
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* Requires `better-sqlite3` to be installed:
|
|
27
|
+
* ```bash
|
|
28
|
+
* pnpm add better-sqlite3
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
16
31
|
var BetterSqlite3Driver = class {
|
|
32
|
+
db;
|
|
33
|
+
constructor(pathOrDb, options) {
|
|
34
|
+
if (typeof pathOrDb === "string") this.db = new (createRequire(import.meta.url)("better-sqlite3"))(pathOrDb, options);
|
|
35
|
+
else this.db = pathOrDb;
|
|
36
|
+
}
|
|
17
37
|
run(sql, params) {
|
|
18
38
|
const stmt = this.db.prepare(sql);
|
|
19
39
|
const result = params ? stmt.run(...params) : stmt.run();
|
|
@@ -36,18 +56,9 @@ var BetterSqlite3Driver = class {
|
|
|
36
56
|
close() {
|
|
37
57
|
this.db.close();
|
|
38
58
|
}
|
|
39
|
-
constructor(pathOrDb, options) {
|
|
40
|
-
_define_property$1(this, "db", void 0);
|
|
41
|
-
if (typeof pathOrDb === "string") {
|
|
42
|
-
const req = createRequire(import.meta.url);
|
|
43
|
-
const Database = req("better-sqlite3");
|
|
44
|
-
this.db = new Database(pathOrDb, options);
|
|
45
|
-
} else this.db = pathOrDb;
|
|
46
|
-
}
|
|
47
59
|
};
|
|
48
|
-
|
|
49
60
|
//#endregion
|
|
50
|
-
//#region
|
|
61
|
+
//#region src/sql-builder.ts
|
|
51
62
|
function esc(name) {
|
|
52
63
|
return name.replace(/"/g, "\"\"");
|
|
53
64
|
}
|
|
@@ -57,7 +68,8 @@ function esc(name) {
|
|
|
57
68
|
* - `abc$` → `%abc`
|
|
58
69
|
* - `^abc$` → `abc`
|
|
59
70
|
* - `abc` → `%abc%`
|
|
60
|
-
*/
|
|
71
|
+
*/
|
|
72
|
+
function regexToLike(pattern) {
|
|
61
73
|
const hasStart = pattern.startsWith("^");
|
|
62
74
|
const hasEnd = pattern.endsWith("$");
|
|
63
75
|
let core = pattern;
|
|
@@ -80,7 +92,7 @@ const sqliteDialect = {
|
|
|
80
92
|
unlimitedLimit: "-1",
|
|
81
93
|
toValue: toSqlValue,
|
|
82
94
|
toParam(value) {
|
|
83
|
-
if (value ===
|
|
95
|
+
if (value === void 0) return null;
|
|
84
96
|
return typeof value === "boolean" ? value ? 1 : 0 : value;
|
|
85
97
|
},
|
|
86
98
|
regex(quotedCol, value) {
|
|
@@ -92,27 +104,60 @@ const sqliteDialect = {
|
|
|
92
104
|
},
|
|
93
105
|
createViewPrefix: "CREATE VIEW IF NOT EXISTS"
|
|
94
106
|
};
|
|
107
|
+
/**
|
|
108
|
+
* Builds an INSERT statement.
|
|
109
|
+
*
|
|
110
|
+
* @param table - Table name.
|
|
111
|
+
* @param data - Column→value map.
|
|
112
|
+
* @returns `{ sql, params }` ready for `driver.run()`.
|
|
113
|
+
*/
|
|
95
114
|
function buildInsert$1(table, data) {
|
|
96
115
|
return buildInsert(sqliteDialect, table, data);
|
|
97
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Builds a SELECT statement with optional sort, limit, offset, projection.
|
|
119
|
+
*/
|
|
98
120
|
function buildSelect$1(table, where, controls) {
|
|
99
121
|
return buildSelect(sqliteDialect, table, where, controls);
|
|
100
122
|
}
|
|
101
|
-
|
|
102
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Builds an UPDATE ... SET ... WHERE statement.
|
|
125
|
+
*/
|
|
126
|
+
function buildUpdate$1(table, data, where, ops) {
|
|
127
|
+
return buildUpdate(sqliteDialect, table, data, where, void 0, ops);
|
|
103
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Builds a DELETE ... WHERE statement.
|
|
131
|
+
*/
|
|
104
132
|
function buildDelete$1(table, where) {
|
|
105
133
|
return buildDelete(sqliteDialect, table, where);
|
|
106
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Builds a CREATE VIEW IF NOT EXISTS statement from a view plan and column mappings.
|
|
137
|
+
*
|
|
138
|
+
* @param viewName - The view name.
|
|
139
|
+
* @param plan - Resolved view plan (entry table, joins, filter).
|
|
140
|
+
* @param columns - Column mappings (view column → source table.column).
|
|
141
|
+
* @param resolveFieldRef - Resolves a query field ref to `"table"."column"` SQL.
|
|
142
|
+
*/
|
|
107
143
|
function buildCreateView$1(viewName, plan, columns, resolveFieldRef) {
|
|
108
144
|
return buildCreateView(sqliteDialect, viewName, plan, columns, resolveFieldRef);
|
|
109
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Builds a SELECT ... GROUP BY statement with aggregate functions.
|
|
148
|
+
*/
|
|
110
149
|
function buildAggregateSelect$1(table, where, controls) {
|
|
111
150
|
return buildAggregateSelect(sqliteDialect, table, where, controls);
|
|
112
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Builds a COUNT query for the number of distinct groups.
|
|
154
|
+
*/
|
|
113
155
|
function buildAggregateCount$1(table, where, controls) {
|
|
114
156
|
return buildAggregateCount(sqliteDialect, table, where, controls);
|
|
115
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Maps Atscript design types to SQLite storage types.
|
|
160
|
+
*/
|
|
116
161
|
function sqliteTypeFromDesignType(designType) {
|
|
117
162
|
switch (designType) {
|
|
118
163
|
case "number":
|
|
@@ -123,6 +168,10 @@ function sqliteTypeFromDesignType(designType) {
|
|
|
123
168
|
default: return "TEXT";
|
|
124
169
|
}
|
|
125
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Builds a CREATE TABLE IF NOT EXISTS statement from field descriptors.
|
|
173
|
+
* Uses pre-computed {@link TDbFieldMeta} — no raw type introspection needed.
|
|
174
|
+
*/
|
|
126
175
|
function buildCreateTable(table, fields, foreignKeys) {
|
|
127
176
|
const colDefs = [];
|
|
128
177
|
const primaryKeys = fields.filter((f) => f.isPrimaryKey);
|
|
@@ -153,12 +202,21 @@ function buildCreateTable(table, fields, foreignKeys) {
|
|
|
153
202
|
}
|
|
154
203
|
return `CREATE TABLE IF NOT EXISTS "${esc(table)}" (${colDefs.join(", ")})`;
|
|
155
204
|
}
|
|
156
|
-
|
|
157
205
|
//#endregion
|
|
158
|
-
//#region
|
|
206
|
+
//#region src/filter-builder.ts
|
|
207
|
+
/**
|
|
208
|
+
* Translates a uniqu filter expression into a parameterized SQL WHERE clause.
|
|
209
|
+
*
|
|
210
|
+
* @returns `{ sql, params }` — the WHERE clause (without "WHERE") and bound params.
|
|
211
|
+
* Returns `{ sql: '1=1', params: [] }` for empty/null filters.
|
|
212
|
+
*/
|
|
159
213
|
function buildWhere(filter) {
|
|
160
214
|
return buildWhere$1(sqliteDialect, filter);
|
|
161
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Like {@link buildWhere} but prefixes all column references with a table alias.
|
|
218
|
+
* Produces `alias."col"` instead of `"col"` — needed for JOINed queries (e.g. FTS5 search).
|
|
219
|
+
*/
|
|
162
220
|
function buildPrefixedWhere(alias, filter) {
|
|
163
221
|
return buildWhere$1({
|
|
164
222
|
...sqliteDialect,
|
|
@@ -167,27 +225,35 @@ function buildPrefixedWhere(alias, filter) {
|
|
|
167
225
|
}
|
|
168
226
|
}, filter);
|
|
169
227
|
}
|
|
170
|
-
|
|
171
228
|
//#endregion
|
|
172
|
-
//#region
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
229
|
+
//#region src/sqlite-adapter.ts
|
|
230
|
+
/**
|
|
231
|
+
* SQLite adapter for {@link AtscriptDbTable}.
|
|
232
|
+
*
|
|
233
|
+
* Accepts any {@link TSqliteDriver} implementation — the actual SQLite engine
|
|
234
|
+
* is fully swappable (better-sqlite3, node:sqlite, sql.js, etc.).
|
|
235
|
+
*
|
|
236
|
+
* Usage:
|
|
237
|
+
* ```typescript
|
|
238
|
+
* import { BetterSqlite3Driver } from '@atscript/db-sqlite'
|
|
239
|
+
*
|
|
240
|
+
* const driver = new BetterSqlite3Driver(':memory:')
|
|
241
|
+
* const adapter = new SqliteAdapter(driver)
|
|
242
|
+
* const users = new AtscriptDbTable(UsersType, adapter)
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
183
245
|
var SqliteAdapter = class extends BaseDbAdapter {
|
|
184
246
|
supportsNativeValueDefaults() {
|
|
185
247
|
return true;
|
|
186
248
|
}
|
|
249
|
+
constructor(driver) {
|
|
250
|
+
super();
|
|
251
|
+
this.driver = driver;
|
|
252
|
+
this.driver.exec("PRAGMA foreign_keys = ON");
|
|
253
|
+
}
|
|
187
254
|
async _beginTransaction() {
|
|
188
255
|
this._log("BEGIN");
|
|
189
256
|
this.driver.exec("BEGIN");
|
|
190
|
-
return undefined;
|
|
191
257
|
}
|
|
192
258
|
async _commitTransaction() {
|
|
193
259
|
this._log("COMMIT");
|
|
@@ -197,10 +263,12 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
197
263
|
this._log("ROLLBACK");
|
|
198
264
|
this.driver.exec("ROLLBACK");
|
|
199
265
|
}
|
|
200
|
-
/** SQLite does not use schemas — override to always exclude schema. */
|
|
266
|
+
/** SQLite does not use schemas — override to always exclude schema. */
|
|
267
|
+
resolveTableName() {
|
|
201
268
|
return super.resolveTableName(false);
|
|
202
269
|
}
|
|
203
|
-
/** SQLite enforces FK constraints natively via PRAGMA foreign_keys. */
|
|
270
|
+
/** SQLite enforces FK constraints natively via PRAGMA foreign_keys. */
|
|
271
|
+
supportsNativeForeignKeys() {
|
|
204
272
|
return true;
|
|
205
273
|
}
|
|
206
274
|
prepareId(id, _fieldType) {
|
|
@@ -209,7 +277,8 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
209
277
|
/**
|
|
210
278
|
* Wraps a write operation to catch native SQLite constraint errors
|
|
211
279
|
* and rethrow as structured `DbError`.
|
|
212
|
-
*/
|
|
280
|
+
*/
|
|
281
|
+
_wrapConstraintError(fn) {
|
|
213
282
|
try {
|
|
214
283
|
return fn();
|
|
215
284
|
} catch (error) {
|
|
@@ -270,46 +339,39 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
270
339
|
}
|
|
271
340
|
async count(query) {
|
|
272
341
|
const where = buildWhere(query.filter);
|
|
273
|
-
const
|
|
274
|
-
const sql = `SELECT COUNT(*) as cnt FROM "${esc(tableName)}" WHERE ${where.sql}`;
|
|
342
|
+
const sql = `SELECT COUNT(*) as cnt FROM "${esc(this.resolveTableName())}" WHERE ${where.sql}`;
|
|
275
343
|
this._log(sql, where.params);
|
|
276
|
-
|
|
277
|
-
return row?.cnt ?? 0;
|
|
344
|
+
return this.driver.get(sql, where.params)?.cnt ?? 0;
|
|
278
345
|
}
|
|
279
346
|
async aggregate(query) {
|
|
280
347
|
const where = buildWhere(query.filter);
|
|
281
348
|
const tableName = this.resolveTableName();
|
|
282
349
|
if (query.controls.$count) {
|
|
283
|
-
const { sql
|
|
284
|
-
this._log(sql
|
|
285
|
-
|
|
286
|
-
return [{ count: row?.count ?? 0 }];
|
|
350
|
+
const { sql, params } = buildAggregateCount$1(tableName, where, query.controls);
|
|
351
|
+
this._log(sql, params);
|
|
352
|
+
return [{ count: this.driver.get(sql, params)?.count ?? 0 }];
|
|
287
353
|
}
|
|
288
354
|
const { sql, params } = buildAggregateSelect$1(tableName, where, query.controls);
|
|
289
355
|
this._log(sql, params);
|
|
290
356
|
return this.driver.all(sql, params);
|
|
291
357
|
}
|
|
292
|
-
async updateOne(filter, data) {
|
|
358
|
+
async updateOne(filter, data, ops) {
|
|
293
359
|
const where = buildWhere(filter);
|
|
294
360
|
const tableName = this.resolveTableName();
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const sql = `UPDATE "${esc(tableName)}" SET ${setClauses.join(", ")} WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
|
|
302
|
-
const allParams = [...setParams, ...where.params];
|
|
303
|
-
this._log(sql, allParams);
|
|
304
|
-
const result = this._wrapConstraintError(() => this.driver.run(sql, allParams));
|
|
361
|
+
const { sql, params } = buildUpdate$1(tableName, data, {
|
|
362
|
+
sql: `rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`,
|
|
363
|
+
params: where.params
|
|
364
|
+
}, ops);
|
|
365
|
+
this._log(sql, params);
|
|
366
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
305
367
|
return {
|
|
306
368
|
matchedCount: result.changes,
|
|
307
369
|
modifiedCount: result.changes
|
|
308
370
|
};
|
|
309
371
|
}
|
|
310
|
-
async updateMany(filter, data) {
|
|
372
|
+
async updateMany(filter, data, ops) {
|
|
311
373
|
const where = buildWhere(filter);
|
|
312
|
-
const { sql, params } = buildUpdate$1(this.resolveTableName(), data, where);
|
|
374
|
+
const { sql, params } = buildUpdate$1(this.resolveTableName(), data, where, ops);
|
|
313
375
|
this._log(sql, params);
|
|
314
376
|
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
315
377
|
return {
|
|
@@ -320,11 +382,10 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
320
382
|
async replaceOne(filter, data) {
|
|
321
383
|
const where = buildWhere(filter);
|
|
322
384
|
const tableName = this.resolveTableName();
|
|
323
|
-
const
|
|
385
|
+
const { sql, params } = buildUpdate$1(tableName, data, {
|
|
324
386
|
sql: `rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`,
|
|
325
387
|
params: where.params
|
|
326
|
-
};
|
|
327
|
-
const { sql, params } = buildUpdate$1(tableName, data, limitedWhere);
|
|
388
|
+
});
|
|
328
389
|
this._log(sql, params);
|
|
329
390
|
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
330
391
|
return {
|
|
@@ -347,15 +408,13 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
347
408
|
const tableName = this.resolveTableName();
|
|
348
409
|
const sql = `DELETE FROM "${esc(tableName)}" WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
|
|
349
410
|
this._log(sql, where.params);
|
|
350
|
-
|
|
351
|
-
return { deletedCount: result.changes };
|
|
411
|
+
return { deletedCount: this._wrapConstraintError(() => this.driver.run(sql, where.params)).changes };
|
|
352
412
|
}
|
|
353
413
|
async deleteMany(filter) {
|
|
354
414
|
const where = buildWhere(filter);
|
|
355
415
|
const { sql, params } = buildDelete$1(this.resolveTableName(), where);
|
|
356
416
|
this._log(sql, params);
|
|
357
|
-
|
|
358
|
-
return { deletedCount: result.changes };
|
|
417
|
+
return { deletedCount: this._wrapConstraintError(() => this.driver.run(sql, params)).changes };
|
|
359
418
|
}
|
|
360
419
|
async ensureTable() {
|
|
361
420
|
if (this._table instanceof AtscriptDbView) return this.ensureView();
|
|
@@ -364,10 +423,12 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
364
423
|
this.driver.exec(sql);
|
|
365
424
|
this._seedIncrementStart();
|
|
366
425
|
}
|
|
426
|
+
_incrementSeeded = false;
|
|
367
427
|
/**
|
|
368
428
|
* Seeds the sqlite_sequence table for auto-increment fields that have a start value.
|
|
369
429
|
* Only applies once per adapter instance (idempotent via INSERT OR IGNORE + flag).
|
|
370
|
-
*/
|
|
430
|
+
*/
|
|
431
|
+
_seedIncrementStart() {
|
|
371
432
|
if (this._incrementSeeded) return;
|
|
372
433
|
this._incrementSeeded = true;
|
|
373
434
|
const tableName = this.resolveTableName();
|
|
@@ -403,7 +464,7 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
403
464
|
let ddl = `ALTER TABLE "${esc(tableName)}" ADD COLUMN "${esc(field.physicalName)}" ${sqlType}`;
|
|
404
465
|
if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
|
|
405
466
|
if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${defaultValueToSqlLiteral(field.designType, field.defaultValue.value)}`;
|
|
406
|
-
else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValueForType(field.designType)}`;
|
|
467
|
+
else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValueForType(field.designType)}`;
|
|
407
468
|
if (field.collate) ddl += ` COLLATE ${field.collate.toUpperCase()}`;
|
|
408
469
|
this._log(ddl);
|
|
409
470
|
this.driver.exec(ddl);
|
|
@@ -491,8 +552,7 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValue
|
|
|
491
552
|
return sqliteTypeFromDesignType(field.designType);
|
|
492
553
|
}
|
|
493
554
|
async getExistingColumnsForTable(tableName) {
|
|
494
|
-
|
|
495
|
-
return rows.map((r) => ({
|
|
555
|
+
return this.driver.all(`PRAGMA table_info("${esc(tableName)}")`).map((r) => ({
|
|
496
556
|
name: r.name,
|
|
497
557
|
type: r.type,
|
|
498
558
|
notnull: r.notnull === 1,
|
|
@@ -540,12 +600,12 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValue
|
|
|
540
600
|
for (const [col, dir] of Object.entries(controls.$sort)) orderParts.push(`t."${esc(col)}" ${dir === -1 ? "DESC" : "ASC"}`);
|
|
541
601
|
if (orderParts.length > 0) sql += ` ORDER BY ${orderParts.join(", ")}`;
|
|
542
602
|
}
|
|
543
|
-
if (controls.$limit !==
|
|
603
|
+
if (controls.$limit !== void 0) {
|
|
544
604
|
sql += ` LIMIT ?`;
|
|
545
605
|
params.push(controls.$limit);
|
|
546
606
|
}
|
|
547
|
-
if (controls.$skip !==
|
|
548
|
-
if (controls.$limit ===
|
|
607
|
+
if (controls.$skip !== void 0) {
|
|
608
|
+
if (controls.$limit === void 0) sql += ` LIMIT -1`;
|
|
549
609
|
sql += ` OFFSET ?`;
|
|
550
610
|
params.push(controls.$skip);
|
|
551
611
|
}
|
|
@@ -561,21 +621,23 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValue
|
|
|
561
621
|
const base = this._buildFtsBase(text, query.filter, indexName);
|
|
562
622
|
const countSql = `SELECT COUNT(*) as cnt ${base.fromWhere}`;
|
|
563
623
|
this._log(countSql, base.params);
|
|
564
|
-
const row = this.driver.get(countSql, base.params);
|
|
565
624
|
return {
|
|
566
625
|
data,
|
|
567
|
-
count:
|
|
626
|
+
count: this.driver.get(countSql, base.params)?.cnt ?? 0
|
|
568
627
|
};
|
|
569
628
|
}
|
|
570
|
-
/** Builds FTS table name from index name: `<table>__fts__<indexName>`. */
|
|
629
|
+
/** Builds FTS table name from index name: `<table>__fts__<indexName>`. */
|
|
630
|
+
_ftsTableName(indexName) {
|
|
571
631
|
return `${this.resolveTableName()}__fts__${indexName}`;
|
|
572
632
|
}
|
|
573
|
-
/** Returns fulltext indexes from table metadata. */
|
|
633
|
+
/** Returns fulltext indexes from table metadata. */
|
|
634
|
+
_getFulltextIndexes() {
|
|
574
635
|
const result = [];
|
|
575
636
|
for (const index of this._table.indexes.values()) if (index.type === "fulltext") result.push(index);
|
|
576
637
|
return result;
|
|
577
638
|
}
|
|
578
|
-
/** Resolves a fulltext index by name, or returns the first available. */
|
|
639
|
+
/** Resolves a fulltext index by name, or returns the first available. */
|
|
640
|
+
_resolveFtsIndex(indexName) {
|
|
579
641
|
const ftIndexes = this._getFulltextIndexes();
|
|
580
642
|
if (ftIndexes.length === 0) throw new Error("No search index available");
|
|
581
643
|
if (indexName) {
|
|
@@ -588,7 +650,8 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValue
|
|
|
588
650
|
/**
|
|
589
651
|
* Builds the shared FROM+JOIN+WHERE fragment for FTS5 queries.
|
|
590
652
|
* Both data and count queries reuse this to avoid duplicating index resolution and filter translation.
|
|
591
|
-
*/
|
|
653
|
+
*/
|
|
654
|
+
_buildFtsBase(text, filter, indexName) {
|
|
592
655
|
const ftsIndex = this._resolveFtsIndex(indexName);
|
|
593
656
|
const ftsTable = this._ftsTableName(ftsIndex.name);
|
|
594
657
|
const tableName = this.resolveTableName();
|
|
@@ -608,7 +671,8 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValue
|
|
|
608
671
|
}
|
|
609
672
|
/**
|
|
610
673
|
* Creates/drops FTS5 virtual tables and sync triggers to match desired fulltext indexes.
|
|
611
|
-
*/
|
|
674
|
+
*/
|
|
675
|
+
_syncFtsIndexes(tableName) {
|
|
612
676
|
const ftIndexes = this._getFulltextIndexes();
|
|
613
677
|
const desiredFtsTables = new Set(ftIndexes.map((idx) => this._ftsTableName(idx.name)));
|
|
614
678
|
const existingFts = this.driver.all(`SELECT name, sql FROM sqlite_master WHERE type='table' AND name LIKE ?`, [`${tableName}__fts__%`]).filter((r) => r.sql.startsWith("CREATE VIRTUAL TABLE")).map((r) => r.name);
|
|
@@ -619,9 +683,9 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValue
|
|
|
619
683
|
if (!existingSet.has(ftsTable)) this._createFtsTable(tableName, ftsTable, index);
|
|
620
684
|
}
|
|
621
685
|
}
|
|
622
|
-
/** Creates an FTS5 virtual table with sync triggers and rebuilds the index. */
|
|
623
|
-
|
|
624
|
-
const fieldList =
|
|
686
|
+
/** Creates an FTS5 virtual table with sync triggers and rebuilds the index. */
|
|
687
|
+
_createFtsTable(tableName, ftsTable, index) {
|
|
688
|
+
const fieldList = index.fields.map((f) => `"${esc(f.name)}"`).join(", ");
|
|
625
689
|
const createSql = `CREATE VIRTUAL TABLE IF NOT EXISTS "${esc(ftsTable)}" USING fts5(${fieldList}, content='${tableName.replace(/'/g, "''")}', content_rowid='rowid')`;
|
|
626
690
|
this._log(createSql);
|
|
627
691
|
this.driver.exec(createSql);
|
|
@@ -641,7 +705,8 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValue
|
|
|
641
705
|
this._log(rebuildSql);
|
|
642
706
|
this.driver.exec(rebuildSql);
|
|
643
707
|
}
|
|
644
|
-
/** Drops an FTS5 virtual table and its sync triggers. */
|
|
708
|
+
/** Drops an FTS5 virtual table and its sync triggers. */
|
|
709
|
+
_dropFtsTable(ftsTable) {
|
|
645
710
|
for (const suffix of [
|
|
646
711
|
"__ai",
|
|
647
712
|
"__ad",
|
|
@@ -651,28 +716,24 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValue
|
|
|
651
716
|
this._log(sql);
|
|
652
717
|
this.driver.exec(sql);
|
|
653
718
|
}
|
|
654
|
-
/** Drops all FTS virtual tables and triggers for a content table. */
|
|
719
|
+
/** Drops all FTS virtual tables and triggers for a content table. */
|
|
720
|
+
_dropAllFtsTables(tableName) {
|
|
655
721
|
const ftsTables = this.driver.all(`SELECT name, sql FROM sqlite_master WHERE type='table' AND name LIKE ?`, [`${tableName}__fts__%`]).filter((r) => r.sql.startsWith("CREATE VIRTUAL TABLE"));
|
|
656
722
|
for (const { name } of ftsTables) this._dropFtsTable(name);
|
|
657
723
|
}
|
|
658
|
-
constructor(driver) {
|
|
659
|
-
super(), _define_property(this, "driver", void 0), _define_property(this, "_incrementSeeded", void 0), this.driver = driver, this._incrementSeeded = false;
|
|
660
|
-
this.driver.exec("PRAGMA foreign_keys = ON");
|
|
661
|
-
}
|
|
662
724
|
};
|
|
663
725
|
/** Normalizes SQLite PRAGMA dflt_value to match serialized format.
|
|
664
|
-
* PRAGMA returns `'active'` (SQL-quoted), we store `active` (raw). */
|
|
665
|
-
|
|
726
|
+
* PRAGMA returns `'active'` (SQL-quoted), we store `active` (raw). */
|
|
727
|
+
function normalizeSqliteDefault(value) {
|
|
728
|
+
if (value === null) return;
|
|
666
729
|
if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
|
|
667
730
|
return value;
|
|
668
731
|
}
|
|
669
|
-
|
|
670
732
|
//#endregion
|
|
671
|
-
//#region
|
|
733
|
+
//#region src/index.ts
|
|
672
734
|
function createAdapter(connection, options) {
|
|
673
735
|
const driver = new BetterSqlite3Driver(connection, options);
|
|
674
736
|
return new DbSpace(() => new SqliteAdapter(driver));
|
|
675
737
|
}
|
|
676
|
-
|
|
677
738
|
//#endregion
|
|
678
|
-
export { BetterSqlite3Driver, SqliteAdapter, buildWhere, createAdapter };
|
|
739
|
+
export { BetterSqlite3Driver, SqliteAdapter, buildWhere, createAdapter };
|
package/package.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/db-sqlite",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.41",
|
|
4
4
|
"description": "SQLite adapter for @atscript/db with swappable driver support.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"atscript",
|
|
7
7
|
"database",
|
|
8
8
|
"sqlite"
|
|
9
9
|
],
|
|
10
|
-
"homepage": "https://github.com/moostjs/atscript/tree/main/packages/db-sqlite#readme",
|
|
10
|
+
"homepage": "https://github.com/moostjs/atscript-db/tree/main/packages/db-sqlite#readme",
|
|
11
11
|
"bugs": {
|
|
12
|
-
"url": "https://github.com/moostjs/atscript/issues"
|
|
12
|
+
"url": "https://github.com/moostjs/atscript-db/issues"
|
|
13
13
|
},
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"author": "Artem Maltsev",
|
|
16
16
|
"repository": {
|
|
17
17
|
"type": "git",
|
|
18
|
-
"url": "git+https://github.com/moostjs/atscript.git",
|
|
18
|
+
"url": "git+https://github.com/moostjs/atscript-db.git",
|
|
19
19
|
"directory": "packages/db-sqlite"
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
@@ -23,27 +23,33 @@
|
|
|
23
23
|
],
|
|
24
24
|
"type": "module",
|
|
25
25
|
"main": "dist/index.mjs",
|
|
26
|
-
"types": "dist/index.d.
|
|
26
|
+
"types": "dist/index.d.mts",
|
|
27
27
|
"exports": {
|
|
28
28
|
".": {
|
|
29
|
-
"types": "./dist/index.d.
|
|
29
|
+
"types": "./dist/index.d.mts",
|
|
30
30
|
"import": "./dist/index.mjs",
|
|
31
31
|
"require": "./dist/index.cjs"
|
|
32
32
|
},
|
|
33
33
|
"./package.json": "./package.json"
|
|
34
34
|
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
35
38
|
"devDependencies": {
|
|
36
|
-
"
|
|
39
|
+
"@atscript/core": "^0.1.39",
|
|
40
|
+
"@atscript/typescript": "^0.1.39",
|
|
37
41
|
"@types/better-sqlite3": "^7.6.13",
|
|
38
|
-
"
|
|
42
|
+
"@uniqu/core": "^0.1.2",
|
|
43
|
+
"better-sqlite3": "^12.6.2",
|
|
44
|
+
"unplugin-atscript": "^0.1.39"
|
|
39
45
|
},
|
|
40
46
|
"peerDependencies": {
|
|
47
|
+
"@atscript/core": "^0.1.39",
|
|
48
|
+
"@atscript/typescript": "^0.1.39",
|
|
41
49
|
"@uniqu/core": "^0.1.2",
|
|
42
50
|
"better-sqlite3": ">=11.0.0",
|
|
43
|
-
"@atscript/db-sql-tools": "^0.1.
|
|
44
|
-
"@atscript/
|
|
45
|
-
"@atscript/db": "^0.1.39",
|
|
46
|
-
"@atscript/typescript": "^0.1.39"
|
|
51
|
+
"@atscript/db-sql-tools": "^0.1.41",
|
|
52
|
+
"@atscript/db": "^0.1.41"
|
|
47
53
|
},
|
|
48
54
|
"peerDependenciesMeta": {
|
|
49
55
|
"better-sqlite3": {
|
|
@@ -51,7 +57,10 @@
|
|
|
51
57
|
}
|
|
52
58
|
},
|
|
53
59
|
"scripts": {
|
|
54
|
-
"
|
|
55
|
-
"
|
|
60
|
+
"postinstall": "asc -f dts",
|
|
61
|
+
"build": "vp pack",
|
|
62
|
+
"dev": "vp pack --watch",
|
|
63
|
+
"test": "vp test",
|
|
64
|
+
"check": "vp check"
|
|
56
65
|
}
|
|
57
66
|
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025-present Artem Maltsev
|
|
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.
|