@travetto/model-sql 8.0.0-alpha.9 → 8.0.1
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 +29 -9
- package/__index__.ts +4 -6
- package/package.json +19 -18
- package/src/connection.ts +218 -0
- package/src/dialect.ts +847 -0
- package/src/schema.ts +61 -0
- package/src/service.ts +749 -240
- package/src/types.ts +23 -9
- package/support/test/dialect.ts +125 -0
- package/support/test/query.ts +115 -72
- package/src/config.ts +0 -45
- package/src/connection/base.ts +0 -188
- package/src/connection/decorator.ts +0 -54
- package/src/dialect/base.ts +0 -1102
- package/src/internal/types.ts +0 -64
- package/src/table-manager.ts +0 -162
- package/src/util.ts +0 -331
package/README.md
CHANGED
|
@@ -13,18 +13,38 @@ npm install @travetto/model-sql
|
|
|
13
13
|
yarn add @travetto/model-sql
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
The current SQL client
|
|
17
|
-
* [MySQL Model Service](https://github.com/travetto/travetto/tree/main/module/model-mysql#readme "MySQL backing for the travetto model module, with real-time modeling support for SQL schemas.") - MySQL 8.
|
|
16
|
+
This module provides the core SQL foundation for [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") datastores. The current SQL client implementations include:
|
|
17
|
+
* [MySQL Model Service](https://github.com/travetto/travetto/tree/main/module/model-mysql#readme "MySQL backing for the travetto model module, with real-time modeling support for SQL schemas.") - MySQL 8.0+
|
|
18
18
|
* [PostgreSQL Model Service](https://github.com/travetto/travetto/tree/main/module/model-postgres#readme "PostgreSQL backing for the travetto model module, with real-time modeling support for SQL schemas.") - Postgres 14+
|
|
19
|
-
* [SQLite Model Service](https://github.com/travetto/travetto/tree/main/module/model-sqlite#readme "SQLite backing for the travetto model module, with real-time modeling support for SQL schemas.") - (
|
|
20
|
-
* `SQL Server` - Currently unsupported
|
|
21
|
-
* `Oracle` - Currently unsupported
|
|
19
|
+
* [SQLite Model Service](https://github.com/travetto/travetto/tree/main/module/model-sqlite#readme "SQLite backing for the travetto model module, with real-time modeling support for SQL schemas.") - SQLite (Node native `DatabaseSync`)
|
|
22
20
|
|
|
23
21
|
**Note**: Wider client support will roll out as usage increases.
|
|
24
22
|
|
|
25
|
-
## Assumed Behavior
|
|
26
|
-
The [SQL Model Service](https://github.com/travetto/travetto/tree/main/module/model-sql#readme "SQL backing for the travetto model module, with real-time modeling support for SQL schemas.") works quite a bit
|
|
23
|
+
## Assumed Behavior & Schema Design
|
|
24
|
+
The [SQL Model Service](https://github.com/travetto/travetto/tree/main/module/model-sql#readme "SQL backing for the travetto model module, with real-time modeling support for SQL schemas.") works quite a bit differently than the average [Object Relationship Mapping](https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping) in that it makes assertions about how data is stored in the database. The primary goal of the [SQL](https://en.wikipedia.org/wiki/SQL) support is not to handle every legacy relational database scenario, but to integrate with the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") structure while leveraging relational datastores to the best of their abilities.
|
|
27
25
|
|
|
28
|
-
The primary
|
|
26
|
+
The primary design maps each model class to a single table where simple fields (primitives, dates, enums) are mapped to individual columns, and complex fields (objects and arrays) are serialized and stored as native JSON document columns. Every table requires a primary key column (`id`), and indices are compiled directly to standard database columns or SQL functional expressions over JSON paths.
|
|
29
27
|
|
|
30
|
-
|
|
28
|
+
In development mode, storage modifications are applied dynamically in real time to match model definitions, minimizing manual database migrations.
|
|
29
|
+
|
|
30
|
+
## Transactions
|
|
31
|
+
Transaction state is tracked seamlessly using [Async Context](https://github.com/travetto/travetto/tree/main/module/context#readme "Async-aware state management, maintaining context across asynchronous calls."). Methods can be wrapped using the [Transactional](https://github.com/travetto/travetto/tree/main/module/model-sql/src/connection.ts#L18) decorator to run operations within a managed transaction.
|
|
32
|
+
|
|
33
|
+
Supported transaction modes include:
|
|
34
|
+
* `required` - Joins an active transaction if one exists, or starts a new top-level transaction.
|
|
35
|
+
* `isolated` - Begins a SAVEPOINT / nested transaction isolated from the surrounding context.
|
|
36
|
+
* `force` - Always creates a separate SAVEPOINT for nested operations.
|
|
37
|
+
|
|
38
|
+
## Bulk Operations
|
|
39
|
+
Bulk operations (`processBulk`) batch insert, delete, and update statements for high performance. Bulk updates utilize standard ANSI SQL `CASE ... WHEN` constructs to update multiple records in a single database query across all SQL engines.
|
|
40
|
+
|
|
41
|
+
## Supported Features
|
|
42
|
+
All SQL model service implementations derive from [BaseSQLModelService](https://github.com/travetto/travetto/tree/main/module/model-sql/src/service.ts#L68) and support:
|
|
43
|
+
* [Bulk](https://github.com/travetto/travetto/tree/main/module/model/src/types/bulk.ts#L60)
|
|
44
|
+
* [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/types/crud.ts#L10)
|
|
45
|
+
* [Expiry](https://github.com/travetto/travetto/tree/main/module/model/src/types/expiry.ts#L10)
|
|
46
|
+
* [Indexed](https://github.com/travetto/travetto/tree/main/module/model-indexed/src/types/service.ts#L21)
|
|
47
|
+
* [Query Crud](https://github.com/travetto/travetto/tree/main/module/model-query/src/types/crud.ts#L11)
|
|
48
|
+
* [Facet](https://github.com/travetto/travetto/tree/main/module/model-query/src/types/facet.ts#L14)
|
|
49
|
+
* [Suggest](https://github.com/travetto/travetto/tree/main/module/model-query/src/types/suggest.ts#L12)
|
|
50
|
+
* [Query](https://github.com/travetto/travetto/tree/main/module/model-query/src/types/query.ts#L10)
|
package/__index__.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
export * from './src/
|
|
1
|
+
export * from './src/connection.ts';
|
|
2
|
+
export * from './src/dialect.ts';
|
|
3
|
+
export * from './src/schema.ts';
|
|
2
4
|
export * from './src/service.ts';
|
|
3
|
-
export * from './src/
|
|
4
|
-
export * from './src/connection/decorator.ts';
|
|
5
|
-
export * from './src/dialect/base.ts';
|
|
6
|
-
export * from './src/util.ts';
|
|
7
|
-
export * from './src/types.ts';
|
|
5
|
+
export * from './src/types.ts';
|
package/package.json
CHANGED
|
@@ -1,41 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-sql",
|
|
3
|
-
"version": "8.0.
|
|
4
|
-
"type": "module",
|
|
3
|
+
"version": "8.0.1",
|
|
5
4
|
"description": "SQL backing for the travetto model module, with real-time modeling support for SQL schemas.",
|
|
6
5
|
"keywords": [
|
|
7
|
-
"sql",
|
|
8
6
|
"data-modeling",
|
|
9
|
-
"real-time",
|
|
10
7
|
"model",
|
|
8
|
+
"real-time",
|
|
9
|
+
"sql",
|
|
11
10
|
"travetto",
|
|
12
11
|
"typescript"
|
|
13
12
|
],
|
|
14
13
|
"homepage": "https://travetto.io",
|
|
15
14
|
"license": "MIT",
|
|
16
15
|
"author": {
|
|
17
|
-
"
|
|
18
|
-
"
|
|
16
|
+
"name": "Travetto Framework",
|
|
17
|
+
"email": "travetto.framework@gmail.com"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"url": "git+https://github.com/travetto/travetto.git",
|
|
21
|
+
"directory": "module/model-sql"
|
|
19
22
|
},
|
|
20
23
|
"files": [
|
|
21
24
|
"__index__.ts",
|
|
22
25
|
"src",
|
|
23
26
|
"support"
|
|
24
27
|
],
|
|
28
|
+
"type": "module",
|
|
25
29
|
"main": "__index__.ts",
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"directory": "module/model-sql"
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
29
32
|
},
|
|
30
33
|
"dependencies": {
|
|
31
|
-
"@travetto/config": "^8.0.
|
|
32
|
-
"@travetto/context": "^8.0.
|
|
33
|
-
"@travetto/model": "^8.0.
|
|
34
|
-
"@travetto/model-
|
|
34
|
+
"@travetto/config": "^8.0.1",
|
|
35
|
+
"@travetto/context": "^8.0.1",
|
|
36
|
+
"@travetto/model": "^8.0.1",
|
|
37
|
+
"@travetto/model-indexed": "^8.0.1",
|
|
38
|
+
"@travetto/model-query": "^8.0.1"
|
|
35
39
|
},
|
|
36
40
|
"peerDependencies": {
|
|
37
|
-
"@travetto/cli": "^8.0.
|
|
38
|
-
"@travetto/test": "^8.0.
|
|
41
|
+
"@travetto/cli": "^8.0.1",
|
|
42
|
+
"@travetto/test": "^8.0.1"
|
|
39
43
|
},
|
|
40
44
|
"peerDependenciesMeta": {
|
|
41
45
|
"@travetto/cli": {
|
|
@@ -47,8 +51,5 @@
|
|
|
47
51
|
},
|
|
48
52
|
"travetto": {
|
|
49
53
|
"displayName": "SQL Model Service"
|
|
50
|
-
},
|
|
51
|
-
"publishConfig": {
|
|
52
|
-
"access": "public"
|
|
53
54
|
}
|
|
54
55
|
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { type AsyncContext, AsyncContextValue } from '@travetto/context';
|
|
2
|
+
import { ModelRegistryIndex, type ModelType } from '@travetto/model';
|
|
3
|
+
import { type AsyncMethodDescriptor, type Class, castTo, Util } from '@travetto/runtime';
|
|
4
|
+
|
|
5
|
+
import type { AbstractANSI99Dialect } from './dialect.ts';
|
|
6
|
+
import { SQLModelSchemaUtil } from './schema.ts';
|
|
7
|
+
import type { TableContext } from './types.ts';
|
|
8
|
+
|
|
9
|
+
export type TransactionType = 'required' | 'isolated' | 'force';
|
|
10
|
+
|
|
11
|
+
export interface ConnectionAware {
|
|
12
|
+
connection: SQLConnection;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Decorator to ensure a method runs inside a database transaction
|
|
17
|
+
*/
|
|
18
|
+
export function Transactional(mode: TransactionType = 'required') {
|
|
19
|
+
return function <Target extends ConnectionAware>(target: Target, property: string, descriptor: AsyncMethodDescriptor<Target>): void {
|
|
20
|
+
const originalMethod = descriptor.value!;
|
|
21
|
+
descriptor.value = function (...args: unknown[]): ReturnType<typeof originalMethod> {
|
|
22
|
+
return this.connection.runWithTransaction(mode, () => originalMethod.call(this, ...args));
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Base abstract connection manager for SQL Model services.
|
|
29
|
+
* Uses @travetto/context to track active client connections and transaction state.
|
|
30
|
+
*/
|
|
31
|
+
export abstract class SQLConnection<
|
|
32
|
+
C = unknown,
|
|
33
|
+
F extends { namespace?: string; database?: string } = { namespace?: string; database?: string }
|
|
34
|
+
> {
|
|
35
|
+
isolatedTransactions = true;
|
|
36
|
+
nestedTransactions = true;
|
|
37
|
+
|
|
38
|
+
abstract readonly dialect: AbstractANSI99Dialect;
|
|
39
|
+
|
|
40
|
+
readonly context: AsyncContext;
|
|
41
|
+
|
|
42
|
+
#activeConnection = new AsyncContextValue<C>(this, { failIfUnbound: { read: false } });
|
|
43
|
+
#activeTransaction = new AsyncContextValue<boolean>(this, { failIfUnbound: { read: false } });
|
|
44
|
+
|
|
45
|
+
constructor(context: AsyncContext) {
|
|
46
|
+
this.context = context;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
abstract config: F;
|
|
50
|
+
|
|
51
|
+
getContext<T extends ModelType>(modelClass: Class<T>): TableContext<T> {
|
|
52
|
+
let tableName = ModelRegistryIndex.getStoreName(modelClass);
|
|
53
|
+
if (this.config.namespace) {
|
|
54
|
+
tableName = `${this.config.namespace}_${tableName}`;
|
|
55
|
+
}
|
|
56
|
+
return { tableName, ...SQLModelSchemaUtil.getSchemaContext(modelClass) };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get active(): C | undefined {
|
|
60
|
+
return this.#activeConnection.get();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get activeTransaction(): boolean {
|
|
64
|
+
return !!this.#activeTransaction.get();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Initializes the connection
|
|
69
|
+
*/
|
|
70
|
+
abstract init(): Promise<void> | void;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Acquires a client connection
|
|
74
|
+
*/
|
|
75
|
+
abstract acquire(): Promise<C>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Releases a client connection
|
|
79
|
+
*/
|
|
80
|
+
abstract release(connection: C): void;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Executes a SQL query
|
|
84
|
+
*/
|
|
85
|
+
abstract execute<Type = unknown>(query: string, values?: unknown[]): Promise<{ count: number; records: Type[] }>;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Runs an operation with an active connection (allocating one if not already active)
|
|
89
|
+
*/
|
|
90
|
+
async runWithConnection<Result>(operation: () => Promise<Result>): Promise<Result> {
|
|
91
|
+
if (this.active) {
|
|
92
|
+
return await operation();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return this.context.run(async () => {
|
|
96
|
+
let connection: C | undefined;
|
|
97
|
+
try {
|
|
98
|
+
connection = await this.acquire();
|
|
99
|
+
this.#activeConnection.set(connection);
|
|
100
|
+
return await operation();
|
|
101
|
+
} finally {
|
|
102
|
+
if (connection) {
|
|
103
|
+
this.release(connection);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Iterates with an active connection
|
|
111
|
+
*/
|
|
112
|
+
async *iterateWithConnection<Result>(operation: () => AsyncIterable<Result>): AsyncIterable<Result> {
|
|
113
|
+
if (this.active) {
|
|
114
|
+
yield* operation();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const self = castTo<SQLConnection<C>>(this);
|
|
119
|
+
yield* this.context.iterate(async function* () {
|
|
120
|
+
let connection: C | undefined;
|
|
121
|
+
try {
|
|
122
|
+
connection = await self.acquire();
|
|
123
|
+
self.#activeConnection.set(connection);
|
|
124
|
+
yield* operation();
|
|
125
|
+
} finally {
|
|
126
|
+
if (connection) {
|
|
127
|
+
self.release(connection);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Runs an operation within a database transaction context
|
|
135
|
+
*/
|
|
136
|
+
async runWithTransaction<Result>(mode: TransactionType, operation: () => Promise<Result>): Promise<Result> {
|
|
137
|
+
if (this.activeTransaction) {
|
|
138
|
+
if (mode === 'isolated' || mode === 'force') {
|
|
139
|
+
const transactionId = mode === 'isolated' ? `tx${Util.uuid()}` : undefined;
|
|
140
|
+
try {
|
|
141
|
+
await this.startTransaction(transactionId);
|
|
142
|
+
const result = await operation();
|
|
143
|
+
await this.commitTransaction(transactionId);
|
|
144
|
+
return result;
|
|
145
|
+
} catch (error) {
|
|
146
|
+
try {
|
|
147
|
+
await this.rollbackTransaction(transactionId);
|
|
148
|
+
} catch {}
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
return await operation();
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
return this.runWithConnection(async () => {
|
|
156
|
+
this.#activeTransaction.set(true);
|
|
157
|
+
if (this.isolatedTransactions) {
|
|
158
|
+
await this.execute(this.dialect.transactionStatements.isolate);
|
|
159
|
+
}
|
|
160
|
+
await this.execute(this.dialect.transactionStatements.begin);
|
|
161
|
+
try {
|
|
162
|
+
const result = await operation();
|
|
163
|
+
await this.execute(this.dialect.transactionStatements.commit);
|
|
164
|
+
return result;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
try {
|
|
167
|
+
await this.execute(this.dialect.transactionStatements.rollback);
|
|
168
|
+
} catch {}
|
|
169
|
+
throw error;
|
|
170
|
+
} finally {
|
|
171
|
+
this.#activeTransaction.set(false);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Starts a transaction or SAVEPOINT
|
|
179
|
+
*/
|
|
180
|
+
async startTransaction(transactionId?: string): Promise<void> {
|
|
181
|
+
if (transactionId) {
|
|
182
|
+
if (this.nestedTransactions) {
|
|
183
|
+
await this.execute(this.dialect.transactionStatements.beginNested, [transactionId]);
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
if (this.isolatedTransactions) {
|
|
187
|
+
await this.execute(this.dialect.transactionStatements.isolate);
|
|
188
|
+
}
|
|
189
|
+
await this.execute(this.dialect.transactionStatements.begin);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Commits the transaction or release SAVEPOINT
|
|
195
|
+
*/
|
|
196
|
+
async commitTransaction(transactionId?: string): Promise<void> {
|
|
197
|
+
if (transactionId) {
|
|
198
|
+
if (this.nestedTransactions) {
|
|
199
|
+
await this.execute(this.dialect.transactionStatements.commitNested, [transactionId]);
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
await this.execute(this.dialect.transactionStatements.commit);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Rolls back the transaction or SAVEPOINT
|
|
208
|
+
*/
|
|
209
|
+
async rollbackTransaction(transactionId?: string): Promise<void> {
|
|
210
|
+
if (transactionId) {
|
|
211
|
+
if (this.nestedTransactions) {
|
|
212
|
+
await this.execute(this.dialect.transactionStatements.rollbackNested, [transactionId]);
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
await this.execute(this.dialect.transactionStatements.rollback);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|