@quatrain/backend-sqlite 1.1.5 → 1.1.7
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/SQLiteAdapter.d.ts +49 -13
- package/dist/SQLiteAdapter.js +103 -69
- package/package.json +4 -3
- package/src/SQLiteAdapter.ts +111 -77
- package/src/__test__/SQLiteAdapter.test.ts +4 -4
- package/src/__test__/Search.test.ts +1 -1
package/dist/SQLiteAdapter.d.ts
CHANGED
|
@@ -2,7 +2,9 @@ import { DataObjectClass, AbstractBackendAdapter, BackendParameters, QueryResult
|
|
|
2
2
|
import sqlite3 from 'sqlite3';
|
|
3
3
|
import { Database } from 'sqlite';
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Backend adapter implementation for SQLite databases.
|
|
6
|
+
* Uses the `sqlite` driver to provide a fast, local relational store without external dependencies.
|
|
7
|
+
* Highly useful for local development, CI/CD testing environments, or lightweight local deployments.
|
|
6
8
|
*/
|
|
7
9
|
export declare class SQLiteAdapter extends AbstractBackendAdapter {
|
|
8
10
|
protected _connection: undefined | Database<sqlite3.Database>;
|
|
@@ -10,8 +12,11 @@ export declare class SQLiteAdapter extends AbstractBackendAdapter {
|
|
|
10
12
|
constructor(params?: BackendParameters);
|
|
11
13
|
protected _buildPath(dataObject: DataObjectClass<any>, uid?: string): string;
|
|
12
14
|
/**
|
|
13
|
-
* Executes
|
|
14
|
-
*
|
|
15
|
+
* Executes an arbitrary raw SQL query against the SQLite database.
|
|
16
|
+
*
|
|
17
|
+
* @param sql - The SQL statement with optional `?` parameterized placeholders.
|
|
18
|
+
* @param params - The array of parameter values.
|
|
19
|
+
* @returns A promise resolving to the SQLite result rows.
|
|
15
20
|
*/
|
|
16
21
|
rawQuery(sql: string, params?: any[]): Promise<any>;
|
|
17
22
|
protected _connect(): Promise<Database<sqlite3.Database>>;
|
|
@@ -28,15 +33,44 @@ export declare class SQLiteAdapter extends AbstractBackendAdapter {
|
|
|
28
33
|
*/
|
|
29
34
|
private _ensureTable;
|
|
30
35
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* @
|
|
36
|
+
* Translates a DataObject creation request into an `INSERT INTO` SQL query.
|
|
37
|
+
* SQLite handles JSON by parsing array properties internally.
|
|
38
|
+
*
|
|
39
|
+
* @param dataObject - The DataObject payload.
|
|
40
|
+
* @param desiredUid - Optional explicit UUID.
|
|
41
|
+
* @returns A promise resolving to the saved DataObject.
|
|
35
42
|
*/
|
|
36
43
|
create(dataObject: DataObjectClass<any>, desiredUid: string | undefined): Promise<DataObjectClass<any>>;
|
|
44
|
+
/**
|
|
45
|
+
* Executes a `SELECT *` query to retrieve a document by its UID.
|
|
46
|
+
* Handles mapping of JSON text columns back into arrays/objects.
|
|
47
|
+
*
|
|
48
|
+
* @param dataObject - The empty DataObject containing the target path.
|
|
49
|
+
* @returns A promise resolving to the hydrated DataObject.
|
|
50
|
+
*/
|
|
37
51
|
read(dataObject: DataObjectClass<any>): Promise<DataObjectClass<any>>;
|
|
52
|
+
/**
|
|
53
|
+
* Processes an `UPDATE` command for modified object properties.
|
|
54
|
+
* Automatically ensures the table exists and properly escapes JSON-backed properties.
|
|
55
|
+
*
|
|
56
|
+
* @param dataObject - The modified DataObject.
|
|
57
|
+
* @returns A promise resolving to the updated instance.
|
|
58
|
+
*/
|
|
38
59
|
update(dataObject: DataObjectClass<any>): Promise<DataObjectClass<any>>;
|
|
60
|
+
/**
|
|
61
|
+
* Generates a `DELETE FROM` or `UPDATE` query depending on the `hardDelete` parameter.
|
|
62
|
+
*
|
|
63
|
+
* @param dataObject - The DataObject to remove.
|
|
64
|
+
* @param hardDelete - Force permanent deletion over soft delete.
|
|
65
|
+
* @returns A promise resolving upon completion.
|
|
66
|
+
*/
|
|
39
67
|
delete(dataObject: DataObjectClass<any>, hardDelete?: boolean): Promise<DataObjectClass<any>>;
|
|
68
|
+
/**
|
|
69
|
+
* Wipes all records from a table by executing a blanket `DELETE FROM`.
|
|
70
|
+
*
|
|
71
|
+
* @param collection - The table name to purge.
|
|
72
|
+
* @param batchSize - Ignored for SQLite bulk deletes.
|
|
73
|
+
*/
|
|
40
74
|
deleteCollection(collection: string, batchSize?: number): Promise<void>;
|
|
41
75
|
/**
|
|
42
76
|
* Convert array into SQL expression
|
|
@@ -45,12 +79,14 @@ export declare class SQLiteAdapter extends AbstractBackendAdapter {
|
|
|
45
79
|
*/
|
|
46
80
|
protected _array2String(from: (string | number)[]): string;
|
|
47
81
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* @param
|
|
52
|
-
* @
|
|
53
|
-
* @
|
|
82
|
+
* Translates the Quatrain `Filters` logic into SQLite query syntax.
|
|
83
|
+
* Employs internal `json_each` extensions for Array-contains searches when JSON1 is available.
|
|
84
|
+
*
|
|
85
|
+
* @param dataObject - The targeted collection object.
|
|
86
|
+
* @param filters - Requested Query filters.
|
|
87
|
+
* @param pagination - Query Limits & Pagination rules.
|
|
88
|
+
* @param parent - Optional parent linkage.
|
|
89
|
+
* @returns A promise resolving to hydrated objects and metadata.
|
|
54
90
|
*/
|
|
55
91
|
find(dataObject: DataObjectClass<any>, filters?: Filters | Filter[] | undefined, pagination?: SortAndLimit | undefined, parent?: DataObjectClass<any> | undefined): Promise<QueryResultType<DataObjectClass<any>>>;
|
|
56
92
|
/**
|
package/dist/SQLiteAdapter.js
CHANGED
|
@@ -34,7 +34,9 @@ const operatorsMap = {
|
|
|
34
34
|
isNotNull: 'IS NOT NULL',
|
|
35
35
|
};
|
|
36
36
|
/**
|
|
37
|
-
*
|
|
37
|
+
* Backend adapter implementation for SQLite databases.
|
|
38
|
+
* Uses the `sqlite` driver to provide a fast, local relational store without external dependencies.
|
|
39
|
+
* Highly useful for local development, CI/CD testing environments, or lightweight local deployments.
|
|
38
40
|
*/
|
|
39
41
|
class SQLiteAdapter extends backend_1.AbstractBackendAdapter {
|
|
40
42
|
constructor(params = {}) {
|
|
@@ -61,8 +63,11 @@ class SQLiteAdapter extends backend_1.AbstractBackendAdapter {
|
|
|
61
63
|
return path;
|
|
62
64
|
}
|
|
63
65
|
/**
|
|
64
|
-
* Executes
|
|
65
|
-
*
|
|
66
|
+
* Executes an arbitrary raw SQL query against the SQLite database.
|
|
67
|
+
*
|
|
68
|
+
* @param sql - The SQL statement with optional `?` parameterized placeholders.
|
|
69
|
+
* @param params - The array of parameter values.
|
|
70
|
+
* @returns A promise resolving to the SQLite result rows.
|
|
66
71
|
*/
|
|
67
72
|
rawQuery(sql_1) {
|
|
68
73
|
return __awaiter(this, arguments, void 0, function* (sql, params = []) {
|
|
@@ -175,72 +180,79 @@ class SQLiteAdapter extends backend_1.AbstractBackendAdapter {
|
|
|
175
180
|
});
|
|
176
181
|
}
|
|
177
182
|
/**
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
* @
|
|
183
|
+
* Translates a DataObject creation request into an `INSERT INTO` SQL query.
|
|
184
|
+
* SQLite handles JSON by parsing array properties internally.
|
|
185
|
+
*
|
|
186
|
+
* @param dataObject - The DataObject payload.
|
|
187
|
+
* @param desiredUid - Optional explicit UUID.
|
|
188
|
+
* @returns A promise resolving to the saved DataObject.
|
|
182
189
|
*/
|
|
183
190
|
create(dataObject, desiredUid) {
|
|
184
191
|
return __awaiter(this, void 0, void 0, function* () {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
throw new backend_1.BackendError(`Data object already has an uid and can't be created`);
|
|
189
|
-
}
|
|
190
|
-
const uid = desiredUid || (0, node_crypto_1.randomUUID)();
|
|
191
|
-
// Make sure table exists
|
|
192
|
-
yield this._ensureTable(dataObject);
|
|
193
|
-
// Set uid before middlewares so they can use it
|
|
194
|
-
dataObject.uri.path = this._buildPath(dataObject, uid);
|
|
195
|
-
// execute middlewares
|
|
196
|
-
yield this.executeMiddlewares(dataObject, backend_1.BackendAction.CREATE, 'before', {
|
|
197
|
-
useDateFormat: true,
|
|
198
|
-
});
|
|
199
|
-
const data = dataObject.toJSON({
|
|
200
|
-
withoutURIData: true,
|
|
201
|
-
converters: {
|
|
202
|
-
datetime: (v) => (v ? new Date(v).getTime() : v), // Store as timestamp in SQLite
|
|
203
|
-
},
|
|
204
|
-
});
|
|
205
|
-
const db = yield this._connect();
|
|
206
|
-
const collection = this.getCollection(dataObject);
|
|
207
|
-
let columns = ['id'];
|
|
208
|
-
let placeholders = ['?'];
|
|
209
|
-
let values = [uid];
|
|
210
|
-
Object.entries(data).forEach(([key, value]) => {
|
|
211
|
-
columns.push(`"${key}"`);
|
|
212
|
-
placeholders.push('?');
|
|
213
|
-
// Convert arrays and objects to JSON strings
|
|
214
|
-
if (Array.isArray(value) ||
|
|
215
|
-
(typeof value === 'object' && value !== null)) {
|
|
216
|
-
values.push(JSON.stringify(value));
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
values.push(value);
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
const query = `INSERT INTO ${collection === null || collection === void 0 ? void 0 : collection.toLowerCase()} (${columns.join(', ')})
|
|
223
|
-
VALUES (${placeholders.join(', ')})`;
|
|
224
|
-
backend_1.Backend.debug(`[SQLA] ${query}`);
|
|
225
|
-
backend_1.Backend.debug(`[SQLA] Values ${JSON.stringify(values)}`);
|
|
226
|
-
yield db.run(query, values);
|
|
227
|
-
// uri.path is already set before middlewares
|
|
228
|
-
dataObject.uri.label = data && Reflect.get(data, 'name');
|
|
229
|
-
dataObject.isPersisted(true);
|
|
230
|
-
backend_1.Backend.info(`[SQLA] Saved object "${data.name}" at path ${dataObject.path}`);
|
|
231
|
-
yield this.executeMiddlewares(dataObject, backend_1.BackendAction.CREATE, 'after', {
|
|
232
|
-
useDateFormat: true,
|
|
233
|
-
});
|
|
234
|
-
resolve(dataObject);
|
|
235
|
-
}
|
|
236
|
-
catch (err) {
|
|
237
|
-
console.error(err);
|
|
238
|
-
backend_1.Backend.error(err.message);
|
|
239
|
-
reject(new backend_1.BackendError(err.message));
|
|
192
|
+
try {
|
|
193
|
+
if (dataObject.uid) {
|
|
194
|
+
throw new backend_1.BackendError(`Data object already has an uid and can't be created`);
|
|
240
195
|
}
|
|
241
|
-
|
|
196
|
+
const uid = desiredUid || (0, node_crypto_1.randomUUID)();
|
|
197
|
+
// Make sure table exists
|
|
198
|
+
yield this._ensureTable(dataObject);
|
|
199
|
+
// Set uid before middlewares so they can use it
|
|
200
|
+
dataObject.uri.path = this._buildPath(dataObject, uid);
|
|
201
|
+
// execute middlewares
|
|
202
|
+
yield this.executeMiddlewares(dataObject, backend_1.BackendAction.CREATE, 'before', {
|
|
203
|
+
useDateFormat: true,
|
|
204
|
+
});
|
|
205
|
+
const data = dataObject.toJSON({
|
|
206
|
+
withoutURIData: true,
|
|
207
|
+
converters: {
|
|
208
|
+
datetime: (v) => (v ? new Date(v).getTime() : v), // Store as timestamp in SQLite
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
const db = yield this._connect();
|
|
212
|
+
const collection = this.getCollection(dataObject);
|
|
213
|
+
let columns = ['id'];
|
|
214
|
+
let placeholders = ['?'];
|
|
215
|
+
let values = [uid];
|
|
216
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
217
|
+
columns.push(`"${key}"`);
|
|
218
|
+
placeholders.push('?');
|
|
219
|
+
// Convert arrays and objects to JSON strings
|
|
220
|
+
if (Array.isArray(value) ||
|
|
221
|
+
(typeof value === 'object' && value !== null)) {
|
|
222
|
+
values.push(JSON.stringify(value));
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
values.push(value);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
const query = `INSERT INTO ${collection === null || collection === void 0 ? void 0 : collection.toLowerCase()} (${columns.join(', ')})
|
|
229
|
+
VALUES (${placeholders.join(', ')})`;
|
|
230
|
+
backend_1.Backend.debug(`[SQLA] ${query}`);
|
|
231
|
+
backend_1.Backend.debug(`[SQLA] Values ${JSON.stringify(values)}`);
|
|
232
|
+
yield db.run(query, values);
|
|
233
|
+
// uri.path is already set before middlewares
|
|
234
|
+
dataObject.uri.label = data && Reflect.get(data, 'name');
|
|
235
|
+
dataObject.isPersisted(true);
|
|
236
|
+
backend_1.Backend.info(`[SQLA] Saved object "${data.name}" at path ${dataObject.path}`);
|
|
237
|
+
yield this.executeMiddlewares(dataObject, backend_1.BackendAction.CREATE, 'after', {
|
|
238
|
+
useDateFormat: true,
|
|
239
|
+
});
|
|
240
|
+
return dataObject;
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
console.error(err);
|
|
244
|
+
backend_1.Backend.error(err.message);
|
|
245
|
+
throw new backend_1.BackendError(err.message);
|
|
246
|
+
}
|
|
242
247
|
});
|
|
243
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* Executes a `SELECT *` query to retrieve a document by its UID.
|
|
251
|
+
* Handles mapping of JSON text columns back into arrays/objects.
|
|
252
|
+
*
|
|
253
|
+
* @param dataObject - The empty DataObject containing the target path.
|
|
254
|
+
* @returns A promise resolving to the hydrated DataObject.
|
|
255
|
+
*/
|
|
244
256
|
read(dataObject) {
|
|
245
257
|
return __awaiter(this, void 0, void 0, function* () {
|
|
246
258
|
const path = dataObject.path;
|
|
@@ -305,6 +317,13 @@ class SQLiteAdapter extends backend_1.AbstractBackendAdapter {
|
|
|
305
317
|
return dataObject;
|
|
306
318
|
});
|
|
307
319
|
}
|
|
320
|
+
/**
|
|
321
|
+
* Processes an `UPDATE` command for modified object properties.
|
|
322
|
+
* Automatically ensures the table exists and properly escapes JSON-backed properties.
|
|
323
|
+
*
|
|
324
|
+
* @param dataObject - The modified DataObject.
|
|
325
|
+
* @returns A promise resolving to the updated instance.
|
|
326
|
+
*/
|
|
308
327
|
update(dataObject) {
|
|
309
328
|
return __awaiter(this, void 0, void 0, function* () {
|
|
310
329
|
if (dataObject.uid === undefined) {
|
|
@@ -351,6 +370,13 @@ class SQLiteAdapter extends backend_1.AbstractBackendAdapter {
|
|
|
351
370
|
return dataObject;
|
|
352
371
|
});
|
|
353
372
|
}
|
|
373
|
+
/**
|
|
374
|
+
* Generates a `DELETE FROM` or `UPDATE` query depending on the `hardDelete` parameter.
|
|
375
|
+
*
|
|
376
|
+
* @param dataObject - The DataObject to remove.
|
|
377
|
+
* @param hardDelete - Force permanent deletion over soft delete.
|
|
378
|
+
* @returns A promise resolving upon completion.
|
|
379
|
+
*/
|
|
354
380
|
delete(dataObject_1) {
|
|
355
381
|
return __awaiter(this, arguments, void 0, function* (dataObject, hardDelete = false) {
|
|
356
382
|
if (dataObject.uid === undefined) {
|
|
@@ -381,6 +407,12 @@ class SQLiteAdapter extends backend_1.AbstractBackendAdapter {
|
|
|
381
407
|
return dataObject;
|
|
382
408
|
});
|
|
383
409
|
}
|
|
410
|
+
/**
|
|
411
|
+
* Wipes all records from a table by executing a blanket `DELETE FROM`.
|
|
412
|
+
*
|
|
413
|
+
* @param collection - The table name to purge.
|
|
414
|
+
* @param batchSize - Ignored for SQLite bulk deletes.
|
|
415
|
+
*/
|
|
384
416
|
deleteCollection(collection_1) {
|
|
385
417
|
return __awaiter(this, arguments, void 0, function* (collection, batchSize = 500) {
|
|
386
418
|
backend_1.Backend.log(`Deleting all records from collection '${collection}'`);
|
|
@@ -402,12 +434,14 @@ class SQLiteAdapter extends backend_1.AbstractBackendAdapter {
|
|
|
402
434
|
return `'${JSON.stringify(from)}'`;
|
|
403
435
|
}
|
|
404
436
|
/**
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
* @param
|
|
409
|
-
* @
|
|
410
|
-
* @
|
|
437
|
+
* Translates the Quatrain `Filters` logic into SQLite query syntax.
|
|
438
|
+
* Employs internal `json_each` extensions for Array-contains searches when JSON1 is available.
|
|
439
|
+
*
|
|
440
|
+
* @param dataObject - The targeted collection object.
|
|
441
|
+
* @param filters - Requested Query filters.
|
|
442
|
+
* @param pagination - Query Limits & Pagination rules.
|
|
443
|
+
* @param parent - Optional parent linkage.
|
|
444
|
+
* @returns A promise resolving to hydrated objects and metadata.
|
|
411
445
|
*/
|
|
412
446
|
find(dataObject_1) {
|
|
413
447
|
return __awaiter(this, arguments, void 0, function* (dataObject, filters = undefined, pagination = undefined, parent = undefined) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quatrain/backend-sqlite",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"description": "Backend adapter for SQLite",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,14 +20,15 @@
|
|
|
20
20
|
},
|
|
21
21
|
"author": "Quatrain Développement SAS <developers@quatrain.com>",
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@quatrain/backend": "^1.2.
|
|
24
|
-
"@quatrain/core": "^1.2.
|
|
23
|
+
"@quatrain/backend": "^1.2.7",
|
|
24
|
+
"@quatrain/core": "^1.2.5",
|
|
25
25
|
"sqlite": "^5.1.1",
|
|
26
26
|
"sqlite3": "^5.1.7"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@faker-js/faker": "^7.6.0",
|
|
30
30
|
"@jest/expect": "^30.0.0",
|
|
31
|
+
"@quatrain/testing": "^1.1.3",
|
|
31
32
|
"@tsconfig/recommended": "^1.0.1",
|
|
32
33
|
"@types/jest": "^29.5.12",
|
|
33
34
|
"@types/node": "^22.10.1",
|
package/src/SQLiteAdapter.ts
CHANGED
|
@@ -40,7 +40,9 @@ const operatorsMap: { [x: string]: string } = {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
43
|
+
* Backend adapter implementation for SQLite databases.
|
|
44
|
+
* Uses the `sqlite` driver to provide a fast, local relational store without external dependencies.
|
|
45
|
+
* Highly useful for local development, CI/CD testing environments, or lightweight local deployments.
|
|
44
46
|
*/
|
|
45
47
|
export class SQLiteAdapter extends AbstractBackendAdapter {
|
|
46
48
|
protected _connection: undefined | Database<sqlite3.Database>
|
|
@@ -78,8 +80,11 @@ export class SQLiteAdapter extends AbstractBackendAdapter {
|
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
/**
|
|
81
|
-
* Executes
|
|
82
|
-
*
|
|
83
|
+
* Executes an arbitrary raw SQL query against the SQLite database.
|
|
84
|
+
*
|
|
85
|
+
* @param sql - The SQL statement with optional `?` parameterized placeholders.
|
|
86
|
+
* @param params - The array of parameter values.
|
|
87
|
+
* @returns A promise resolving to the SQLite result rows.
|
|
83
88
|
*/
|
|
84
89
|
async rawQuery(sql: string, params: any[] = []): Promise<any> {
|
|
85
90
|
const connection = await this._connect()
|
|
@@ -205,98 +210,105 @@ export class SQLiteAdapter extends AbstractBackendAdapter {
|
|
|
205
210
|
}
|
|
206
211
|
|
|
207
212
|
/**
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
* @
|
|
213
|
+
* Translates a DataObject creation request into an `INSERT INTO` SQL query.
|
|
214
|
+
* SQLite handles JSON by parsing array properties internally.
|
|
215
|
+
*
|
|
216
|
+
* @param dataObject - The DataObject payload.
|
|
217
|
+
* @param desiredUid - Optional explicit UUID.
|
|
218
|
+
* @returns A promise resolving to the saved DataObject.
|
|
212
219
|
*/
|
|
213
220
|
async create(
|
|
214
221
|
dataObject: DataObjectClass<any>,
|
|
215
222
|
desiredUid: string | undefined
|
|
216
223
|
): Promise<DataObjectClass<any>> {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
+
try {
|
|
225
|
+
if (dataObject.uid) {
|
|
226
|
+
throw new BackendError(
|
|
227
|
+
`Data object already has an uid and can't be created`
|
|
228
|
+
)
|
|
229
|
+
}
|
|
224
230
|
|
|
225
|
-
|
|
231
|
+
const uid = desiredUid || randomUUID()
|
|
226
232
|
|
|
227
|
-
|
|
228
|
-
|
|
233
|
+
// Make sure table exists
|
|
234
|
+
await this._ensureTable(dataObject)
|
|
229
235
|
|
|
230
|
-
|
|
231
|
-
|
|
236
|
+
// Set uid before middlewares so they can use it
|
|
237
|
+
dataObject.uri.path = this._buildPath(dataObject, uid)
|
|
232
238
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
239
|
+
// execute middlewares
|
|
240
|
+
await this.executeMiddlewares(dataObject, BackendAction.CREATE, 'before', {
|
|
241
|
+
useDateFormat: true,
|
|
242
|
+
})
|
|
237
243
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
+
const data = dataObject.toJSON({
|
|
245
|
+
withoutURIData: true,
|
|
246
|
+
converters: {
|
|
247
|
+
datetime: (v: any) => (v ? new Date(v).getTime() : v), // Store as timestamp in SQLite
|
|
248
|
+
},
|
|
249
|
+
})
|
|
244
250
|
|
|
245
|
-
|
|
246
|
-
|
|
251
|
+
const db = await this._connect()
|
|
252
|
+
const collection = this.getCollection(dataObject)
|
|
247
253
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
254
|
+
let columns = ['id']
|
|
255
|
+
let placeholders = ['?']
|
|
256
|
+
let values = [uid]
|
|
251
257
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
258
|
+
Object.entries(data).forEach(
|
|
259
|
+
([key, value]: [key: string, value: any]) => {
|
|
260
|
+
columns.push(`"${key}"`)
|
|
261
|
+
placeholders.push('?')
|
|
256
262
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
263
|
+
// Convert arrays and objects to JSON strings
|
|
264
|
+
if (
|
|
265
|
+
Array.isArray(value) ||
|
|
266
|
+
(typeof value === 'object' && value !== null)
|
|
267
|
+
) {
|
|
268
|
+
values.push(JSON.stringify(value))
|
|
269
|
+
} else {
|
|
270
|
+
values.push(value)
|
|
266
271
|
}
|
|
267
|
-
|
|
272
|
+
}
|
|
273
|
+
)
|
|
268
274
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
275
|
+
const query = `INSERT INTO ${collection?.toLowerCase()} (${columns.join(
|
|
276
|
+
', '
|
|
277
|
+
)})
|
|
278
|
+
VALUES (${placeholders.join(', ')})`
|
|
273
279
|
|
|
274
|
-
|
|
275
|
-
|
|
280
|
+
Backend.debug(`[SQLA] ${query}`)
|
|
281
|
+
Backend.debug(`[SQLA] Values ${JSON.stringify(values)}`)
|
|
276
282
|
|
|
277
|
-
|
|
283
|
+
await db.run(query, values)
|
|
278
284
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
285
|
+
// uri.path is already set before middlewares
|
|
286
|
+
dataObject.uri.label = data && Reflect.get(data, 'name')
|
|
287
|
+
dataObject.isPersisted(true)
|
|
282
288
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
289
|
+
Backend.info(
|
|
290
|
+
`[SQLA] Saved object "${data.name}" at path ${dataObject.path}`
|
|
291
|
+
)
|
|
286
292
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
293
|
+
await this.executeMiddlewares(dataObject, BackendAction.CREATE, 'after', {
|
|
294
|
+
useDateFormat: true,
|
|
295
|
+
})
|
|
290
296
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
})
|
|
297
|
+
return dataObject
|
|
298
|
+
} catch (err) {
|
|
299
|
+
console.error(err)
|
|
300
|
+
Backend.error((err as Error).message)
|
|
301
|
+
throw new BackendError((err as Error).message)
|
|
302
|
+
}
|
|
298
303
|
}
|
|
299
304
|
|
|
305
|
+
/**
|
|
306
|
+
* Executes a `SELECT *` query to retrieve a document by its UID.
|
|
307
|
+
* Handles mapping of JSON text columns back into arrays/objects.
|
|
308
|
+
*
|
|
309
|
+
* @param dataObject - The empty DataObject containing the target path.
|
|
310
|
+
* @returns A promise resolving to the hydrated DataObject.
|
|
311
|
+
*/
|
|
300
312
|
async read(dataObject: DataObjectClass<any>): Promise<DataObjectClass<any>> {
|
|
301
313
|
const path = dataObject.path
|
|
302
314
|
const collection = this.getCollection(dataObject)
|
|
@@ -384,6 +396,13 @@ export class SQLiteAdapter extends AbstractBackendAdapter {
|
|
|
384
396
|
return dataObject
|
|
385
397
|
}
|
|
386
398
|
|
|
399
|
+
/**
|
|
400
|
+
* Processes an `UPDATE` command for modified object properties.
|
|
401
|
+
* Automatically ensures the table exists and properly escapes JSON-backed properties.
|
|
402
|
+
*
|
|
403
|
+
* @param dataObject - The modified DataObject.
|
|
404
|
+
* @returns A promise resolving to the updated instance.
|
|
405
|
+
*/
|
|
387
406
|
async update(
|
|
388
407
|
dataObject: DataObjectClass<any>
|
|
389
408
|
): Promise<DataObjectClass<any>> {
|
|
@@ -452,6 +471,13 @@ export class SQLiteAdapter extends AbstractBackendAdapter {
|
|
|
452
471
|
return dataObject
|
|
453
472
|
}
|
|
454
473
|
|
|
474
|
+
/**
|
|
475
|
+
* Generates a `DELETE FROM` or `UPDATE` query depending on the `hardDelete` parameter.
|
|
476
|
+
*
|
|
477
|
+
* @param dataObject - The DataObject to remove.
|
|
478
|
+
* @param hardDelete - Force permanent deletion over soft delete.
|
|
479
|
+
* @returns A promise resolving upon completion.
|
|
480
|
+
*/
|
|
455
481
|
async delete(
|
|
456
482
|
dataObject: DataObjectClass<any>,
|
|
457
483
|
hardDelete = false
|
|
@@ -493,6 +519,12 @@ export class SQLiteAdapter extends AbstractBackendAdapter {
|
|
|
493
519
|
return dataObject
|
|
494
520
|
}
|
|
495
521
|
|
|
522
|
+
/**
|
|
523
|
+
* Wipes all records from a table by executing a blanket `DELETE FROM`.
|
|
524
|
+
*
|
|
525
|
+
* @param collection - The table name to purge.
|
|
526
|
+
* @param batchSize - Ignored for SQLite bulk deletes.
|
|
527
|
+
*/
|
|
496
528
|
async deleteCollection(collection: string, batchSize = 500): Promise<void> {
|
|
497
529
|
Backend.log(`Deleting all records from collection '${collection}'`)
|
|
498
530
|
const db = await this._connect()
|
|
@@ -519,12 +551,14 @@ export class SQLiteAdapter extends AbstractBackendAdapter {
|
|
|
519
551
|
}
|
|
520
552
|
|
|
521
553
|
/**
|
|
522
|
-
*
|
|
523
|
-
*
|
|
524
|
-
*
|
|
525
|
-
* @param
|
|
526
|
-
* @
|
|
527
|
-
* @
|
|
554
|
+
* Translates the Quatrain `Filters` logic into SQLite query syntax.
|
|
555
|
+
* Employs internal `json_each` extensions for Array-contains searches when JSON1 is available.
|
|
556
|
+
*
|
|
557
|
+
* @param dataObject - The targeted collection object.
|
|
558
|
+
* @param filters - Requested Query filters.
|
|
559
|
+
* @param pagination - Query Limits & Pagination rules.
|
|
560
|
+
* @param parent - Optional parent linkage.
|
|
561
|
+
* @returns A promise resolving to hydrated objects and metadata.
|
|
528
562
|
*/
|
|
529
563
|
async find(
|
|
530
564
|
dataObject: DataObjectClass<any>,
|
|
@@ -208,7 +208,7 @@ describe('SQLiteAdapter Tests', () => {
|
|
|
208
208
|
testUser._.firstname = `SearchUser${i}`
|
|
209
209
|
testUser._.lastname = 'SearchTest'
|
|
210
210
|
testUser._.email = `searchuser${i}@test.com`
|
|
211
|
-
testUser._.password = 'password'
|
|
211
|
+
testUser._.password = 'password' // NOSONAR
|
|
212
212
|
await adapter.create(testUser.dataObject, undefined)
|
|
213
213
|
}
|
|
214
214
|
})
|
|
@@ -314,7 +314,7 @@ describe('SQLiteAdapter Tests', () => {
|
|
|
314
314
|
newUser._.firstname = 'Auto'
|
|
315
315
|
newUser._.lastname = 'Table'
|
|
316
316
|
newUser._.email = 'auto@table.com'
|
|
317
|
-
newUser._.password = 'password'
|
|
317
|
+
newUser._.password = 'password' // NOSONAR
|
|
318
318
|
|
|
319
319
|
// This should automatically create the table
|
|
320
320
|
const result = await adapter.create(newUser.dataObject, undefined)
|
|
@@ -327,7 +327,7 @@ describe('SQLiteAdapter Tests', () => {
|
|
|
327
327
|
user._.firstname = 'Table'
|
|
328
328
|
user._.lastname = 'Exists'
|
|
329
329
|
user._.email = 'table@exists.com'
|
|
330
|
-
user._.password = 'password'
|
|
330
|
+
user._.password = 'password' // NOSONAR
|
|
331
331
|
|
|
332
332
|
await adapter.create(user.dataObject, undefined)
|
|
333
333
|
|
|
@@ -404,7 +404,7 @@ describe('SQLiteAdapter Tests', () => {
|
|
|
404
404
|
testUser._.firstname = 'Ref'
|
|
405
405
|
testUser._.lastname = 'Test'
|
|
406
406
|
testUser._.email = 'ref@test.com'
|
|
407
|
-
testUser._.password = 'password'
|
|
407
|
+
testUser._.password = 'password' // NOSONAR
|
|
408
408
|
testUser._.entity = entity
|
|
409
409
|
|
|
410
410
|
const result = await adapter.create(testUser.dataObject, undefined)
|
|
@@ -21,7 +21,7 @@ describe('SQLite Search Operations', () => {
|
|
|
21
21
|
testUser._.firstname = `TestUser${i}`
|
|
22
22
|
testUser._.lastname = i % 2 === 0 ? 'Even' : 'Odd'
|
|
23
23
|
testUser._.email = `testuser${i}@example.com`
|
|
24
|
-
testUser._.password = 'password'
|
|
24
|
+
testUser._.password = 'password' // NOSONAR
|
|
25
25
|
|
|
26
26
|
// Some users belong to the entity
|
|
27
27
|
if (i < 5) {
|