@travetto/model-sqlite 8.0.0-alpha.3 → 8.0.0-alpha.5
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/package.json +6 -8
- package/src/connection.ts +35 -24
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-sqlite",
|
|
3
|
-
"version": "8.0.0-alpha.
|
|
3
|
+
"version": "8.0.0-alpha.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "SQLite backing for the travetto model module, with real-time modeling support for SQL schemas.",
|
|
6
6
|
"keywords": [
|
|
@@ -27,13 +27,11 @@
|
|
|
27
27
|
"directory": "module/model-sqlite"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@travetto/config": "^8.0.0-alpha.
|
|
31
|
-
"@travetto/context": "^8.0.0-alpha.
|
|
32
|
-
"@travetto/model": "^8.0.0-alpha.
|
|
33
|
-
"@travetto/model-query": "^8.0.0-alpha.
|
|
34
|
-
"@travetto/model-sql": "^8.0.0-alpha.
|
|
35
|
-
"@types/better-sqlite3": "^7.6.13",
|
|
36
|
-
"better-sqlite3": "^12.6.2"
|
|
30
|
+
"@travetto/config": "^8.0.0-alpha.5",
|
|
31
|
+
"@travetto/context": "^8.0.0-alpha.5",
|
|
32
|
+
"@travetto/model": "^8.0.0-alpha.5",
|
|
33
|
+
"@travetto/model-query": "^8.0.0-alpha.5",
|
|
34
|
+
"@travetto/model-sql": "^8.0.0-alpha.5"
|
|
37
35
|
},
|
|
38
36
|
"travetto": {
|
|
39
37
|
"displayName": "SQLite Model Service",
|
package/src/connection.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { DatabaseSync, type DatabaseSyncOptions, type SQLInputValue, } from 'node:sqlite';
|
|
3
4
|
|
|
4
|
-
import sqlDb, { type Database, type Options } from 'better-sqlite3';
|
|
5
5
|
import { type Pool, createPool } from 'generic-pool';
|
|
6
6
|
|
|
7
7
|
import { ShutdownManager, Util, Runtime, RuntimeError, castTo } from '@travetto/runtime';
|
|
@@ -17,16 +17,16 @@ const isRecoverableError = (error: unknown): error is Error =>
|
|
|
17
17
|
/**
|
|
18
18
|
* Connection support for Sqlite
|
|
19
19
|
*/
|
|
20
|
-
export class SqliteConnection extends Connection<
|
|
20
|
+
export class SqliteConnection extends Connection<DatabaseSync> {
|
|
21
21
|
|
|
22
22
|
isolatedTransactions = false;
|
|
23
23
|
|
|
24
|
-
#config: SQLModelConfig<
|
|
25
|
-
#pool: Pool<
|
|
24
|
+
#config: SQLModelConfig<DatabaseSyncOptions & { file?: string }>;
|
|
25
|
+
#pool: Pool<DatabaseSync>;
|
|
26
26
|
|
|
27
27
|
constructor(
|
|
28
28
|
context: AsyncContext,
|
|
29
|
-
config: SQLModelConfig<
|
|
29
|
+
config: SQLModelConfig<DatabaseSyncOptions & { file?: string }>
|
|
30
30
|
) {
|
|
31
31
|
super(context);
|
|
32
32
|
this.#config = config;
|
|
@@ -38,7 +38,6 @@ export class SqliteConnection extends Connection<Database> {
|
|
|
38
38
|
return await operation();
|
|
39
39
|
} catch (error) {
|
|
40
40
|
if (isRecoverableError(error)) {
|
|
41
|
-
console.error('Failed, and waiting', retries);
|
|
42
41
|
await Util.blockingTimeout(delay);
|
|
43
42
|
} else {
|
|
44
43
|
throw error;
|
|
@@ -48,13 +47,17 @@ export class SqliteConnection extends Connection<Database> {
|
|
|
48
47
|
throw new RuntimeError('Max retries exceeded');
|
|
49
48
|
}
|
|
50
49
|
|
|
51
|
-
async #create(): Promise<
|
|
50
|
+
async #create(): Promise<DatabaseSync> {
|
|
52
51
|
const file = path.resolve(this.#config.options.file ?? Runtime.toolPath('@', 'sqlite_db'));
|
|
53
52
|
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
54
|
-
const db = new
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
const db = new DatabaseSync(file, this.#config.options);
|
|
54
|
+
for (const q of [
|
|
55
|
+
'PRAGMA foreign_keys = ON',
|
|
56
|
+
'PRAGMA journal_mode = WAL',
|
|
57
|
+
'PRAGMA synchronous = NORMAL',
|
|
58
|
+
]) {
|
|
59
|
+
await this.#withRetries(async () => db.exec(q));
|
|
60
|
+
}
|
|
58
61
|
db.function('regexp', (a, b) => new RegExp(`${a}`).test(`${b}`) ? 1 : 0);
|
|
59
62
|
return db;
|
|
60
63
|
}
|
|
@@ -68,7 +71,7 @@ export class SqliteConnection extends Connection<Database> {
|
|
|
68
71
|
|
|
69
72
|
await this.#create();
|
|
70
73
|
|
|
71
|
-
this.#pool = createPool<
|
|
74
|
+
this.#pool = createPool<DatabaseSync>({
|
|
72
75
|
create: () => this.#withRetries(() => this.#create()),
|
|
73
76
|
destroy: async db => { db.close(); }
|
|
74
77
|
}, { max: 1 });
|
|
@@ -77,29 +80,37 @@ export class SqliteConnection extends Connection<Database> {
|
|
|
77
80
|
ShutdownManager.signal.addEventListener('abort', () => this.#pool.clear());
|
|
78
81
|
}
|
|
79
82
|
|
|
80
|
-
async execute<T = unknown>(connection:
|
|
83
|
+
async execute<T = unknown>(connection: DatabaseSync, query: string, values?: unknown[]): Promise<{ count: number, records: T[] }> {
|
|
84
|
+
const isSelect = query.trim().startsWith('SELECT');
|
|
81
85
|
return this.#withRetries(async () => {
|
|
82
86
|
console.debug('Executing query', { query });
|
|
83
87
|
|
|
84
88
|
try {
|
|
85
|
-
const prepared = connection.prepare
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
+
const prepared = connection.prepare(query);
|
|
90
|
+
prepared.setReadBigInts(true);
|
|
91
|
+
if (isSelect) {
|
|
92
|
+
const out = prepared.all(...castTo<SQLInputValue[]>(values ?? []));
|
|
93
|
+
const records: T[] = out.map(item => ({ ...castTo<T>(item) }));
|
|
89
94
|
return { count: out.length, records };
|
|
90
95
|
} else {
|
|
91
|
-
const out = prepared.run(...values ?? []);
|
|
92
|
-
return { count: out.changes, records: [] };
|
|
96
|
+
const out = prepared.run(...castTo<SQLInputValue[]>(values ?? []));
|
|
97
|
+
return { count: typeof out.changes === 'number' ? out.changes : +out.changes.toString(), records: [] };
|
|
93
98
|
}
|
|
94
99
|
} catch (error) {
|
|
95
100
|
const code = error && typeof error === 'object' && 'code' in error ? error.code : undefined;
|
|
101
|
+
const message = Error.isError(error) ? error.message : undefined;
|
|
96
102
|
switch (code) {
|
|
103
|
+
case 'ERR_SQLITE_ERROR': {
|
|
104
|
+
if (message?.startsWith('UNIQUE')) {
|
|
105
|
+
throw new ExistsError('query', query);
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
97
109
|
case 'SQLITE_CONSTRAINT_PRIMARYKEY':
|
|
98
110
|
case 'SQLITE_CONSTRAINT_UNIQUE':
|
|
99
111
|
case 'SQLITE_CONSTRAINT_INDEX': throw new ExistsError('query', query);
|
|
100
112
|
};
|
|
101
|
-
|
|
102
|
-
if (/index.*?already exists/.test(message)) {
|
|
113
|
+
if (/index.*?already exists/.test(message ?? '')) {
|
|
103
114
|
throw new ExistsError('index', query);
|
|
104
115
|
}
|
|
105
116
|
throw error;
|
|
@@ -107,18 +118,18 @@ export class SqliteConnection extends Connection<Database> {
|
|
|
107
118
|
});
|
|
108
119
|
}
|
|
109
120
|
|
|
110
|
-
async acquire(): Promise<
|
|
121
|
+
async acquire(): Promise<DatabaseSync> {
|
|
111
122
|
return await this.#pool.acquire();
|
|
112
123
|
}
|
|
113
124
|
|
|
114
|
-
async release(db:
|
|
125
|
+
async release(db: DatabaseSync): Promise<void> {
|
|
115
126
|
return this.#pool.release(db);
|
|
116
127
|
}
|
|
117
128
|
|
|
118
129
|
async pragma<T>(query: string): Promise<T> {
|
|
119
130
|
const db = await this.acquire();
|
|
120
131
|
try {
|
|
121
|
-
const result = db.
|
|
132
|
+
const result = this.#withRetries(async () => db.prepare(`PRAGMA ${query}`).get());
|
|
122
133
|
return castTo<T>(result);
|
|
123
134
|
} finally {
|
|
124
135
|
await this.release(db);
|