@proteinjs/db 1.0.14 → 1.0.16
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/.eslintrc.js +20 -0
- package/.prettierignore +3 -0
- package/.prettierrc +8 -0
- package/CHANGELOG.md +14 -24
- package/LICENSE +21 -0
- package/dist/generated/index.js +1 -1
- package/dist/generated/index.js.map +1 -1
- package/dist/src/Columns.d.ts.map +1 -1
- package/dist/src/Columns.js +34 -17
- package/dist/src/Columns.js.map +1 -1
- package/dist/src/Db.d.ts.map +1 -1
- package/dist/src/Db.js +45 -18
- package/dist/src/Db.js.map +1 -1
- package/dist/src/MigrationRunner.d.ts.map +1 -1
- package/dist/src/MigrationRunner.js +15 -7
- package/dist/src/MigrationRunner.js.map +1 -1
- package/dist/src/QueryBuilderFactory.d.ts.map +1 -1
- package/dist/src/QueryBuilderFactory.js +5 -7
- package/dist/src/QueryBuilderFactory.js.map +1 -1
- package/dist/src/Record.d.ts.map +1 -1
- package/dist/src/Record.js +4 -2
- package/dist/src/Record.js.map +1 -1
- package/dist/src/StatementConfigFactory.d.ts.map +1 -1
- package/dist/src/StatementConfigFactory.js +3 -2
- package/dist/src/StatementConfigFactory.js.map +1 -1
- package/dist/src/Table.d.ts.map +1 -1
- package/dist/src/Table.js +6 -3
- package/dist/src/Table.js.map +1 -1
- package/dist/src/auth/TableAuth.d.ts.map +1 -1
- package/dist/src/auth/TableAuth.js +16 -11
- package/dist/src/auth/TableAuth.js.map +1 -1
- package/dist/src/auth/TableServiceAuth.d.ts.map +1 -1
- package/dist/src/auth/TableServiceAuth.js +12 -6
- package/dist/src/auth/TableServiceAuth.js.map +1 -1
- package/dist/src/reference/Reference.d.ts.map +1 -1
- package/dist/src/reference/Reference.js.map +1 -1
- package/dist/src/reference/ReferenceArray.d.ts.map +1 -1
- package/dist/src/reference/ReferenceArray.js.map +1 -1
- package/dist/src/schema/SchemaMetadata.d.ts.map +1 -1
- package/dist/src/schema/SchemaMetadata.js +18 -15
- package/dist/src/schema/SchemaMetadata.js.map +1 -1
- package/dist/src/schema/SchemaOperations.d.ts.map +1 -1
- package/dist/src/schema/TableManager.d.ts.map +1 -1
- package/dist/src/schema/TableManager.js +24 -9
- package/dist/src/schema/TableManager.js.map +1 -1
- package/dist/src/serializers/GraphSerializer.d.ts.map +1 -1
- package/dist/src/serializers/QueryBuilderSerializer.d.ts.map +1 -1
- package/dist/src/serializers/ReferenceArraySerializer.d.ts.map +1 -1
- package/dist/src/serializers/ReferenceArraySerializer.js.map +1 -1
- package/dist/src/serializers/ReferenceSerializer.d.ts.map +1 -1
- package/dist/src/serializers/TableSerializer.d.ts.map +1 -1
- package/dist/src/services/DbService.d.ts.map +1 -1
- package/dist/src/source/SourceRecord.d.ts.map +1 -1
- package/dist/src/source/SourceRecord.js +7 -3
- package/dist/src/source/SourceRecord.js.map +1 -1
- package/dist/src/source/SourceRecordLoader.d.ts.map +1 -1
- package/dist/src/source/SourceRecordLoader.js +11 -4
- package/dist/src/source/SourceRecordLoader.js.map +1 -1
- package/dist/src/source/SourceRecordRepo.d.ts.map +1 -1
- package/dist/src/source/SourceRecordRepo.js.map +1 -1
- package/dist/src/tables/MigrationTable.d.ts.map +1 -1
- package/dist/src/tables/MigrationTable.js.map +1 -1
- package/dist/src/tables/tables.d.ts.map +1 -1
- package/dist/src/tables/tables.js.map +1 -1
- package/dist/test/CrudTests.d.ts +2 -0
- package/dist/test/CrudTests.d.ts.map +1 -1
- package/dist/test/CrudTests.js +19 -4
- package/dist/test/CrudTests.js.map +1 -1
- package/dist/test/TableManagerTests.d.ts.map +1 -1
- package/dist/test/TableManagerTests.js +17 -9
- package/dist/test/TableManagerTests.js.map +1 -1
- package/generated/index.ts +15 -18
- package/index.ts +1 -1
- package/jest.config.js +8 -17
- package/package.json +49 -43
- package/src/Columns.ts +284 -241
- package/src/Db.ts +188 -147
- package/src/MigrationRunner.ts +24 -10
- package/src/QueryBuilderFactory.ts +6 -9
- package/src/Record.ts +35 -29
- package/src/StatementConfigFactory.ts +14 -16
- package/src/Table.ts +91 -88
- package/src/auth/TableAuth.ts +37 -27
- package/src/auth/TableServiceAuth.ts +11 -9
- package/src/opt/moment.ts +1 -1
- package/src/reference/Reference.ts +7 -7
- package/src/reference/ReferenceArray.ts +8 -8
- package/src/schema/SchemaMetadata.ts +84 -64
- package/src/schema/SchemaOperations.ts +2 -2
- package/src/schema/TableManager.ts +178 -168
- package/src/serializers/GraphSerializer.ts +4 -4
- package/src/serializers/QueryBuilderSerializer.ts +11 -11
- package/src/serializers/ReferenceArraySerializer.ts +12 -8
- package/src/serializers/ReferenceSerializer.ts +7 -7
- package/src/serializers/TableSerializer.ts +5 -5
- package/src/services/DbService.ts +3 -3
- package/src/services/MigrationRunnerService.ts +1 -1
- package/src/source/SourceRecord.ts +43 -24
- package/src/source/SourceRecordLoader.ts +29 -18
- package/src/source/SourceRecordRepo.ts +3 -3
- package/src/tables/MigrationTable.ts +5 -5
- package/src/tables/tables.ts +1 -1
- package/test/CrudTests.ts +46 -26
- package/test/TableManagerTests.ts +329 -154
- package/tsconfig.json +18 -22
package/src/Db.ts
CHANGED
|
@@ -12,191 +12,232 @@ import { TableManager } from './schema/TableManager';
|
|
|
12
12
|
import { TableAuth } from './auth/TableAuth';
|
|
13
13
|
import { TableServiceAuth } from './auth/TableServiceAuth';
|
|
14
14
|
|
|
15
|
-
export const getDb = <R extends Record = Record>() =>
|
|
15
|
+
export const getDb = <R extends Record = Record>() =>
|
|
16
|
+
typeof self === 'undefined' ? new Db<R>() : (getDbService() as Db<R>);
|
|
16
17
|
export const getSystemDb = <R extends Record = Record>() => new Db<R>(undefined, undefined, true);
|
|
17
18
|
|
|
18
|
-
export type DbDriverStatementConfig = ParameterizationConfig & { prefixTablesWithDb?: boolean }
|
|
19
|
+
export type DbDriverStatementConfig = ParameterizationConfig & { prefixTablesWithDb?: boolean };
|
|
19
20
|
|
|
20
21
|
export interface DefaultDbDriverFactory extends Loadable {
|
|
21
|
-
|
|
22
|
+
getDbDriver(): DbDriver;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export interface DbDriver {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
getDbName(): string;
|
|
27
|
+
createDbIfNotExists(): Promise<void>;
|
|
28
|
+
start?(): Promise<void>;
|
|
29
|
+
stop?(): Promise<void>;
|
|
30
|
+
getTableManager(): TableManager;
|
|
31
|
+
runQuery(generateStatement: (config: DbDriverStatementConfig) => Statement): Promise<SerializedRecord[]>;
|
|
32
|
+
runDml(generateStatement: (config: DbDriverStatementConfig) => Statement): Promise<number>; // returns the number of affected rows
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
export class Db<R extends Record = Record> implements DbService<R> {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
36
|
+
private static defaultDbDriver: DbDriver;
|
|
37
|
+
private dbDriver: DbDriver;
|
|
38
|
+
private logger = new Logger(this.constructor.name);
|
|
39
|
+
private statementConfigFactory: StatementConfigFactory;
|
|
40
|
+
private auth = new TableAuth();
|
|
41
|
+
public serviceMetadata: Service['serviceMetadata'] = {
|
|
42
|
+
auth: {
|
|
43
|
+
canAccess: (methodName, args) => new TableServiceAuth().canAccess(methodName, args),
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
constructor(
|
|
48
|
+
dbDriver?: DbDriver,
|
|
49
|
+
getTable?: (tableName: string) => Table<any>,
|
|
50
|
+
private ignoreAuth?: boolean
|
|
51
|
+
) {
|
|
52
|
+
this.dbDriver = dbDriver ? dbDriver : this.getDefaultDbDriver();
|
|
53
|
+
this.statementConfigFactory = new StatementConfigFactory(this.dbDriver.getDbName(), getTable);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private getDefaultDbDriver(): DbDriver {
|
|
57
|
+
if (!Db.defaultDbDriver) {
|
|
58
|
+
const defaultDbDriverFactory = SourceRepository.get().object<DefaultDbDriverFactory>(
|
|
59
|
+
'@proteinjs/db/DefaultDbDriverFactory'
|
|
60
|
+
);
|
|
61
|
+
if (!defaultDbDriverFactory) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Unable to find a @proteinjs/db/DefaultDbDriverFactory implementation. Either implement DefaultDbDriverFactory or pass in a db driver when instantiating Db.`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
Db.defaultDbDriver = defaultDbDriverFactory.getDbDriver();
|
|
53
68
|
}
|
|
54
69
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const defaultDbDriverFactory = SourceRepository.get().object<DefaultDbDriverFactory>('@proteinjs/db/DefaultDbDriverFactory');
|
|
58
|
-
if (!defaultDbDriverFactory)
|
|
59
|
-
throw new Error(`Unable to find a @proteinjs/db/DefaultDbDriverFactory implementation. Either implement DefaultDbDriverFactory or pass in a db driver when instantiating Db.`);
|
|
70
|
+
return Db.defaultDbDriver;
|
|
71
|
+
}
|
|
60
72
|
|
|
61
|
-
|
|
62
|
-
|
|
73
|
+
async init(): Promise<void> {
|
|
74
|
+
await this.dbDriver.createDbIfNotExists();
|
|
75
|
+
await this.dbDriver.getTableManager().loadTables();
|
|
76
|
+
await new SourceRecordLoader().load();
|
|
77
|
+
}
|
|
63
78
|
|
|
64
|
-
|
|
65
|
-
|
|
79
|
+
async tableExists<T extends R>(table: Table<T>): Promise<boolean> {
|
|
80
|
+
return await this.dbDriver.getTableManager().tableExists(table);
|
|
81
|
+
}
|
|
66
82
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
await new SourceRecordLoader().load();
|
|
71
|
-
}
|
|
83
|
+
async get<T extends R>(table: Table<T>, query: Query<T>): Promise<T> {
|
|
84
|
+
return (await this.query(table, query))[0];
|
|
85
|
+
}
|
|
72
86
|
|
|
73
|
-
|
|
74
|
-
|
|
87
|
+
async insert<T extends R>(table: Table<T>, record: Omit<T, keyof R>): Promise<T> {
|
|
88
|
+
if (!this.ignoreAuth) {
|
|
89
|
+
this.auth.canInsert(table);
|
|
75
90
|
}
|
|
76
91
|
|
|
77
|
-
|
|
78
|
-
|
|
92
|
+
const recordCopy = Object.assign({}, record);
|
|
93
|
+
await this.addDefaultFieldValues(table, recordCopy);
|
|
94
|
+
const recordSearializer = new RecordSerializer(table);
|
|
95
|
+
const serializedRecord = await recordSearializer.serialize(recordCopy);
|
|
96
|
+
const generateInsert = (config: DbDriverStatementConfig) =>
|
|
97
|
+
new StatementFactory<T>().insert(
|
|
98
|
+
table.name,
|
|
99
|
+
serializedRecord as Partial<T>,
|
|
100
|
+
this.statementConfigFactory.getStatementConfig(config)
|
|
101
|
+
);
|
|
102
|
+
await this.dbDriver.runDml(generateInsert);
|
|
103
|
+
return recordCopy as T;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private async addDefaultFieldValues<T extends R>(table: Table<T>, record: any) {
|
|
107
|
+
for (const columnPropertyName in table.columns) {
|
|
108
|
+
const column = (table.columns as any)[columnPropertyName] as Column<any, any>;
|
|
109
|
+
if (column.options?.defaultValue && typeof record[columnPropertyName] === 'undefined') {
|
|
110
|
+
record[columnPropertyName] = await column.options.defaultValue(record);
|
|
111
|
+
}
|
|
79
112
|
}
|
|
113
|
+
}
|
|
80
114
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const recordCopy = Object.assign({}, record);
|
|
86
|
-
await this.addDefaultFieldValues(table, recordCopy);
|
|
87
|
-
const recordSearializer = new RecordSerializer(table);
|
|
88
|
-
const serializedRecord = await recordSearializer.serialize(recordCopy);
|
|
89
|
-
const generateInsert = (config: DbDriverStatementConfig) => new StatementFactory<T>().insert(table.name, serializedRecord as Partial<T>, this.statementConfigFactory.getStatementConfig(config));
|
|
90
|
-
await this.dbDriver.runDml(generateInsert);
|
|
91
|
-
return recordCopy as T;
|
|
115
|
+
async update<T extends R>(table: Table<T>, record: Partial<T>, query?: Query<T>): Promise<number> {
|
|
116
|
+
if (!this.ignoreAuth) {
|
|
117
|
+
this.auth.canUpdate(table);
|
|
92
118
|
}
|
|
93
119
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const column = (table.columns as any)[columnPropertyName] as Column<any, any>;
|
|
97
|
-
if (column.options?.defaultValue && typeof record[columnPropertyName] === 'undefined')
|
|
98
|
-
record[columnPropertyName] = await column.options.defaultValue(record);
|
|
99
|
-
}
|
|
120
|
+
if (!query && !record.id) {
|
|
121
|
+
throw new Error(`Update must be called with either a QueryBuilder or a record with an id property`);
|
|
100
122
|
}
|
|
101
123
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const recordCopy = Object.assign({}, record);
|
|
110
|
-
await this.addUpdateFieldValues(table, recordCopy);
|
|
111
|
-
const recordSearializer = new RecordSerializer<T>(table);
|
|
112
|
-
const serializedRecord = await recordSearializer.serialize(recordCopy);
|
|
113
|
-
const qb = new QueryBuilderFactory().getQueryBuilder(table, query);
|
|
114
|
-
if (!query)
|
|
115
|
-
qb.condition({ field: 'id', operator: '=', value: recordCopy.id as T[keyof T] });
|
|
116
|
-
|
|
117
|
-
delete serializedRecord['id'];
|
|
118
|
-
const generateUpdate = (config: DbDriverStatementConfig) => new StatementFactory<T>().update(table.name, serializedRecord as Partial<T>, qb, this.statementConfigFactory.getStatementConfig(config));
|
|
119
|
-
return await this.dbDriver.runDml(generateUpdate);
|
|
124
|
+
const recordCopy = Object.assign({}, record);
|
|
125
|
+
await this.addUpdateFieldValues(table, recordCopy);
|
|
126
|
+
const recordSearializer = new RecordSerializer<T>(table);
|
|
127
|
+
const serializedRecord = await recordSearializer.serialize(recordCopy);
|
|
128
|
+
const qb = new QueryBuilderFactory().getQueryBuilder(table, query);
|
|
129
|
+
if (!query) {
|
|
130
|
+
qb.condition({ field: 'id', operator: '=', value: recordCopy.id as T[keyof T] });
|
|
120
131
|
}
|
|
121
132
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
133
|
+
delete serializedRecord['id'];
|
|
134
|
+
const generateUpdate = (config: DbDriverStatementConfig) =>
|
|
135
|
+
new StatementFactory<T>().update(
|
|
136
|
+
table.name,
|
|
137
|
+
serializedRecord as Partial<T>,
|
|
138
|
+
qb,
|
|
139
|
+
this.statementConfigFactory.getStatementConfig(config)
|
|
140
|
+
);
|
|
141
|
+
return await this.dbDriver.runDml(generateUpdate);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private async addUpdateFieldValues<T extends R>(table: Table<T>, record: any) {
|
|
145
|
+
for (const columnPropertyName in table.columns) {
|
|
146
|
+
const column = (table.columns as any)[columnPropertyName] as Column<any, any>;
|
|
147
|
+
if (column.options?.updateValue) {
|
|
148
|
+
record[columnPropertyName] = await column.options.updateValue(record);
|
|
149
|
+
}
|
|
128
150
|
}
|
|
151
|
+
}
|
|
129
152
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const recordsToDelete = await this.query(table, query);
|
|
135
|
-
if (recordsToDelete.length == 0)
|
|
136
|
-
return 0;
|
|
137
|
-
|
|
138
|
-
const recordsToDeleteIds = recordsToDelete.map(record => record.id);
|
|
139
|
-
const deleteQb = new QueryBuilderFactory().getQueryBuilder(table);
|
|
140
|
-
deleteQb.condition({ field: 'id', operator: 'IN', value: recordsToDeleteIds as T[keyof T][] });
|
|
141
|
-
const generateDelete = (config: DbDriverStatementConfig) => new StatementFactory<T>().delete(table.name, deleteQb, this.statementConfigFactory.getStatementConfig(config));
|
|
142
|
-
await this.beforeDelete(table, recordsToDelete);
|
|
143
|
-
const deletedRowCount = await this.dbDriver.runDml(generateDelete);
|
|
144
|
-
await this.cascadeDeletions(table, recordsToDeleteIds);
|
|
145
|
-
return deletedRowCount;
|
|
153
|
+
async delete<T extends R>(table: Table<T>, query: Query<T>): Promise<number> {
|
|
154
|
+
if (!this.ignoreAuth) {
|
|
155
|
+
this.auth.canDelete(table);
|
|
146
156
|
}
|
|
147
157
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (typeof column.beforeDelete !== 'undefined')
|
|
152
|
-
await column.beforeDelete(table, columnPropertyName, recordsToDelete);
|
|
153
|
-
}
|
|
158
|
+
const recordsToDelete = await this.query(table, query);
|
|
159
|
+
if (recordsToDelete.length == 0) {
|
|
160
|
+
return 0;
|
|
154
161
|
}
|
|
155
162
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
163
|
+
const recordsToDeleteIds = recordsToDelete.map((record) => record.id);
|
|
164
|
+
const deleteQb = new QueryBuilderFactory().getQueryBuilder(table);
|
|
165
|
+
deleteQb.condition({ field: 'id', operator: 'IN', value: recordsToDeleteIds as T[keyof T][] });
|
|
166
|
+
const generateDelete = (config: DbDriverStatementConfig) =>
|
|
167
|
+
new StatementFactory<T>().delete(table.name, deleteQb, this.statementConfigFactory.getStatementConfig(config));
|
|
168
|
+
await this.beforeDelete(table, recordsToDelete);
|
|
169
|
+
const deletedRowCount = await this.dbDriver.runDml(generateDelete);
|
|
170
|
+
await this.cascadeDeletions(table, recordsToDeleteIds);
|
|
171
|
+
return deletedRowCount;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private async beforeDelete(table: Table<any>, recordsToDelete: Record[]) {
|
|
175
|
+
for (const columnPropertyName in table.columns) {
|
|
176
|
+
const column = (table.columns as any)[columnPropertyName] as Column<any, any>;
|
|
177
|
+
if (typeof column.beforeDelete !== 'undefined') {
|
|
178
|
+
await column.beforeDelete(table, columnPropertyName, recordsToDelete);
|
|
179
|
+
}
|
|
169
180
|
}
|
|
181
|
+
}
|
|
170
182
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
183
|
+
private async cascadeDeletions(table: Table<any>, deletedRecordIds: string[]) {
|
|
184
|
+
if (table.cascadeDeleteReferences().length < 1) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
174
187
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
188
|
+
for (const cascadeDeleteReference of table.cascadeDeleteReferences()) {
|
|
189
|
+
const referenceTable = tableByName(cascadeDeleteReference.table);
|
|
190
|
+
const referenceColumnPropertyName = getColumnPropertyName(referenceTable, cascadeDeleteReference.referenceColumn);
|
|
191
|
+
this.logger.info(
|
|
192
|
+
`Executing cascade delete for table: ${table.name}, referenceTable: ${referenceTable.name}, referenceColumnPropertyName: ${referenceColumnPropertyName}, deletedRecordIds: ${JSON.stringify(deletedRecordIds)}`
|
|
193
|
+
);
|
|
194
|
+
const cascadeDeleteQb = new QueryBuilderFactory().getQueryBuilder(referenceTable);
|
|
195
|
+
cascadeDeleteQb.condition({
|
|
196
|
+
field: referenceColumnPropertyName as string,
|
|
197
|
+
operator: 'IN',
|
|
198
|
+
value: deletedRecordIds,
|
|
199
|
+
});
|
|
200
|
+
const deleteCount = await this.delete(referenceTable, cascadeDeleteQb);
|
|
201
|
+
this.logger.info(`Cascade deleted ${deleteCount} record${deleteCount == 1 ? '' : 's'}`);
|
|
181
202
|
}
|
|
203
|
+
}
|
|
182
204
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
205
|
+
async query<T extends R>(table: Table<T>, query: Query<T>): Promise<T[]> {
|
|
206
|
+
if (!this.ignoreAuth) {
|
|
207
|
+
this.auth.canQuery(table);
|
|
208
|
+
}
|
|
186
209
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
210
|
+
const qb = new QueryBuilderFactory().getQueryBuilder(table, query);
|
|
211
|
+
this.addColumnQueries(table, qb);
|
|
212
|
+
const generateQuery = (config: DbDriverStatementConfig) =>
|
|
213
|
+
qb.toSql(this.statementConfigFactory.getStatementConfig(config));
|
|
214
|
+
const serializedRecords = await this.dbDriver.runQuery(generateQuery);
|
|
215
|
+
const recordSearializer = new RecordSerializer(table);
|
|
216
|
+
return await Promise.all(
|
|
217
|
+
serializedRecords.map(async (serializedRecord) => recordSearializer.deserialize(serializedRecord))
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async getRowCount<T extends R>(table: Table<T>, query?: Query<T>): Promise<number> {
|
|
222
|
+
if (!this.ignoreAuth) {
|
|
223
|
+
this.auth.canQuery(table);
|
|
193
224
|
}
|
|
194
225
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
226
|
+
const qb = new QueryBuilderFactory().getQueryBuilder(table, query);
|
|
227
|
+
qb.aggregate({ function: 'COUNT', resultProp: 'count' });
|
|
228
|
+
this.addColumnQueries(table, qb);
|
|
229
|
+
const generateQuery = (config: DbDriverStatementConfig) =>
|
|
230
|
+
qb.toSql(this.statementConfigFactory.getStatementConfig(config));
|
|
231
|
+
const result = await this.dbDriver.runQuery(generateQuery);
|
|
232
|
+
return result[0]['count'];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private async addColumnQueries<T extends R>(table: Table<T>, qb: QueryBuilder<T>) {
|
|
236
|
+
for (const columnPropertyName in table.columns) {
|
|
237
|
+
const column = (table.columns as any)[columnPropertyName] as Column<any, any>;
|
|
238
|
+
if (column.options?.addToQuery) {
|
|
239
|
+
column.options.addToQuery(qb);
|
|
240
|
+
}
|
|
201
241
|
}
|
|
202
|
-
}
|
|
242
|
+
}
|
|
243
|
+
}
|
package/src/MigrationRunner.ts
CHANGED
|
@@ -7,7 +7,8 @@ import { Migration, MigrationTable } from './tables/MigrationTable';
|
|
|
7
7
|
import { Service } from '@proteinjs/service';
|
|
8
8
|
import { Logger } from '@proteinjs/util';
|
|
9
9
|
|
|
10
|
-
export const getMigrationRunner = () =>
|
|
10
|
+
export const getMigrationRunner = () =>
|
|
11
|
+
typeof self === 'undefined' ? new MigrationRunner() : (getMigrationRunnerService() as MigrationRunner);
|
|
11
12
|
|
|
12
13
|
export class MigrationRunner implements MigrationRunnerService {
|
|
13
14
|
private logger = new Logger(this.constructor.name);
|
|
@@ -18,8 +19,9 @@ export class MigrationRunner implements MigrationRunnerService {
|
|
|
18
19
|
async runMigration(id: string): Promise<void> {
|
|
19
20
|
const migrationTable: Table<Migration> = new MigrationTable();
|
|
20
21
|
const migration = new SourceRecordRepo().getSourceRecord<Migration>(migrationTable.name, id);
|
|
21
|
-
if (!migration)
|
|
22
|
+
if (!migration) {
|
|
22
23
|
throw new Error(`Unable to find migration source record for id: ${id}`);
|
|
24
|
+
}
|
|
23
25
|
|
|
24
26
|
const db = getDb();
|
|
25
27
|
migration.status = 'running';
|
|
@@ -38,12 +40,14 @@ export class MigrationRunner implements MigrationRunnerService {
|
|
|
38
40
|
}
|
|
39
41
|
migration.duration = this.duration(migration.startTime, migration.endTime);
|
|
40
42
|
await db.update(migrationTable, migration);
|
|
41
|
-
this.logger.info(
|
|
43
|
+
this.logger.info(
|
|
44
|
+
`[${migration.status}] (${migration.duration}) Finished running migration (${migration.id}) ${migration.description}`
|
|
45
|
+
);
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
private duration(start: Moment, end: Moment): string {
|
|
45
49
|
const duration = moment.duration(end.diff(start));
|
|
46
|
-
|
|
50
|
+
const parts: string[] = [];
|
|
47
51
|
|
|
48
52
|
const days = duration.days();
|
|
49
53
|
const hours = duration.hours();
|
|
@@ -51,12 +55,22 @@ export class MigrationRunner implements MigrationRunnerService {
|
|
|
51
55
|
const seconds = duration.seconds();
|
|
52
56
|
const milliseconds = duration.milliseconds();
|
|
53
57
|
|
|
54
|
-
if (days > 0)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (
|
|
58
|
-
|
|
58
|
+
if (days > 0) {
|
|
59
|
+
parts.push(`${days} day${days > 1 ? 's' : ''}`);
|
|
60
|
+
}
|
|
61
|
+
if (hours > 0) {
|
|
62
|
+
parts.push(`${hours} hour${hours > 1 ? 's' : ''}`);
|
|
63
|
+
}
|
|
64
|
+
if (minutes > 0) {
|
|
65
|
+
parts.push(`${minutes} min${minutes > 1 ? 's' : ''}`);
|
|
66
|
+
}
|
|
67
|
+
if (seconds > 0) {
|
|
68
|
+
parts.push(`${seconds} sec${seconds > 1 ? 's' : ''}`);
|
|
69
|
+
}
|
|
70
|
+
if (days == 0 && hours == 0 && minutes == 0 && seconds == 0) {
|
|
71
|
+
parts.push(`${milliseconds} ms`);
|
|
72
|
+
}
|
|
59
73
|
|
|
60
74
|
return parts.join(' ');
|
|
61
75
|
}
|
|
62
|
-
}
|
|
76
|
+
}
|
|
@@ -6,14 +6,11 @@ import { QueryBuilder } from '@proteinjs/db-query';
|
|
|
6
6
|
|
|
7
7
|
export class QueryBuilderFactory {
|
|
8
8
|
getQueryBuilder<T extends Record>(table: Table<T>, query?: Query<T>): QueryBuilder<T> {
|
|
9
|
-
const qb = query
|
|
10
|
-
isInstanceOf(query, QueryBuilder)
|
|
11
|
-
query as QueryBuilder<T>
|
|
12
|
-
:
|
|
13
|
-
|
|
14
|
-
:
|
|
15
|
-
new QueryBuilder<T>(table.name)
|
|
16
|
-
;
|
|
9
|
+
const qb = query
|
|
10
|
+
? isInstanceOf(query, QueryBuilder)
|
|
11
|
+
? (query as QueryBuilder<T>)
|
|
12
|
+
: QueryBuilder.fromObject<T>(query as T, table.name)
|
|
13
|
+
: new QueryBuilder<T>(table.name);
|
|
17
14
|
return qb;
|
|
18
15
|
}
|
|
19
|
-
}
|
|
16
|
+
}
|
package/src/Record.ts
CHANGED
|
@@ -3,50 +3,53 @@ import { Column, Table, Columns } from './Table';
|
|
|
3
3
|
import { moment } from './opt/moment';
|
|
4
4
|
|
|
5
5
|
export interface Record {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
id: string;
|
|
7
|
+
created: moment.Moment;
|
|
8
|
+
updated: moment.Moment;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
const recordColumns: Columns<Record> = {
|
|
12
12
|
id: new UuidColumn('id', { ui: { hidden: true } }),
|
|
13
|
-
created: new DateTimeColumn('created', {
|
|
13
|
+
created: new DateTimeColumn('created', {
|
|
14
14
|
defaultValue: async () => moment(),
|
|
15
15
|
}),
|
|
16
|
-
updated: new DateTimeColumn('updated', {
|
|
17
|
-
defaultValue: async () => moment(),
|
|
16
|
+
updated: new DateTimeColumn('updated', {
|
|
17
|
+
defaultValue: async () => moment(),
|
|
18
18
|
updateValue: async () => moment(),
|
|
19
19
|
}),
|
|
20
|
-
}
|
|
20
|
+
};
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Wrapper function to add default Record columns to your table's columns (should always use).
|
|
24
|
-
*
|
|
24
|
+
*
|
|
25
25
|
* Note: using this requires an explicit dependency on moment@2.29.4 in your package (since transient dependencies are brittle by typescript's standards)
|
|
26
|
-
*
|
|
26
|
+
*
|
|
27
27
|
* @param columns your columns
|
|
28
28
|
* @returns recordColumns & your columns
|
|
29
29
|
*/
|
|
30
|
-
export function withRecordColumns<T extends Record>(
|
|
30
|
+
export function withRecordColumns<T extends Record>(
|
|
31
|
+
columns: Columns<Omit<T, keyof Record>>
|
|
32
|
+
): Columns<Record> & Columns<Omit<T, keyof Record>> {
|
|
31
33
|
return Object.assign(Object.assign({}, recordColumns), columns);
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
export type SerializedRecord = { [columnName: string]: any }
|
|
36
|
+
export type SerializedRecord = { [columnName: string]: any };
|
|
35
37
|
|
|
36
38
|
export class RecordSerializer<T extends Record> {
|
|
37
39
|
private table: Table<T>;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
|
|
41
|
+
constructor(table: Table<T>) {
|
|
40
42
|
this.table = table;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
async serialize(record: any): Promise<SerializedRecord> {
|
|
44
46
|
const serialized: any = {};
|
|
45
47
|
const fieldSerializer = new FieldSerializer(this.table);
|
|
46
|
-
for (
|
|
47
|
-
if (typeof record[fieldPropertyName] === 'function')
|
|
48
|
+
for (const fieldPropertyName in record) {
|
|
49
|
+
if (typeof record[fieldPropertyName] === 'function') {
|
|
48
50
|
continue;
|
|
49
|
-
|
|
51
|
+
}
|
|
52
|
+
|
|
50
53
|
const fieldValue = await record[fieldPropertyName];
|
|
51
54
|
const { columnName, serializedFieldValue } = await fieldSerializer.serialize(fieldPropertyName, fieldValue);
|
|
52
55
|
serialized[columnName] = serializedFieldValue;
|
|
@@ -57,7 +60,7 @@ export class RecordSerializer<T extends Record> {
|
|
|
57
60
|
async deserialize(serializedRecord: SerializedRecord): Promise<T> {
|
|
58
61
|
const deserialized: any = {};
|
|
59
62
|
const fieldSerializer = new FieldSerializer(this.table);
|
|
60
|
-
for (
|
|
63
|
+
for (const columnName in serializedRecord) {
|
|
61
64
|
const serializedFieldValue = serializedRecord[columnName];
|
|
62
65
|
const { fieldPropertyName, fieldValue } = await fieldSerializer.deserialize(columnName, serializedFieldValue);
|
|
63
66
|
deserialized[fieldPropertyName] = fieldValue;
|
|
@@ -67,29 +70,31 @@ export class RecordSerializer<T extends Record> {
|
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
export class FieldSerializer<T extends Record> {
|
|
70
|
-
constructor(
|
|
71
|
-
private table: Table<T>
|
|
72
|
-
){}
|
|
73
|
+
constructor(private table: Table<T>) {}
|
|
73
74
|
|
|
74
75
|
async serialize(fieldPropertyName: string, fieldValue: any) {
|
|
75
|
-
const columns: {[prop: string]: Column<any, any>} = this.table.columns;
|
|
76
|
+
const columns: { [prop: string]: Column<any, any> } = this.table.columns;
|
|
76
77
|
const column = columns[fieldPropertyName];
|
|
77
|
-
if (!column)
|
|
78
|
-
throw new Error(
|
|
78
|
+
if (!column) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`[FieldSerializer.serialize] (${this.table.name}) no column matches fieldPropertyName: ${fieldPropertyName}`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
79
83
|
|
|
80
84
|
let serializedFieldValue = fieldValue;
|
|
81
|
-
if (column.serialize)
|
|
85
|
+
if (column.serialize) {
|
|
82
86
|
serializedFieldValue = await column.serialize(fieldValue);
|
|
87
|
+
}
|
|
83
88
|
|
|
84
89
|
return { columnName: column.name, serializedFieldValue };
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
async deserialize(columnName: string, serializedFieldValue: any) {
|
|
88
|
-
const columns: {[prop: string]: Column<any, any>} = this.table.columns;
|
|
93
|
+
const columns: { [prop: string]: Column<any, any> } = this.table.columns;
|
|
89
94
|
let fieldPropertyName = columnName;
|
|
90
95
|
let column = columns[columnName]; // the scenario that the column name is the same as the property name
|
|
91
96
|
if (!column) {
|
|
92
|
-
for (
|
|
97
|
+
for (const columnPropertyName in columns) {
|
|
93
98
|
const checkColumn = (this.table.columns as any)[columnPropertyName];
|
|
94
99
|
if (checkColumn && columnName == checkColumn.name) {
|
|
95
100
|
fieldPropertyName = columnPropertyName;
|
|
@@ -104,10 +109,11 @@ export class FieldSerializer<T extends Record> {
|
|
|
104
109
|
return { fieldPropertyName, fieldValue: undefined };
|
|
105
110
|
}
|
|
106
111
|
|
|
107
|
-
let fieldValue = serializedFieldValue
|
|
108
|
-
if (column.deserialize)
|
|
112
|
+
let fieldValue = serializedFieldValue;
|
|
113
|
+
if (column.deserialize) {
|
|
109
114
|
fieldValue = await column.deserialize(serializedFieldValue);
|
|
115
|
+
}
|
|
110
116
|
|
|
111
117
|
return { fieldPropertyName, fieldValue };
|
|
112
118
|
}
|
|
113
|
-
}
|
|
119
|
+
}
|
|
@@ -6,31 +6,29 @@ export class StatementConfigFactory {
|
|
|
6
6
|
private dbName: string;
|
|
7
7
|
private getTable: (tableName: string) => Table<any>;
|
|
8
8
|
|
|
9
|
-
constructor(
|
|
10
|
-
dbName: string,
|
|
11
|
-
getTable?: (tableName: string) => Table<any>
|
|
12
|
-
) {
|
|
9
|
+
constructor(dbName: string, getTable?: (tableName: string) => Table<any>) {
|
|
13
10
|
this.dbName = dbName;
|
|
14
11
|
this.getTable = getTable ? getTable : tableByName;
|
|
15
12
|
}
|
|
16
13
|
|
|
17
14
|
getStatementConfig(config: DbDriverStatementConfig): StatementConfig {
|
|
18
15
|
return {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
dbName: config.prefixTablesWithDb ? this.dbName : undefined,
|
|
17
|
+
resolveFieldName: this.getResolveFieldName(),
|
|
18
|
+
useParams: config.useParams,
|
|
19
|
+
useNamedParams: config.useNamedParams,
|
|
23
20
|
};
|
|
24
21
|
}
|
|
25
22
|
|
|
26
23
|
private getResolveFieldName() {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return column.name;
|
|
24
|
+
return (tableName: string, propertyName: string): string => {
|
|
25
|
+
const table = this.getTable(tableName);
|
|
26
|
+
const column = table.columns[propertyName];
|
|
27
|
+
if (!column) {
|
|
28
|
+
throw new Error(`(${table.name}) Column does not exist for property: ${propertyName}`);
|
|
34
29
|
}
|
|
30
|
+
|
|
31
|
+
return column.name;
|
|
32
|
+
};
|
|
35
33
|
}
|
|
36
|
-
}
|
|
34
|
+
}
|