@travetto/model-sqlite 8.0.0-alpha.24 → 8.0.0-alpha.25
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 +7 -8
- package/__index__.ts +1 -1
- package/package.json +6 -6
- package/src/connection.ts +23 -26
- package/src/dialect.ts +9 -10
package/README.md
CHANGED
|
@@ -13,18 +13,18 @@ npm install @travetto/model-sqlite
|
|
|
13
13
|
yarn add @travetto/model-sqlite
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
This module provides a [SQLite](https://www.sqlite.org/)-based implementation for the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") module.
|
|
16
|
+
This module provides a [SQLite](https://www.sqlite.org/)-based implementation for the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") module. This source allows the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") module to read, write and query against [SQL](https://en.wikipedia.org/wiki/SQL) databases. In development mode, the [SQLModelService](https://github.com/travetto/travetto/tree/main/module/model-sql/src/service.ts#L69) will also modify the database schema in real time to minimize impact to development.
|
|
17
17
|
|
|
18
|
-
The schema generated will not generally map to existing tables as it is attempting to produce a document store like experience on top of a [SQL](https://en.wikipedia.org/wiki/SQL) database.
|
|
18
|
+
The schema generated will not generally map to existing tables as it is attempting to produce a document store like experience on top of a [SQL](https://en.wikipedia.org/wiki/SQL) database. Every table generated will have a `path_id` which determines it's location in the document hierarchy as well as sub tables will have a `parent_path_id` to associate records with the parent values.
|
|
19
19
|
|
|
20
20
|
Supported features:
|
|
21
|
-
* [
|
|
22
|
-
* [
|
|
23
|
-
* [Indexed](https://github.com/travetto/travetto/tree/main/module/model-indexed/src/types/service.ts#
|
|
21
|
+
* [Bulk](https://github.com/travetto/travetto/tree/main/module/model/src/types/bulk.ts#L60)
|
|
22
|
+
* [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/types/crud.ts#L10)
|
|
23
|
+
* [Indexed](https://github.com/travetto/travetto/tree/main/module/model-indexed/src/types/service.ts#L21)
|
|
24
24
|
* [Query Crud](https://github.com/travetto/travetto/tree/main/module/model-query/src/types/crud.ts#L11)
|
|
25
25
|
* [Facet](https://github.com/travetto/travetto/tree/main/module/model-query/src/types/facet.ts#L14)
|
|
26
|
-
* [Query](https://github.com/travetto/travetto/tree/main/module/model-query/src/types/query.ts#L10)
|
|
27
26
|
* [Suggest](https://github.com/travetto/travetto/tree/main/module/model-query/src/types/suggest.ts#L12)
|
|
27
|
+
* [Query](https://github.com/travetto/travetto/tree/main/module/model-query/src/types/query.ts#L10)
|
|
28
28
|
|
|
29
29
|
Out of the box, by installing the module, everything should be wired up by default.If you need to customize any aspect of the source or config, you can override and register it with the [Dependency Injection](https://github.com/travetto/travetto/tree/main/module/di#readme "Dependency registration/management and injection support.") module.
|
|
30
30
|
|
|
@@ -32,8 +32,7 @@ Out of the box, by installing the module, everything should be wired up by defau
|
|
|
32
32
|
```typescript
|
|
33
33
|
import type { AsyncContext } from '@travetto/context';
|
|
34
34
|
import { InjectableFactory } from '@travetto/di';
|
|
35
|
-
|
|
36
|
-
import { SQLModelService, type SQLModelConfig } from '@travetto/model-sql';
|
|
35
|
+
import { type SQLModelConfig, SQLModelService } from '@travetto/model-sql';
|
|
37
36
|
import { SqliteDialect } from '@travetto/model-sqlite';
|
|
38
37
|
|
|
39
38
|
export class Init {
|
package/__index__.ts
CHANGED
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.25",
|
|
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,11 +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.
|
|
30
|
+
"@travetto/config": "^8.0.0-alpha.22",
|
|
31
|
+
"@travetto/context": "^8.0.0-alpha.20",
|
|
32
|
+
"@travetto/model": "^8.0.0-alpha.23",
|
|
33
|
+
"@travetto/model-query": "^8.0.0-alpha.24",
|
|
34
|
+
"@travetto/model-sql": "^8.0.0-alpha.25"
|
|
35
35
|
},
|
|
36
36
|
"travetto": {
|
|
37
37
|
"displayName": "SQLite Model Service"
|
package/src/connection.ts
CHANGED
|
@@ -1,33 +1,28 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { DatabaseSync, type DatabaseSyncOptions, type SQLInputValue
|
|
3
|
+
import { DatabaseSync, type DatabaseSyncOptions, type SQLInputValue } from 'node:sqlite';
|
|
4
4
|
|
|
5
|
-
import { type Pool
|
|
5
|
+
import { createPool, type Pool } from 'generic-pool';
|
|
6
6
|
|
|
7
|
-
import { ShutdownManager, Util, Runtime, RuntimeError, castTo } from '@travetto/runtime';
|
|
8
7
|
import { type AsyncContext, WithAsyncContext } from '@travetto/context';
|
|
9
8
|
import { ExistsError } from '@travetto/model';
|
|
10
|
-
import { type SQLModelConfig
|
|
9
|
+
import { Connection, type SQLModelConfig } from '@travetto/model-sql';
|
|
10
|
+
import { castTo, Runtime, RuntimeError, ShutdownManager, Util } from '@travetto/runtime';
|
|
11
11
|
|
|
12
12
|
const RECOVERABLE_MESSAGE = /database( table| schema)? is (locked|busy)/;
|
|
13
13
|
|
|
14
|
-
const isRecoverableError = (error: unknown): error is Error =>
|
|
15
|
-
error instanceof Error && RECOVERABLE_MESSAGE.test(error.message);
|
|
14
|
+
const isRecoverableError = (error: unknown): error is Error => error instanceof Error && RECOVERABLE_MESSAGE.test(error.message);
|
|
16
15
|
|
|
17
16
|
/**
|
|
18
17
|
* Connection support for Sqlite
|
|
19
18
|
*/
|
|
20
19
|
export class SqliteConnection extends Connection<DatabaseSync> {
|
|
21
|
-
|
|
22
20
|
isolatedTransactions = false;
|
|
23
21
|
|
|
24
22
|
#config: SQLModelConfig<DatabaseSyncOptions & { file?: string }>;
|
|
25
23
|
#pool: Pool<DatabaseSync>;
|
|
26
24
|
|
|
27
|
-
constructor(
|
|
28
|
-
context: AsyncContext,
|
|
29
|
-
config: SQLModelConfig<DatabaseSyncOptions & { file?: string }>
|
|
30
|
-
) {
|
|
25
|
+
constructor(context: AsyncContext, config: SQLModelConfig<DatabaseSyncOptions & { file?: string }>) {
|
|
31
26
|
super(context);
|
|
32
27
|
this.#config = config;
|
|
33
28
|
}
|
|
@@ -51,14 +46,10 @@ export class SqliteConnection extends Connection<DatabaseSync> {
|
|
|
51
46
|
const file = path.resolve(this.#config.options.file ?? Runtime.toolPath('@', 'sqlite_db'));
|
|
52
47
|
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
53
48
|
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
|
-
]) {
|
|
49
|
+
for (const q of ['PRAGMA foreign_keys = ON', 'PRAGMA journal_mode = WAL', 'PRAGMA synchronous = NORMAL']) {
|
|
59
50
|
await this.#withRetries(async () => db.exec(q));
|
|
60
51
|
}
|
|
61
|
-
db.function('regexp', (a, b) => new RegExp(`${a}`).test(`${b}`) ? 1 : 0);
|
|
52
|
+
db.function('regexp', (a, b) => (new RegExp(`${a}`).test(`${b}`) ? 1 : 0));
|
|
62
53
|
return db;
|
|
63
54
|
}
|
|
64
55
|
|
|
@@ -71,16 +62,21 @@ export class SqliteConnection extends Connection<DatabaseSync> {
|
|
|
71
62
|
|
|
72
63
|
await this.#create();
|
|
73
64
|
|
|
74
|
-
this.#pool = createPool<DatabaseSync>(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
65
|
+
this.#pool = createPool<DatabaseSync>(
|
|
66
|
+
{
|
|
67
|
+
create: () => this.#withRetries(() => this.#create()),
|
|
68
|
+
destroy: async db => {
|
|
69
|
+
db.close();
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{ max: 1 }
|
|
73
|
+
);
|
|
78
74
|
|
|
79
75
|
// Close postgres
|
|
80
76
|
ShutdownManager.signal.addEventListener('abort', () => this.#pool.clear());
|
|
81
77
|
}
|
|
82
78
|
|
|
83
|
-
async execute<T = unknown>(connection: DatabaseSync, query: string, values?: unknown[]): Promise<{ count: number
|
|
79
|
+
async execute<T = unknown>(connection: DatabaseSync, query: string, values?: unknown[]): Promise<{ count: number; records: T[] }> {
|
|
84
80
|
const isSelect = query.trim().startsWith('SELECT');
|
|
85
81
|
return this.#withRetries(async () => {
|
|
86
82
|
console.debug('Executing query', { query });
|
|
@@ -98,7 +94,7 @@ export class SqliteConnection extends Connection<DatabaseSync> {
|
|
|
98
94
|
}
|
|
99
95
|
} catch (error) {
|
|
100
96
|
const code = error && typeof error === 'object' && 'code' in error ? error.code : undefined;
|
|
101
|
-
const message =
|
|
97
|
+
const message = Error.isError(error) || error instanceof Error ? error.message : undefined;
|
|
102
98
|
switch (code) {
|
|
103
99
|
case 'ERR_SQLITE_ERROR': {
|
|
104
100
|
if (message?.startsWith('UNIQUE')) {
|
|
@@ -108,8 +104,9 @@ export class SqliteConnection extends Connection<DatabaseSync> {
|
|
|
108
104
|
}
|
|
109
105
|
case 'SQLITE_CONSTRAINT_PRIMARYKEY':
|
|
110
106
|
case 'SQLITE_CONSTRAINT_UNIQUE':
|
|
111
|
-
case 'SQLITE_CONSTRAINT_INDEX':
|
|
112
|
-
|
|
107
|
+
case 'SQLITE_CONSTRAINT_INDEX':
|
|
108
|
+
throw new ExistsError('query', query);
|
|
109
|
+
}
|
|
113
110
|
if (/index.*?already exists/.test(message ?? '')) {
|
|
114
111
|
throw new ExistsError('index', query);
|
|
115
112
|
}
|
|
@@ -135,4 +132,4 @@ export class SqliteConnection extends Connection<DatabaseSync> {
|
|
|
135
132
|
await this.release(db);
|
|
136
133
|
}
|
|
137
134
|
}
|
|
138
|
-
}
|
|
135
|
+
}
|
package/src/dialect.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import type { SchemaFieldConfig } from '@travetto/schema';
|
|
2
|
-
import { Injectable } from '@travetto/di';
|
|
3
1
|
import type { AsyncContext } from '@travetto/context';
|
|
2
|
+
import { Injectable } from '@travetto/di';
|
|
4
3
|
import type { WhereClause } from '@travetto/model-query';
|
|
4
|
+
import { SQLDialect, type SQLModelConfig, type SQLTableDescription, type VisitStack } from '@travetto/model-sql';
|
|
5
5
|
import { castTo } from '@travetto/runtime';
|
|
6
|
-
|
|
7
|
-
import { type SQLModelConfig, SQLDialect, type VisitStack, type SQLTableDescription } from '@travetto/model-sql';
|
|
6
|
+
import type { SchemaFieldConfig } from '@travetto/schema';
|
|
8
7
|
|
|
9
8
|
import { SqliteConnection } from './connection.ts';
|
|
10
9
|
|
|
@@ -13,7 +12,6 @@ import { SqliteConnection } from './connection.ts';
|
|
|
13
12
|
*/
|
|
14
13
|
@Injectable()
|
|
15
14
|
export class SqliteDialect extends SQLDialect {
|
|
16
|
-
|
|
17
15
|
connection: SqliteConnection;
|
|
18
16
|
config: SQLModelConfig;
|
|
19
17
|
|
|
@@ -50,7 +48,7 @@ export class SqliteDialect extends SQLDialect {
|
|
|
50
48
|
const IGNORE_FIELDS = [this.pathField.name, this.parentPathField.name, this.idxField.name].map(field => `'${field}'`);
|
|
51
49
|
|
|
52
50
|
const [columns, foreignKeys, indices] = await Promise.all([
|
|
53
|
-
this.executeSQL<{ name: string
|
|
51
|
+
this.executeSQL<{ name: string; type: string; is_not_null: 1 | 0 }>(`
|
|
54
52
|
SELECT
|
|
55
53
|
name,
|
|
56
54
|
type,
|
|
@@ -58,7 +56,7 @@ export class SqliteDialect extends SQLDialect {
|
|
|
58
56
|
FROM PRAGMA_TABLE_INFO('${table}')
|
|
59
57
|
WHERE name NOT IN (${IGNORE_FIELDS.join(',')})
|
|
60
58
|
`),
|
|
61
|
-
this.executeSQL<{ name: string
|
|
59
|
+
this.executeSQL<{ name: string; to_table: string; from_column: string; to_column: string }>(`
|
|
62
60
|
SELECT
|
|
63
61
|
'fk_' || '${table}' || '_' || ${this.identifier('from')} AS name,
|
|
64
62
|
${this.identifier('from')} as from_column,
|
|
@@ -66,7 +64,7 @@ export class SqliteDialect extends SQLDialect {
|
|
|
66
64
|
${this.identifier('table')} as to_table
|
|
67
65
|
FROM PRAGMA_FOREIGN_KEY_LIST('${table}')
|
|
68
66
|
`),
|
|
69
|
-
this.executeSQL<{ name: string
|
|
67
|
+
this.executeSQL<{ name: string; is_unique: boolean; columns: string }>(`
|
|
70
68
|
SELECT
|
|
71
69
|
il.name as name,
|
|
72
70
|
il.${this.identifier('unique')} = 1 as is_unique,
|
|
@@ -87,7 +85,8 @@ export class SqliteDialect extends SQLDialect {
|
|
|
87
85
|
indices: indices.records.map(idx => ({
|
|
88
86
|
name: idx.name,
|
|
89
87
|
is_unique: idx.is_unique,
|
|
90
|
-
columns: idx.columns
|
|
88
|
+
columns: idx.columns
|
|
89
|
+
.split(',')
|
|
91
90
|
.map(col => col.split(' '))
|
|
92
91
|
.map(([order, name, desc]) => [+order, { name, desc: desc === '1' }] as const)
|
|
93
92
|
.sort((a, b) => a[0] - b[0])
|
|
@@ -120,4 +119,4 @@ export class SqliteDialect extends SQLDialect {
|
|
|
120
119
|
override getUpdateSQL(stack: VisitStack[], data: Record<string, unknown>, where?: WhereClause<unknown>): string {
|
|
121
120
|
return super.getUpdateSQL(stack, data, where).replace(/_ROOT[.]?/g, '');
|
|
122
121
|
}
|
|
123
|
-
}
|
|
122
|
+
}
|