@atscript/db-sqlite 0.1.39 → 0.1.40
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/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="https://atscript.
|
|
2
|
+
<img src="https://atscript.dev/logo.svg" alt="Atscript" width="120" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<h1 align="center">@atscript/db-sqlite</h1>
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<a href="https://atscript.
|
|
12
|
+
<a href="https://db.atscript.dev">Documentation</a> · <a href="https://db.atscript.dev/adapters/sqlite">SQLite Adapter</a>
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
15
|
---
|
|
@@ -25,13 +25,13 @@ pnpm add @atscript/db-sqlite better-sqlite3
|
|
|
25
25
|
## Quick Start
|
|
26
26
|
|
|
27
27
|
```typescript
|
|
28
|
-
import { DbSpace } from
|
|
29
|
-
import { createAdapter } from
|
|
28
|
+
import { DbSpace } from "@atscript/db";
|
|
29
|
+
import { createAdapter } from "@atscript/db-sqlite";
|
|
30
30
|
|
|
31
|
-
const db = createAdapter(
|
|
32
|
-
const users = db.getTable(UsersType)
|
|
31
|
+
const db = createAdapter("./myapp.db");
|
|
32
|
+
const users = db.getTable(UsersType);
|
|
33
33
|
|
|
34
|
-
await users.insertOne({ name:
|
|
34
|
+
await users.insertOne({ name: "John", email: "john@example.com" });
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
## Features
|
|
@@ -46,8 +46,8 @@ await users.insertOne({ name: 'John', email: 'john@example.com' })
|
|
|
46
46
|
|
|
47
47
|
## Documentation
|
|
48
48
|
|
|
49
|
-
- [SQLite Adapter Guide](https://atscript.
|
|
50
|
-
- [Full Documentation](https://atscript.
|
|
49
|
+
- [SQLite Adapter Guide](https://db.atscript.dev/adapters/sqlite)
|
|
50
|
+
- [Full Documentation](https://db.atscript.dev)
|
|
51
51
|
|
|
52
52
|
## License
|
|
53
53
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,43 +1,40 @@
|
|
|
1
|
-
"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
value,
|
|
33
|
-
enumerable: true,
|
|
34
|
-
configurable: true,
|
|
35
|
-
writable: true
|
|
36
|
-
});
|
|
37
|
-
else obj[key] = value;
|
|
38
|
-
return obj;
|
|
39
|
-
}
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _atscript_db = require("@atscript/db");
|
|
3
|
+
let node_module = require("node:module");
|
|
4
|
+
let _atscript_db_sql_tools = require("@atscript/db-sql-tools");
|
|
5
|
+
//#region src/better-sqlite3-driver.ts
|
|
6
|
+
/**
|
|
7
|
+
* {@link TSqliteDriver} implementation backed by `better-sqlite3`.
|
|
8
|
+
*
|
|
9
|
+
* Accepts either a file path (opens a new database) or a pre-created
|
|
10
|
+
* `Database` instance from `better-sqlite3`.
|
|
11
|
+
*
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { BetterSqlite3Driver } from '@atscript/db-sqlite'
|
|
14
|
+
*
|
|
15
|
+
* // In-memory database
|
|
16
|
+
* const driver = new BetterSqlite3Driver(':memory:')
|
|
17
|
+
*
|
|
18
|
+
* // File-based database
|
|
19
|
+
* const driver = new BetterSqlite3Driver('./my-data.db')
|
|
20
|
+
*
|
|
21
|
+
* // Pre-created instance
|
|
22
|
+
* import Database from 'better-sqlite3'
|
|
23
|
+
* const db = new Database(':memory:', { verbose: console.log })
|
|
24
|
+
* const driver = new BetterSqlite3Driver(db)
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* Requires `better-sqlite3` to be installed:
|
|
28
|
+
* ```bash
|
|
29
|
+
* pnpm add better-sqlite3
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
40
32
|
var BetterSqlite3Driver = class {
|
|
33
|
+
db;
|
|
34
|
+
constructor(pathOrDb, options) {
|
|
35
|
+
if (typeof pathOrDb === "string") this.db = new ((0, node_module.createRequire)(require("url").pathToFileURL(__filename).href)("better-sqlite3"))(pathOrDb, options);
|
|
36
|
+
else this.db = pathOrDb;
|
|
37
|
+
}
|
|
41
38
|
run(sql, params) {
|
|
42
39
|
const stmt = this.db.prepare(sql);
|
|
43
40
|
const result = params ? stmt.run(...params) : stmt.run();
|
|
@@ -60,18 +57,9 @@ var BetterSqlite3Driver = class {
|
|
|
60
57
|
close() {
|
|
61
58
|
this.db.close();
|
|
62
59
|
}
|
|
63
|
-
constructor(pathOrDb, options) {
|
|
64
|
-
_define_property$1(this, "db", void 0);
|
|
65
|
-
if (typeof pathOrDb === "string") {
|
|
66
|
-
const req = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
67
|
-
const Database = req("better-sqlite3");
|
|
68
|
-
this.db = new Database(pathOrDb, options);
|
|
69
|
-
} else this.db = pathOrDb;
|
|
70
|
-
}
|
|
71
60
|
};
|
|
72
|
-
|
|
73
61
|
//#endregion
|
|
74
|
-
//#region
|
|
62
|
+
//#region src/sql-builder.ts
|
|
75
63
|
function esc(name) {
|
|
76
64
|
return name.replace(/"/g, "\"\"");
|
|
77
65
|
}
|
|
@@ -81,7 +69,8 @@ function esc(name) {
|
|
|
81
69
|
* - `abc$` → `%abc`
|
|
82
70
|
* - `^abc$` → `abc`
|
|
83
71
|
* - `abc` → `%abc%`
|
|
84
|
-
*/
|
|
72
|
+
*/
|
|
73
|
+
function regexToLike(pattern) {
|
|
85
74
|
const hasStart = pattern.startsWith("^");
|
|
86
75
|
const hasEnd = pattern.endsWith("$");
|
|
87
76
|
let core = pattern;
|
|
@@ -102,9 +91,9 @@ const sqliteDialect = {
|
|
|
102
91
|
return `"${esc(name)}"`;
|
|
103
92
|
},
|
|
104
93
|
unlimitedLimit: "-1",
|
|
105
|
-
toValue:
|
|
94
|
+
toValue: _atscript_db_sql_tools.toSqlValue,
|
|
106
95
|
toParam(value) {
|
|
107
|
-
if (value ===
|
|
96
|
+
if (value === void 0) return null;
|
|
108
97
|
return typeof value === "boolean" ? value ? 1 : 0 : value;
|
|
109
98
|
},
|
|
110
99
|
regex(quotedCol, value) {
|
|
@@ -116,27 +105,60 @@ const sqliteDialect = {
|
|
|
116
105
|
},
|
|
117
106
|
createViewPrefix: "CREATE VIEW IF NOT EXISTS"
|
|
118
107
|
};
|
|
108
|
+
/**
|
|
109
|
+
* Builds an INSERT statement.
|
|
110
|
+
*
|
|
111
|
+
* @param table - Table name.
|
|
112
|
+
* @param data - Column→value map.
|
|
113
|
+
* @returns `{ sql, params }` ready for `driver.run()`.
|
|
114
|
+
*/
|
|
119
115
|
function buildInsert(table, data) {
|
|
120
|
-
return (0,
|
|
116
|
+
return (0, _atscript_db_sql_tools.buildInsert)(sqliteDialect, table, data);
|
|
121
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Builds a SELECT statement with optional sort, limit, offset, projection.
|
|
120
|
+
*/
|
|
122
121
|
function buildSelect(table, where, controls) {
|
|
123
|
-
return (0,
|
|
122
|
+
return (0, _atscript_db_sql_tools.buildSelect)(sqliteDialect, table, where, controls);
|
|
124
123
|
}
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Builds an UPDATE ... SET ... WHERE statement.
|
|
126
|
+
*/
|
|
127
|
+
function buildUpdate(table, data, where, ops) {
|
|
128
|
+
return (0, _atscript_db_sql_tools.buildUpdate)(sqliteDialect, table, data, where, void 0, ops);
|
|
127
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Builds a DELETE ... WHERE statement.
|
|
132
|
+
*/
|
|
128
133
|
function buildDelete(table, where) {
|
|
129
|
-
return (0,
|
|
134
|
+
return (0, _atscript_db_sql_tools.buildDelete)(sqliteDialect, table, where);
|
|
130
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Builds a CREATE VIEW IF NOT EXISTS statement from a view plan and column mappings.
|
|
138
|
+
*
|
|
139
|
+
* @param viewName - The view name.
|
|
140
|
+
* @param plan - Resolved view plan (entry table, joins, filter).
|
|
141
|
+
* @param columns - Column mappings (view column → source table.column).
|
|
142
|
+
* @param resolveFieldRef - Resolves a query field ref to `"table"."column"` SQL.
|
|
143
|
+
*/
|
|
131
144
|
function buildCreateView(viewName, plan, columns, resolveFieldRef) {
|
|
132
|
-
return (0,
|
|
145
|
+
return (0, _atscript_db_sql_tools.buildCreateView)(sqliteDialect, viewName, plan, columns, resolveFieldRef);
|
|
133
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Builds a SELECT ... GROUP BY statement with aggregate functions.
|
|
149
|
+
*/
|
|
134
150
|
function buildAggregateSelect(table, where, controls) {
|
|
135
|
-
return (0,
|
|
151
|
+
return (0, _atscript_db_sql_tools.buildAggregateSelect)(sqliteDialect, table, where, controls);
|
|
136
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Builds a COUNT query for the number of distinct groups.
|
|
155
|
+
*/
|
|
137
156
|
function buildAggregateCount(table, where, controls) {
|
|
138
|
-
return (0,
|
|
157
|
+
return (0, _atscript_db_sql_tools.buildAggregateCount)(sqliteDialect, table, where, controls);
|
|
139
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* Maps Atscript design types to SQLite storage types.
|
|
161
|
+
*/
|
|
140
162
|
function sqliteTypeFromDesignType(designType) {
|
|
141
163
|
switch (designType) {
|
|
142
164
|
case "number":
|
|
@@ -147,6 +169,10 @@ function sqliteTypeFromDesignType(designType) {
|
|
|
147
169
|
default: return "TEXT";
|
|
148
170
|
}
|
|
149
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Builds a CREATE TABLE IF NOT EXISTS statement from field descriptors.
|
|
174
|
+
* Uses pre-computed {@link TDbFieldMeta} — no raw type introspection needed.
|
|
175
|
+
*/
|
|
150
176
|
function buildCreateTable(table, fields, foreignKeys) {
|
|
151
177
|
const colDefs = [];
|
|
152
178
|
const primaryKeys = fields.filter((f) => f.isPrimaryKey);
|
|
@@ -159,7 +185,7 @@ function buildCreateTable(table, fields, foreignKeys) {
|
|
|
159
185
|
if (field.defaultValue?.kind === "fn" && field.defaultValue.fn === "increment" && (field.designType === "number" || field.designType === "integer")) def += " AUTOINCREMENT";
|
|
160
186
|
}
|
|
161
187
|
if (!field.optional && !field.isPrimaryKey) def += " NOT NULL";
|
|
162
|
-
if (field.defaultValue?.kind === "value") def += ` DEFAULT ${(0,
|
|
188
|
+
if (field.defaultValue?.kind === "value") def += ` DEFAULT ${(0, _atscript_db_sql_tools.defaultValueToSqlLiteral)(field.designType, field.defaultValue.value)}`;
|
|
163
189
|
if (field.collate) def += ` COLLATE ${field.collate.toUpperCase()}`;
|
|
164
190
|
colDefs.push(def);
|
|
165
191
|
}
|
|
@@ -171,47 +197,64 @@ function buildCreateTable(table, fields, foreignKeys) {
|
|
|
171
197
|
const localCols = fk.fields.map((f) => `"${esc(f)}"`).join(", ");
|
|
172
198
|
const targetCols = fk.targetFields.map((f) => `"${esc(f)}"`).join(", ");
|
|
173
199
|
let constraint = `FOREIGN KEY (${localCols}) REFERENCES "${esc(fk.targetTable)}" (${targetCols})`;
|
|
174
|
-
if (fk.onDelete) constraint += ` ON DELETE ${(0,
|
|
175
|
-
if (fk.onUpdate) constraint += ` ON UPDATE ${(0,
|
|
200
|
+
if (fk.onDelete) constraint += ` ON DELETE ${(0, _atscript_db_sql_tools.refActionToSql)(fk.onDelete)}`;
|
|
201
|
+
if (fk.onUpdate) constraint += ` ON UPDATE ${(0, _atscript_db_sql_tools.refActionToSql)(fk.onUpdate)}`;
|
|
176
202
|
colDefs.push(constraint);
|
|
177
203
|
}
|
|
178
204
|
return `CREATE TABLE IF NOT EXISTS "${esc(table)}" (${colDefs.join(", ")})`;
|
|
179
205
|
}
|
|
180
|
-
|
|
181
206
|
//#endregion
|
|
182
|
-
//#region
|
|
207
|
+
//#region src/filter-builder.ts
|
|
208
|
+
/**
|
|
209
|
+
* Translates a uniqu filter expression into a parameterized SQL WHERE clause.
|
|
210
|
+
*
|
|
211
|
+
* @returns `{ sql, params }` — the WHERE clause (without "WHERE") and bound params.
|
|
212
|
+
* Returns `{ sql: '1=1', params: [] }` for empty/null filters.
|
|
213
|
+
*/
|
|
183
214
|
function buildWhere(filter) {
|
|
184
|
-
return (0,
|
|
215
|
+
return (0, _atscript_db_sql_tools.buildWhere)(sqliteDialect, filter);
|
|
185
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Like {@link buildWhere} but prefixes all column references with a table alias.
|
|
219
|
+
* Produces `alias."col"` instead of `"col"` — needed for JOINed queries (e.g. FTS5 search).
|
|
220
|
+
*/
|
|
186
221
|
function buildPrefixedWhere(alias, filter) {
|
|
187
|
-
return (0,
|
|
222
|
+
return (0, _atscript_db_sql_tools.buildWhere)({
|
|
188
223
|
...sqliteDialect,
|
|
189
224
|
quoteIdentifier(name) {
|
|
190
225
|
return `${alias}."${esc(name)}"`;
|
|
191
226
|
}
|
|
192
227
|
}, filter);
|
|
193
228
|
}
|
|
194
|
-
|
|
195
229
|
//#endregion
|
|
196
|
-
//#region
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
230
|
+
//#region src/sqlite-adapter.ts
|
|
231
|
+
/**
|
|
232
|
+
* SQLite adapter for {@link AtscriptDbTable}.
|
|
233
|
+
*
|
|
234
|
+
* Accepts any {@link TSqliteDriver} implementation — the actual SQLite engine
|
|
235
|
+
* is fully swappable (better-sqlite3, node:sqlite, sql.js, etc.).
|
|
236
|
+
*
|
|
237
|
+
* Usage:
|
|
238
|
+
* ```typescript
|
|
239
|
+
* import { BetterSqlite3Driver } from '@atscript/db-sqlite'
|
|
240
|
+
*
|
|
241
|
+
* const driver = new BetterSqlite3Driver(':memory:')
|
|
242
|
+
* const adapter = new SqliteAdapter(driver)
|
|
243
|
+
* const users = new AtscriptDbTable(UsersType, adapter)
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
246
|
+
var SqliteAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
208
247
|
supportsNativeValueDefaults() {
|
|
209
248
|
return true;
|
|
210
249
|
}
|
|
250
|
+
constructor(driver) {
|
|
251
|
+
super();
|
|
252
|
+
this.driver = driver;
|
|
253
|
+
this.driver.exec("PRAGMA foreign_keys = ON");
|
|
254
|
+
}
|
|
211
255
|
async _beginTransaction() {
|
|
212
256
|
this._log("BEGIN");
|
|
213
257
|
this.driver.exec("BEGIN");
|
|
214
|
-
return undefined;
|
|
215
258
|
}
|
|
216
259
|
async _commitTransaction() {
|
|
217
260
|
this._log("COMMIT");
|
|
@@ -221,10 +264,12 @@ var SqliteAdapter = class extends __atscript_db.BaseDbAdapter {
|
|
|
221
264
|
this._log("ROLLBACK");
|
|
222
265
|
this.driver.exec("ROLLBACK");
|
|
223
266
|
}
|
|
224
|
-
/** SQLite does not use schemas — override to always exclude schema. */
|
|
267
|
+
/** SQLite does not use schemas — override to always exclude schema. */
|
|
268
|
+
resolveTableName() {
|
|
225
269
|
return super.resolveTableName(false);
|
|
226
270
|
}
|
|
227
|
-
/** SQLite enforces FK constraints natively via PRAGMA foreign_keys. */
|
|
271
|
+
/** SQLite enforces FK constraints natively via PRAGMA foreign_keys. */
|
|
272
|
+
supportsNativeForeignKeys() {
|
|
228
273
|
return true;
|
|
229
274
|
}
|
|
230
275
|
prepareId(id, _fieldType) {
|
|
@@ -233,21 +278,22 @@ var SqliteAdapter = class extends __atscript_db.BaseDbAdapter {
|
|
|
233
278
|
/**
|
|
234
279
|
* Wraps a write operation to catch native SQLite constraint errors
|
|
235
280
|
* and rethrow as structured `DbError`.
|
|
236
|
-
*/
|
|
281
|
+
*/
|
|
282
|
+
_wrapConstraintError(fn) {
|
|
237
283
|
try {
|
|
238
284
|
return fn();
|
|
239
285
|
} catch (error) {
|
|
240
286
|
if (error instanceof Error) {
|
|
241
|
-
if (error.message.includes("FOREIGN KEY constraint failed")) throw new
|
|
287
|
+
if (error.message.includes("FOREIGN KEY constraint failed")) throw new _atscript_db.DbError("FK_VIOLATION", [{
|
|
242
288
|
path: "",
|
|
243
289
|
message: error.message
|
|
244
290
|
}]);
|
|
245
291
|
const uniqueMatch = error.message.match(/UNIQUE constraint failed:\s*\S+\.(\S+)/);
|
|
246
|
-
if (uniqueMatch) throw new
|
|
292
|
+
if (uniqueMatch) throw new _atscript_db.DbError("CONFLICT", [{
|
|
247
293
|
path: uniqueMatch[1],
|
|
248
294
|
message: error.message
|
|
249
295
|
}]);
|
|
250
|
-
if (error.message.includes("UNIQUE constraint failed")) throw new
|
|
296
|
+
if (error.message.includes("UNIQUE constraint failed")) throw new _atscript_db.DbError("CONFLICT", [{
|
|
251
297
|
path: "",
|
|
252
298
|
message: error.message
|
|
253
299
|
}]);
|
|
@@ -294,46 +340,39 @@ var SqliteAdapter = class extends __atscript_db.BaseDbAdapter {
|
|
|
294
340
|
}
|
|
295
341
|
async count(query) {
|
|
296
342
|
const where = buildWhere(query.filter);
|
|
297
|
-
const
|
|
298
|
-
const sql = `SELECT COUNT(*) as cnt FROM "${esc(tableName)}" WHERE ${where.sql}`;
|
|
343
|
+
const sql = `SELECT COUNT(*) as cnt FROM "${esc(this.resolveTableName())}" WHERE ${where.sql}`;
|
|
299
344
|
this._log(sql, where.params);
|
|
300
|
-
|
|
301
|
-
return row?.cnt ?? 0;
|
|
345
|
+
return this.driver.get(sql, where.params)?.cnt ?? 0;
|
|
302
346
|
}
|
|
303
347
|
async aggregate(query) {
|
|
304
348
|
const where = buildWhere(query.filter);
|
|
305
349
|
const tableName = this.resolveTableName();
|
|
306
350
|
if (query.controls.$count) {
|
|
307
|
-
const { sql
|
|
308
|
-
this._log(sql
|
|
309
|
-
|
|
310
|
-
return [{ count: row?.count ?? 0 }];
|
|
351
|
+
const { sql, params } = buildAggregateCount(tableName, where, query.controls);
|
|
352
|
+
this._log(sql, params);
|
|
353
|
+
return [{ count: this.driver.get(sql, params)?.count ?? 0 }];
|
|
311
354
|
}
|
|
312
355
|
const { sql, params } = buildAggregateSelect(tableName, where, query.controls);
|
|
313
356
|
this._log(sql, params);
|
|
314
357
|
return this.driver.all(sql, params);
|
|
315
358
|
}
|
|
316
|
-
async updateOne(filter, data) {
|
|
359
|
+
async updateOne(filter, data, ops) {
|
|
317
360
|
const where = buildWhere(filter);
|
|
318
361
|
const tableName = this.resolveTableName();
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const sql = `UPDATE "${esc(tableName)}" SET ${setClauses.join(", ")} WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
|
|
326
|
-
const allParams = [...setParams, ...where.params];
|
|
327
|
-
this._log(sql, allParams);
|
|
328
|
-
const result = this._wrapConstraintError(() => this.driver.run(sql, allParams));
|
|
362
|
+
const { sql, params } = buildUpdate(tableName, data, {
|
|
363
|
+
sql: `rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`,
|
|
364
|
+
params: where.params
|
|
365
|
+
}, ops);
|
|
366
|
+
this._log(sql, params);
|
|
367
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
329
368
|
return {
|
|
330
369
|
matchedCount: result.changes,
|
|
331
370
|
modifiedCount: result.changes
|
|
332
371
|
};
|
|
333
372
|
}
|
|
334
|
-
async updateMany(filter, data) {
|
|
373
|
+
async updateMany(filter, data, ops) {
|
|
335
374
|
const where = buildWhere(filter);
|
|
336
|
-
const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
|
|
375
|
+
const { sql, params } = buildUpdate(this.resolveTableName(), data, where, ops);
|
|
337
376
|
this._log(sql, params);
|
|
338
377
|
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
339
378
|
return {
|
|
@@ -344,11 +383,10 @@ var SqliteAdapter = class extends __atscript_db.BaseDbAdapter {
|
|
|
344
383
|
async replaceOne(filter, data) {
|
|
345
384
|
const where = buildWhere(filter);
|
|
346
385
|
const tableName = this.resolveTableName();
|
|
347
|
-
const
|
|
386
|
+
const { sql, params } = buildUpdate(tableName, data, {
|
|
348
387
|
sql: `rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`,
|
|
349
388
|
params: where.params
|
|
350
|
-
};
|
|
351
|
-
const { sql, params } = buildUpdate(tableName, data, limitedWhere);
|
|
389
|
+
});
|
|
352
390
|
this._log(sql, params);
|
|
353
391
|
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
354
392
|
return {
|
|
@@ -371,27 +409,27 @@ var SqliteAdapter = class extends __atscript_db.BaseDbAdapter {
|
|
|
371
409
|
const tableName = this.resolveTableName();
|
|
372
410
|
const sql = `DELETE FROM "${esc(tableName)}" WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
|
|
373
411
|
this._log(sql, where.params);
|
|
374
|
-
|
|
375
|
-
return { deletedCount: result.changes };
|
|
412
|
+
return { deletedCount: this._wrapConstraintError(() => this.driver.run(sql, where.params)).changes };
|
|
376
413
|
}
|
|
377
414
|
async deleteMany(filter) {
|
|
378
415
|
const where = buildWhere(filter);
|
|
379
416
|
const { sql, params } = buildDelete(this.resolveTableName(), where);
|
|
380
417
|
this._log(sql, params);
|
|
381
|
-
|
|
382
|
-
return { deletedCount: result.changes };
|
|
418
|
+
return { deletedCount: this._wrapConstraintError(() => this.driver.run(sql, params)).changes };
|
|
383
419
|
}
|
|
384
420
|
async ensureTable() {
|
|
385
|
-
if (this._table instanceof
|
|
421
|
+
if (this._table instanceof _atscript_db.AtscriptDbView) return this.ensureView();
|
|
386
422
|
const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors, this._table.foreignKeys);
|
|
387
423
|
this._log(sql);
|
|
388
424
|
this.driver.exec(sql);
|
|
389
425
|
this._seedIncrementStart();
|
|
390
426
|
}
|
|
427
|
+
_incrementSeeded = false;
|
|
391
428
|
/**
|
|
392
429
|
* Seeds the sqlite_sequence table for auto-increment fields that have a start value.
|
|
393
430
|
* Only applies once per adapter instance (idempotent via INSERT OR IGNORE + flag).
|
|
394
|
-
*/
|
|
431
|
+
*/
|
|
432
|
+
_seedIncrementStart() {
|
|
395
433
|
if (this._incrementSeeded) return;
|
|
396
434
|
this._incrementSeeded = true;
|
|
397
435
|
const tableName = this.resolveTableName();
|
|
@@ -426,8 +464,8 @@ var SqliteAdapter = class extends __atscript_db.BaseDbAdapter {
|
|
|
426
464
|
const sqlType = this.typeMapper(field);
|
|
427
465
|
let ddl = `ALTER TABLE "${esc(tableName)}" ADD COLUMN "${esc(field.physicalName)}" ${sqlType}`;
|
|
428
466
|
if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
|
|
429
|
-
if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${(0,
|
|
430
|
-
else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${(0,
|
|
467
|
+
if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${(0, _atscript_db_sql_tools.defaultValueToSqlLiteral)(field.designType, field.defaultValue.value)}`;
|
|
468
|
+
else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${(0, _atscript_db_sql_tools.defaultValueForType)(field.designType)}`;
|
|
431
469
|
if (field.collate) ddl += ` COLLATE ${field.collate.toUpperCase()}`;
|
|
432
470
|
this._log(ddl);
|
|
433
471
|
this.driver.exec(ddl);
|
|
@@ -458,7 +496,7 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${(0, __atscri
|
|
|
458
496
|
const selectExprs = commonCols.map((c) => {
|
|
459
497
|
const field = fieldsByName.get(c);
|
|
460
498
|
if (field && !field.optional && !field.isPrimaryKey) {
|
|
461
|
-
const fallback = field.defaultValue?.kind === "value" ? (0,
|
|
499
|
+
const fallback = field.defaultValue?.kind === "value" ? (0, _atscript_db_sql_tools.defaultValueToSqlLiteral)(field.designType, field.defaultValue.value) : (0, _atscript_db_sql_tools.defaultValueForType)(field.designType);
|
|
462
500
|
return `COALESCE("${esc(c)}", ${fallback}) AS "${esc(c)}"`;
|
|
463
501
|
}
|
|
464
502
|
return `"${esc(c)}"`;
|
|
@@ -515,8 +553,7 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${(0, __atscri
|
|
|
515
553
|
return sqliteTypeFromDesignType(field.designType);
|
|
516
554
|
}
|
|
517
555
|
async getExistingColumnsForTable(tableName) {
|
|
518
|
-
|
|
519
|
-
return rows.map((r) => ({
|
|
556
|
+
return this.driver.all(`PRAGMA table_info("${esc(tableName)}")`).map((r) => ({
|
|
520
557
|
name: r.name,
|
|
521
558
|
type: r.type,
|
|
522
559
|
notnull: r.notnull === 1,
|
|
@@ -564,12 +601,12 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${(0, __atscri
|
|
|
564
601
|
for (const [col, dir] of Object.entries(controls.$sort)) orderParts.push(`t."${esc(col)}" ${dir === -1 ? "DESC" : "ASC"}`);
|
|
565
602
|
if (orderParts.length > 0) sql += ` ORDER BY ${orderParts.join(", ")}`;
|
|
566
603
|
}
|
|
567
|
-
if (controls.$limit !==
|
|
604
|
+
if (controls.$limit !== void 0) {
|
|
568
605
|
sql += ` LIMIT ?`;
|
|
569
606
|
params.push(controls.$limit);
|
|
570
607
|
}
|
|
571
|
-
if (controls.$skip !==
|
|
572
|
-
if (controls.$limit ===
|
|
608
|
+
if (controls.$skip !== void 0) {
|
|
609
|
+
if (controls.$limit === void 0) sql += ` LIMIT -1`;
|
|
573
610
|
sql += ` OFFSET ?`;
|
|
574
611
|
params.push(controls.$skip);
|
|
575
612
|
}
|
|
@@ -585,21 +622,23 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${(0, __atscri
|
|
|
585
622
|
const base = this._buildFtsBase(text, query.filter, indexName);
|
|
586
623
|
const countSql = `SELECT COUNT(*) as cnt ${base.fromWhere}`;
|
|
587
624
|
this._log(countSql, base.params);
|
|
588
|
-
const row = this.driver.get(countSql, base.params);
|
|
589
625
|
return {
|
|
590
626
|
data,
|
|
591
|
-
count:
|
|
627
|
+
count: this.driver.get(countSql, base.params)?.cnt ?? 0
|
|
592
628
|
};
|
|
593
629
|
}
|
|
594
|
-
/** Builds FTS table name from index name: `<table>__fts__<indexName>`. */
|
|
630
|
+
/** Builds FTS table name from index name: `<table>__fts__<indexName>`. */
|
|
631
|
+
_ftsTableName(indexName) {
|
|
595
632
|
return `${this.resolveTableName()}__fts__${indexName}`;
|
|
596
633
|
}
|
|
597
|
-
/** Returns fulltext indexes from table metadata. */
|
|
634
|
+
/** Returns fulltext indexes from table metadata. */
|
|
635
|
+
_getFulltextIndexes() {
|
|
598
636
|
const result = [];
|
|
599
637
|
for (const index of this._table.indexes.values()) if (index.type === "fulltext") result.push(index);
|
|
600
638
|
return result;
|
|
601
639
|
}
|
|
602
|
-
/** Resolves a fulltext index by name, or returns the first available. */
|
|
640
|
+
/** Resolves a fulltext index by name, or returns the first available. */
|
|
641
|
+
_resolveFtsIndex(indexName) {
|
|
603
642
|
const ftIndexes = this._getFulltextIndexes();
|
|
604
643
|
if (ftIndexes.length === 0) throw new Error("No search index available");
|
|
605
644
|
if (indexName) {
|
|
@@ -612,7 +651,8 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${(0, __atscri
|
|
|
612
651
|
/**
|
|
613
652
|
* Builds the shared FROM+JOIN+WHERE fragment for FTS5 queries.
|
|
614
653
|
* Both data and count queries reuse this to avoid duplicating index resolution and filter translation.
|
|
615
|
-
*/
|
|
654
|
+
*/
|
|
655
|
+
_buildFtsBase(text, filter, indexName) {
|
|
616
656
|
const ftsIndex = this._resolveFtsIndex(indexName);
|
|
617
657
|
const ftsTable = this._ftsTableName(ftsIndex.name);
|
|
618
658
|
const tableName = this.resolveTableName();
|
|
@@ -632,7 +672,8 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${(0, __atscri
|
|
|
632
672
|
}
|
|
633
673
|
/**
|
|
634
674
|
* Creates/drops FTS5 virtual tables and sync triggers to match desired fulltext indexes.
|
|
635
|
-
*/
|
|
675
|
+
*/
|
|
676
|
+
_syncFtsIndexes(tableName) {
|
|
636
677
|
const ftIndexes = this._getFulltextIndexes();
|
|
637
678
|
const desiredFtsTables = new Set(ftIndexes.map((idx) => this._ftsTableName(idx.name)));
|
|
638
679
|
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);
|
|
@@ -643,9 +684,9 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${(0, __atscri
|
|
|
643
684
|
if (!existingSet.has(ftsTable)) this._createFtsTable(tableName, ftsTable, index);
|
|
644
685
|
}
|
|
645
686
|
}
|
|
646
|
-
/** Creates an FTS5 virtual table with sync triggers and rebuilds the index. */
|
|
647
|
-
|
|
648
|
-
const fieldList =
|
|
687
|
+
/** Creates an FTS5 virtual table with sync triggers and rebuilds the index. */
|
|
688
|
+
_createFtsTable(tableName, ftsTable, index) {
|
|
689
|
+
const fieldList = index.fields.map((f) => `"${esc(f.name)}"`).join(", ");
|
|
649
690
|
const createSql = `CREATE VIRTUAL TABLE IF NOT EXISTS "${esc(ftsTable)}" USING fts5(${fieldList}, content='${tableName.replace(/'/g, "''")}', content_rowid='rowid')`;
|
|
650
691
|
this._log(createSql);
|
|
651
692
|
this.driver.exec(createSql);
|
|
@@ -665,7 +706,8 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${(0, __atscri
|
|
|
665
706
|
this._log(rebuildSql);
|
|
666
707
|
this.driver.exec(rebuildSql);
|
|
667
708
|
}
|
|
668
|
-
/** Drops an FTS5 virtual table and its sync triggers. */
|
|
709
|
+
/** Drops an FTS5 virtual table and its sync triggers. */
|
|
710
|
+
_dropFtsTable(ftsTable) {
|
|
669
711
|
for (const suffix of [
|
|
670
712
|
"__ai",
|
|
671
713
|
"__ad",
|
|
@@ -675,31 +717,27 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${(0, __atscri
|
|
|
675
717
|
this._log(sql);
|
|
676
718
|
this.driver.exec(sql);
|
|
677
719
|
}
|
|
678
|
-
/** Drops all FTS virtual tables and triggers for a content table. */
|
|
720
|
+
/** Drops all FTS virtual tables and triggers for a content table. */
|
|
721
|
+
_dropAllFtsTables(tableName) {
|
|
679
722
|
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"));
|
|
680
723
|
for (const { name } of ftsTables) this._dropFtsTable(name);
|
|
681
724
|
}
|
|
682
|
-
constructor(driver) {
|
|
683
|
-
super(), _define_property(this, "driver", void 0), _define_property(this, "_incrementSeeded", void 0), this.driver = driver, this._incrementSeeded = false;
|
|
684
|
-
this.driver.exec("PRAGMA foreign_keys = ON");
|
|
685
|
-
}
|
|
686
725
|
};
|
|
687
726
|
/** Normalizes SQLite PRAGMA dflt_value to match serialized format.
|
|
688
|
-
* PRAGMA returns `'active'` (SQL-quoted), we store `active` (raw). */
|
|
689
|
-
|
|
727
|
+
* PRAGMA returns `'active'` (SQL-quoted), we store `active` (raw). */
|
|
728
|
+
function normalizeSqliteDefault(value) {
|
|
729
|
+
if (value === null) return;
|
|
690
730
|
if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
|
|
691
731
|
return value;
|
|
692
732
|
}
|
|
693
|
-
|
|
694
733
|
//#endregion
|
|
695
|
-
//#region
|
|
734
|
+
//#region src/index.ts
|
|
696
735
|
function createAdapter(connection, options) {
|
|
697
736
|
const driver = new BetterSqlite3Driver(connection, options);
|
|
698
|
-
return new
|
|
737
|
+
return new _atscript_db.DbSpace(() => new SqliteAdapter(driver));
|
|
699
738
|
}
|
|
700
|
-
|
|
701
739
|
//#endregion
|
|
702
|
-
exports.BetterSqlite3Driver = BetterSqlite3Driver
|
|
703
|
-
exports.SqliteAdapter = SqliteAdapter
|
|
704
|
-
exports.buildWhere = buildWhere
|
|
705
|
-
exports.createAdapter = createAdapter
|
|
740
|
+
exports.BetterSqlite3Driver = BetterSqlite3Driver;
|
|
741
|
+
exports.SqliteAdapter = SqliteAdapter;
|
|
742
|
+
exports.buildWhere = buildWhere;
|
|
743
|
+
exports.createAdapter = createAdapter;
|