@travetto/model-mysql 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 +21 -0
- package/README.md +97 -0
- package/index.ts +2 -0
- package/package.json +45 -0
- package/src/connection.ts +82 -0
- package/src/dialect.ts +98 -0
- package/support/service.mysql.ts +15 -0
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-mysql/doc.ts and execute "npx trv doc" to rebuild -->
|
|
3
|
+
# MySQL Model Service
|
|
4
|
+
## MySQL backing for the travetto model module, with real-time modeling support for SQL schemas.
|
|
5
|
+
|
|
6
|
+
**Install: @travetto/model-mysql**
|
|
7
|
+
```bash
|
|
8
|
+
npm install @travetto/model-mysql
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This module provides a [MySQL](https://www.mysql.com/)-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 { MySQLDialect } from '@travetto/model-mysql';
|
|
36
|
+
|
|
37
|
+
export class Init {
|
|
38
|
+
@InjectableFactory({ primary: true })
|
|
39
|
+
static getModelService(ctx: AsyncContext, conf: SQLModelConfig) {
|
|
40
|
+
return new SQLModelService(ctx, conf, new MySQLDialect(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
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@travetto/model-mysql",
|
|
3
|
+
"displayName": "MySQL Model Service",
|
|
4
|
+
"version": "3.0.0-rc.0",
|
|
5
|
+
"description": "MySQL 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
|
+
"support"
|
|
24
|
+
],
|
|
25
|
+
"main": "index.ts",
|
|
26
|
+
"repository": {
|
|
27
|
+
"url": "https://github.com/travetto/travetto.git",
|
|
28
|
+
"directory": "module/model-mysql"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@travetto/config": "^3.0.0-rc.0",
|
|
32
|
+
"@travetto/context": "^3.0.0-rc.0",
|
|
33
|
+
"@travetto/model": "^3.0.0-rc.0",
|
|
34
|
+
"@travetto/model-query": "3.0.0-rc.0",
|
|
35
|
+
"@travetto/model-sql": "^3.0.0-rc.0",
|
|
36
|
+
"mysql": "^2.18.1",
|
|
37
|
+
"@types/mysql": "^2.15.21"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@travetto/app": "^3.0.0-rc.0"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import * as mysql from 'mysql';
|
|
2
|
+
|
|
3
|
+
import { ShutdownManager } from '@travetto/base';
|
|
4
|
+
import { AsyncContext } from '@travetto/context';
|
|
5
|
+
import { ExistsError } from '@travetto/model';
|
|
6
|
+
import { Connection, SQLModelConfig } from '@travetto/model-sql';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Connection support for mysql
|
|
10
|
+
*/
|
|
11
|
+
export class MySQLConnection extends Connection<mysql.PoolConnection> {
|
|
12
|
+
|
|
13
|
+
#pool: mysql.Pool;
|
|
14
|
+
#config: SQLModelConfig;
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
context: AsyncContext,
|
|
18
|
+
config: SQLModelConfig
|
|
19
|
+
) {
|
|
20
|
+
super(context);
|
|
21
|
+
this.#config = config;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async init(): Promise<void> {
|
|
25
|
+
this.#pool = mysql.createPool({
|
|
26
|
+
user: this.#config.user,
|
|
27
|
+
password: this.#config.password,
|
|
28
|
+
database: this.#config.database,
|
|
29
|
+
host: this.#config.host,
|
|
30
|
+
port: this.#config.port,
|
|
31
|
+
timezone: 'utc',
|
|
32
|
+
typeCast: this.typeCast.bind(this),
|
|
33
|
+
...(this.#config.options || {})
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Close mysql
|
|
37
|
+
ShutdownManager.onShutdown(this.constructor.ᚕid, () => new Promise(r => this.#pool.end(r)));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Support some basic type support for JSON data
|
|
42
|
+
*/
|
|
43
|
+
typeCast(field: Parameters<Exclude<mysql.TypeCast, boolean>>[0], next: () => unknown): unknown {
|
|
44
|
+
const res = next();
|
|
45
|
+
if (typeof res === 'string' && (field.type === 'JSON' || field.type === 'BLOB')) {
|
|
46
|
+
if (res.charAt(0) === '{' && res.charAt(res.length - 1) === '}') {
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse(res);
|
|
49
|
+
} catch { }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return res;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async execute<T = unknown>(conn: mysql.Connection, query: string): Promise<{ count: number, records: T[] }> {
|
|
56
|
+
return new Promise<{ count: number, records: T[] }>((res, rej) => {
|
|
57
|
+
console.debug('Executing Query', { query });
|
|
58
|
+
conn.query(query, (err, results, fields) => {
|
|
59
|
+
if (err) {
|
|
60
|
+
console.debug('Failed query', { error: err, query });
|
|
61
|
+
if (err.message.startsWith('ER_DUP_ENTRY')) {
|
|
62
|
+
rej(new ExistsError('query', query));
|
|
63
|
+
} else {
|
|
64
|
+
rej(err);
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
const records: T[] = Array.isArray(results) ? [...results].map(v => ({ ...v })) : [{ ...results }];
|
|
68
|
+
res({ records, count: results.affectedRows });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
acquire(): Promise<mysql.PoolConnection> {
|
|
75
|
+
return new Promise<mysql.PoolConnection>((res, rej) =>
|
|
76
|
+
this.#pool.getConnection((err, conn) => err ? rej(err) : res(conn)));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
release(conn: mysql.PoolConnection): void {
|
|
80
|
+
conn.release();
|
|
81
|
+
}
|
|
82
|
+
}
|
package/src/dialect.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
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
|
+
import { Class } from '@travetto/base';
|
|
6
|
+
import { ModelType } from '@travetto/model';
|
|
7
|
+
import { SQLModelConfig, SQLDialect } from '@travetto/model-sql';
|
|
8
|
+
import { SQLDialect } from '@travetto/model-sql/srcinternal/util';
|
|
9
|
+
|
|
10
|
+
import { MySQLConnection } from './connection';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* MYSQL Dialect for the SQL Model Source
|
|
14
|
+
*/
|
|
15
|
+
@Injectable()
|
|
16
|
+
export class MySQLDialect extends SQLDialect {
|
|
17
|
+
|
|
18
|
+
conn: MySQLConnection;
|
|
19
|
+
tablePostfix = "COLLATE='utf8mb4_unicode_ci' ENGINE=InnoDB";
|
|
20
|
+
|
|
21
|
+
constructor(context: AsyncContext, public config: SQLModelConfig) {
|
|
22
|
+
super(config.namespace);
|
|
23
|
+
this.conn = new MySQLConnection(context, config);
|
|
24
|
+
|
|
25
|
+
// Customer operators
|
|
26
|
+
Object.assign(this.SQL_OPS, {
|
|
27
|
+
$regex: 'REGEXP BINARY',
|
|
28
|
+
$iregex: 'REGEXP'
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Custom types
|
|
32
|
+
Object.assign(this.COLUMN_TYPES, {
|
|
33
|
+
TIMESTAMP: 'DATETIME(3)',
|
|
34
|
+
JSON: 'TEXT'
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Word boundary
|
|
38
|
+
this.regexWordBoundary = '([[:<:]]|[[:>:]])';
|
|
39
|
+
// Field maxlength
|
|
40
|
+
this.idField.minlength = this.idField.maxlength = { n: this.KEY_LEN };
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Set string length limit based on version
|
|
44
|
+
*/
|
|
45
|
+
if (/^5[.][56]/.test(this.config.version)) {
|
|
46
|
+
this.DEFAULT_STRING_LEN = 191; // Mysql limitation with utf8 and keys
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Compute hash
|
|
52
|
+
*/
|
|
53
|
+
hash(value: string): string {
|
|
54
|
+
return `SHA2('${value}', ${this.KEY_LEN * 4})`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Build identifier
|
|
59
|
+
*/
|
|
60
|
+
ident(field: FieldConfig | string): string {
|
|
61
|
+
return `\`${typeof field === 'string' ? field : field.name}\``;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Create table, adding in specific engine options
|
|
66
|
+
*/
|
|
67
|
+
override getCreateTableSQL(stack: VisitStack[]): string {
|
|
68
|
+
return super.getCreateTableSQL(stack).replace(/;$/, ` ${this.tablePostfix};`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Define column modification
|
|
73
|
+
*/
|
|
74
|
+
getModifyColumnSQL(stack: VisitStack[]): string {
|
|
75
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
76
|
+
const field = stack[stack.length - 1] as FieldConfig;
|
|
77
|
+
return `ALTER TABLE ${this.parentTable(stack)} MODIFY COLUMN ${this.getColumnDefinition(field)};`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Add root alias to delete clause
|
|
82
|
+
*/
|
|
83
|
+
override getDeleteSQL(stack: VisitStack[], where?: WhereClause<unknown>): string {
|
|
84
|
+
const sql = super.getDeleteSQL(stack, where);
|
|
85
|
+
return sql.replace(/\bDELETE\b/g, `DELETE ${this.rootAlias}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Suppress foreign key checks
|
|
90
|
+
*/
|
|
91
|
+
override getTruncateAllTablesSQL<T extends ModelType>(cls: Class<T>): string[] {
|
|
92
|
+
return [
|
|
93
|
+
'SET FOREIGN_KEY_CHECKS = 0;',
|
|
94
|
+
...super.getTruncateAllTablesSQL(cls),
|
|
95
|
+
'SET FOREIGN_KEY_CHECKS = 1;'
|
|
96
|
+
];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { EnvUtil } from '@travetto/boot';
|
|
2
|
+
import type { Service } from '@travetto/command/bin/lib/service';
|
|
3
|
+
|
|
4
|
+
const version = EnvUtil.get('TRV_SERVICE_MYSQL', '5.6');
|
|
5
|
+
|
|
6
|
+
export const service: Service = {
|
|
7
|
+
name: 'mysql',
|
|
8
|
+
version,
|
|
9
|
+
image: `mysql:${version}`,
|
|
10
|
+
port: 3306,
|
|
11
|
+
env: {
|
|
12
|
+
MYSQL_ROOT_PASSWORD: 'password',
|
|
13
|
+
MYSQL_DATABASE: 'app'
|
|
14
|
+
},
|
|
15
|
+
};
|