@travetto/model-sqlite 3.0.0-rc.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 ArcSine Technologies
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.
package/README.md ADDED
@@ -0,0 +1,97 @@
1
+ <!-- This file was generated by @travetto/doc and should not be modified directly -->
2
+ <!-- Please modify https://github.com/travetto/travetto/tree/main/module/model-sqlite/doc.ts and execute "npx trv doc" to rebuild -->
3
+ # SQLite Model Service
4
+ ## SQLite backing for the travetto model module, with real-time modeling support for SQL schemas.
5
+
6
+ **Install: @travetto/model-sqlite**
7
+ ```bash
8
+ npm install @travetto/model-sqlite
9
+ ```
10
+
11
+ 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#L38) will also modify the database schema in real time to minimize impact to development.
12
+
13
+ The schema generated will not generally map to existing tables as it is attempting to produce a document store like experience on top of
14
+ 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.
15
+
16
+ Supported features:
17
+
18
+ * [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/service/crud.ts#L11)
19
+ * [Bulk](https://github.com/travetto/travetto/tree/main/module/model/src/service/bulk.ts#L23)
20
+ * [Query Crud](https://github.com/travetto/travetto/tree/main/module/model-query/src/service/crud.ts#L11)
21
+ * [Facet](https://github.com/travetto/travetto/tree/main/module/model-query/src/service/facet.ts#L12)
22
+ * [Query](https://github.com/travetto/travetto/tree/main/module/model-query/src/service/query.ts#L10)
23
+ * [Suggest](https://github.com/travetto/travetto/tree/main/module/model-query/src/service/suggest.ts#L12)
24
+
25
+ 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
26
+ 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.
27
+
28
+
29
+ **Code: Wiring up a custom Model Source**
30
+ ```typescript
31
+ import { AsyncContext } from '@travetto/context';
32
+ import { InjectableFactory } from '@travetto/di';
33
+
34
+ import { SQLModelService, SQLModelConfig } from '@travetto/model-sql';
35
+ import { SqliteDialect } from '@travetto/model-sqlite';
36
+
37
+ export class Init {
38
+ @InjectableFactory({ primary: true })
39
+ static getModelService(ctx: AsyncContext, conf: SQLModelConfig) {
40
+ return new SQLModelService(ctx, conf, new SqliteDialect(ctx, conf));
41
+ }
42
+ }
43
+ ```
44
+
45
+ where the [SQLModelConfig](https://github.com/travetto/travetto/tree/main/module/model-sql/src/config.ts#L7) is defined by:
46
+
47
+
48
+ **Code: Structure of SQLModelConfig**
49
+ ```typescript
50
+ import { Config } from '@travetto/config';
51
+
52
+ /**
53
+ * SQL Model Config
54
+ */
55
+ @Config('model.sql')
56
+ export class SQLModelConfig {
57
+ /**
58
+ * Host to connect to
59
+ */
60
+ host = '127.0.0.1';
61
+ /**
62
+ * Default port
63
+ */
64
+ port = 0;
65
+ /**
66
+ * Username
67
+ */
68
+ user = '';
69
+ /**
70
+ * Password
71
+ */
72
+ password = '';
73
+ /**
74
+ * Table prefix
75
+ */
76
+ namespace = '';
77
+ /**
78
+ * Database name
79
+ */
80
+ database = 'app';
81
+ /**
82
+ * Auto schema creation
83
+ */
84
+ autoCreate?: boolean;
85
+ /**
86
+ * Db version
87
+ */
88
+ version = '';
89
+ /**
90
+ * Raw client options
91
+ */
92
+ options = {};
93
+ }
94
+ ```
95
+
96
+ Additionally, you can see that the class is registered with the [@Config](https://github.com/travetto/travetto/tree/main/module/config/src/decorator.ts#L9) annotation, and so these values can be overridden using the
97
+ standard [Configuration](https://github.com/travetto/travetto/tree/main/module/config#readme "Environment-aware config management using yaml files")resolution paths.
package/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './src/dialect';
2
+ export * from './src/connection';
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@travetto/model-sqlite",
3
+ "displayName": "SQLite Model Service",
4
+ "version": "3.0.0-rc.0",
5
+ "description": "SQLite backing for the travetto model module, with real-time modeling support for SQL schemas.",
6
+ "keywords": [
7
+ "sql",
8
+ "data-modeling",
9
+ "real-time",
10
+ "model",
11
+ "travetto",
12
+ "typescript"
13
+ ],
14
+ "homepage": "https://travetto.io",
15
+ "license": "MIT",
16
+ "author": {
17
+ "email": "travetto.framework@gmail.com",
18
+ "name": "Travetto Framework"
19
+ },
20
+ "files": [
21
+ "index.ts",
22
+ "src"
23
+ ],
24
+ "main": "index.ts",
25
+ "repository": {
26
+ "url": "https://github.com/travetto/travetto.git",
27
+ "directory": "module/model-sqlite"
28
+ },
29
+ "dependencies": {
30
+ "@travetto/config": "^3.0.0-rc.0",
31
+ "@travetto/context": "^3.0.0-rc.0",
32
+ "@travetto/model": "^3.0.0-rc.0",
33
+ "@travetto/model-query": "3.0.0-rc.0",
34
+ "@travetto/model-sql": "3.0.0-rc.0",
35
+ "better-sqlite3": "^7.6.2",
36
+ "@types/better-sqlite3": "^7.6.0"
37
+ },
38
+ "devDependencies": {
39
+ "@travetto/app": "^3.0.0-rc.0"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ }
44
+ }
@@ -0,0 +1,95 @@
1
+ import * as sqlite3 from 'better-sqlite3';
2
+ import Db = require('better-sqlite3');
3
+ import * as pool from 'generic-pool';
4
+
5
+ import { ShutdownManager, Util } from '@travetto/base';
6
+ import { AsyncContext, WithAsyncContext } from '@travetto/context';
7
+ import { ExistsError } from '@travetto/model';
8
+ import { AppCache } from '@travetto/boot';
9
+ import { SQLModelConfig, Connection } from '@travetto/model-sql';
10
+
11
+ /**
12
+ * Connection support for Sqlite
13
+ */
14
+ export class SqliteConnection extends Connection<sqlite3.Database> {
15
+
16
+ isolatedTransactions = false;
17
+
18
+ #config: SQLModelConfig;
19
+ #pool: pool.Pool<sqlite3.Database>;
20
+
21
+ constructor(
22
+ context: AsyncContext,
23
+ config: SQLModelConfig
24
+ ) {
25
+ super(context);
26
+ this.#config = config;
27
+ }
28
+
29
+ async #withRetries<T>(op: () => Promise<T>, retries = 10, delay = 250): Promise<T> {
30
+ for (; ;) {
31
+ try {
32
+ return await op();
33
+ } catch (err) {
34
+ if (err instanceof Error && retries > 1 && err.message.includes('database is locked')) {
35
+ console.error('Failed, and waiting', retries);
36
+ await Util.wait(delay);
37
+ retries -= 1;
38
+ } else {
39
+ throw err;
40
+ }
41
+ }
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Initializes connection and establishes crypto extension for use with hashing
47
+ */
48
+ @WithAsyncContext()
49
+ override async init(): Promise<void> {
50
+ this.#pool = pool.createPool({
51
+ create: () => this.#withRetries(async () => {
52
+ const db = Db(AppCache.toEntryName('sqlite_db'),
53
+ this.#config.options
54
+ );
55
+ await db.pragma('foreign_keys = ON');
56
+ await db.pragma('journal_mode = WAL');
57
+ db.function('regexp', (a, b) => new RegExp(a).test(b) ? 1 : 0);
58
+ return db;
59
+ }),
60
+ destroy: async db => { db.close(); }
61
+ }, { max: 1 });
62
+
63
+ // Close postgres
64
+ ShutdownManager.onShutdown(this.constructor.ᚕid, () => this.#pool.clear());
65
+ }
66
+
67
+ async execute<T = unknown>(conn: sqlite3.Database, query: string): Promise<{ count: number, records: T[] }> {
68
+ return this.#withRetries(async () => {
69
+ console.debug('Executing query', { query });
70
+ try {
71
+ const out = await conn.prepare(query)[query.trim().startsWith('SELECT') ? 'all' : 'run']();
72
+ if (Array.isArray(out)) {
73
+ const records: T[] = [...out].map(v => ({ ...v }));
74
+ return { count: out.length, records };
75
+ } else {
76
+ return { count: out.changes, records: [] };
77
+ }
78
+ } catch (err) {
79
+ if (err instanceof Error && err.message.includes('UNIQUE constraint failed')) {
80
+ throw new ExistsError('query', query);
81
+ } else {
82
+ throw err;
83
+ }
84
+ }
85
+ });
86
+ }
87
+
88
+ async acquire(): Promise<Db.Database> {
89
+ return this.#pool.acquire();
90
+ }
91
+
92
+ async release(db: sqlite3.Database): Promise<void> {
93
+ return this.#pool.release(db);
94
+ }
95
+ }
package/src/dialect.ts ADDED
@@ -0,0 +1,79 @@
1
+ import { FieldConfig } from '@travetto/schema';
2
+ import { Injectable } from '@travetto/di';
3
+ import { AsyncContext } from '@travetto/context';
4
+ import { WhereClause } from '@travetto/model-query';
5
+
6
+ import { SQLModelConfig, SQLDialect } from '@travetto/model-sql';
7
+ import { VisitStack } from '@travetto/model-sql/src/internal/util';
8
+
9
+ import { SqliteConnection } from './connection';
10
+
11
+ /**
12
+ * Sqlite Dialect for the SQL Model Source
13
+ */
14
+ @Injectable()
15
+ export class SqliteDialect extends SQLDialect {
16
+
17
+ conn: SqliteConnection;
18
+
19
+ constructor(context: AsyncContext, public config: SQLModelConfig) {
20
+ super(config.namespace);
21
+ this.conn = new SqliteConnection(context, config);
22
+
23
+ // Special operators
24
+ Object.assign(this.SQL_OPS, {
25
+ $regex: 'REGEXP',
26
+ $ilike: undefined
27
+ });
28
+
29
+ // Special types
30
+ Object.assign(this.COLUMN_TYPES, {
31
+ JSON: 'TEXT',
32
+ TIMESTAMP: 'INTEGER'
33
+ });
34
+ }
35
+
36
+ override resolveDateValue(value: Date): string {
37
+ return `${value.getTime()}`;
38
+ }
39
+
40
+ /**
41
+ * How to hash
42
+ */
43
+ hash(value: string): string {
44
+ return `hex('${value}')`;
45
+ }
46
+
47
+ /**
48
+ * Build identifier
49
+ */
50
+ ident(field: FieldConfig | string): string {
51
+ return `\`${typeof field === 'string' ? field : field.name}\``;
52
+ }
53
+
54
+ /**
55
+ * Define column modification
56
+ */
57
+ getModifyColumnSQL(stack: VisitStack[]): string {
58
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
59
+ const field = stack[stack.length - 1] as FieldConfig;
60
+ const type = this.getColumnType(field);
61
+ const ident = this.ident(field.name);
62
+ return `ALTER TABLE ${this.parentTable(stack)} ALTER COLUMN ${ident} TYPE ${type} USING (${ident}::${type});`;
63
+ }
64
+
65
+ /**
66
+ * Generate truncate SQL
67
+ */
68
+ override getTruncateTableSQL(stack: VisitStack[]): string {
69
+ return `DELETE FROM ${this.table(stack)};`;
70
+ }
71
+
72
+ override getDeleteSQL(stack: VisitStack[], where?: WhereClause<unknown>): string {
73
+ return super.getDeleteSQL(stack, where).replace(/_ROOT[.]?/g, '');
74
+ }
75
+
76
+ override getUpdateSQL(stack: VisitStack[], data: Record<string, unknown>, where?: WhereClause<unknown>): string {
77
+ return super.getUpdateSQL(stack, data, where).replace(/_ROOT[.]?/g, '');
78
+ }
79
+ }