@proteinjs/db 1.0.15 → 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 +10 -31
- 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.map +1 -1
- package/dist/test/CrudTests.js +13 -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 +48 -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 +43 -30
- package/test/TableManagerTests.ts +329 -154
- package/tsconfig.json +18 -22
package/src/Table.ts
CHANGED
|
@@ -10,117 +10,120 @@ export const isTable = (obj: any) => obj.__serializerId === TableSerializerId;
|
|
|
10
10
|
export const getTables = <T extends Record = any>() => SourceRepository.get().objects<Table<T>>('@proteinjs/db/Table');
|
|
11
11
|
|
|
12
12
|
export const tableByName = (name: string) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
const tables = getTables();
|
|
14
|
+
for (const table of tables) {
|
|
15
|
+
if (table.name == name) {
|
|
16
|
+
return table;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
}
|
|
20
|
+
throw new Error(`Unable to find table: ${name}`);
|
|
21
|
+
};
|
|
21
22
|
|
|
22
23
|
export const getColumnPropertyName = (table: Table<any>, columnName: string) => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
for (const columnPropertyName in table.columns) {
|
|
25
|
+
const column = table.columns[columnPropertyName];
|
|
26
|
+
if (column.name == columnName) {
|
|
27
|
+
return columnPropertyName;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
+
return null;
|
|
32
|
+
};
|
|
31
33
|
|
|
32
34
|
export const getColumnByName = (table: Table<any>, columnName: string) => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
for (const columnPropertyName in table.columns) {
|
|
36
|
+
const column = table.columns[columnPropertyName];
|
|
37
|
+
if (column.name == columnName) {
|
|
38
|
+
return column;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
}
|
|
42
|
+
return null;
|
|
43
|
+
};
|
|
41
44
|
|
|
42
45
|
/**
|
|
43
46
|
* primary key is `id`
|
|
44
47
|
*/
|
|
45
48
|
export abstract class Table<T extends Record> implements Loadable, CustomSerializableObject {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
49
|
+
public __serializerId = TableSerializerId;
|
|
50
|
+
abstract name: string;
|
|
51
|
+
abstract columns: Columns<T>;
|
|
52
|
+
public indexes: { columns: (keyof T)[]; name?: string }[] = [];
|
|
53
|
+
/** When records are deleted, delete records having references pointing to deleted records */
|
|
54
|
+
public cascadeDeleteReferences: () => { table: string; referenceColumn: string }[] = () => [];
|
|
55
|
+
/**
|
|
56
|
+
* Options for configuring SourceRecords
|
|
57
|
+
* @param doNotDeleteSourceRecordsFromDb if true, the SourceRecordLoader will not delete source records from the db if they no longer exist on the file system
|
|
58
|
+
*/
|
|
59
|
+
public sourceRecordOptions: SourceRecordOptions = {
|
|
60
|
+
doNotDeleteSourceRecordsFromDb: false,
|
|
61
|
+
};
|
|
62
|
+
public auth?: {
|
|
63
|
+
db?: TableOperationsAuth;
|
|
64
|
+
service?: TableOperationsAuth;
|
|
65
|
+
ui?: {
|
|
66
|
+
recordTable?: Identity;
|
|
67
|
+
recordForm?: Identity;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
type ExcludeFunctions<T> = {
|
|
70
|
-
[P in keyof T as T[P] extends Function ? never : P]: T[P]
|
|
71
|
-
}
|
|
73
|
+
[P in keyof T as T[P] extends Function ? never : P]: T[P];
|
|
74
|
+
};
|
|
72
75
|
|
|
73
76
|
type RequiredProps<T> = {
|
|
74
|
-
[P in keyof ExcludeFunctions<T>]: ExcludeFunctions<T>[P] extends undefined ? never : P
|
|
75
|
-
}[keyof ExcludeFunctions<T>]
|
|
77
|
+
[P in keyof ExcludeFunctions<T>]: ExcludeFunctions<T>[P] extends undefined ? never : P;
|
|
78
|
+
}[keyof ExcludeFunctions<T>];
|
|
76
79
|
|
|
77
80
|
type OptionalProps<T> = {
|
|
78
|
-
[P in keyof ExcludeFunctions<T>]: ExcludeFunctions<T>[P] extends undefined ? P : never
|
|
79
|
-
}[keyof ExcludeFunctions<T>]
|
|
81
|
+
[P in keyof ExcludeFunctions<T>]: ExcludeFunctions<T>[P] extends undefined ? P : never;
|
|
82
|
+
}[keyof ExcludeFunctions<T>];
|
|
80
83
|
|
|
81
84
|
export type Columns<T> = {
|
|
82
|
-
|
|
85
|
+
[P in RequiredProps<T>]: Column<T[P], any>;
|
|
83
86
|
} & {
|
|
84
|
-
|
|
85
|
-
}
|
|
87
|
+
[P in OptionalProps<T>]?: Column<T[P] | undefined, any>;
|
|
88
|
+
};
|
|
86
89
|
|
|
87
90
|
export type Column<T, Serialized> = {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
91
|
+
name: string;
|
|
92
|
+
/**
|
|
93
|
+
* Use to rename column, will find column with `oldName` and change it to `name`.
|
|
94
|
+
*
|
|
95
|
+
* Note: after name change has happened in prod, oldName can be removed.
|
|
96
|
+
*/
|
|
97
|
+
oldName?: string;
|
|
98
|
+
options?: ColumnOptions;
|
|
99
|
+
serialize?: (fieldValue: T | undefined) => Promise<Serialized | undefined>;
|
|
100
|
+
deserialize?: (serializedFieldValue: Serialized) => Promise<T | void>;
|
|
101
|
+
beforeDelete?: (table: Table<any>, columnPropertyName: string, records: any[]) => Promise<void>;
|
|
102
|
+
};
|
|
100
103
|
|
|
101
104
|
export type ColumnOptions = {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export type SourceRecordOptions = {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
105
|
+
unique?: { unique: boolean; indexName?: string };
|
|
106
|
+
/**
|
|
107
|
+
* The column in the reference table `table` is the primary key of the table (`id` unless otherwise specified in the Table definition)
|
|
108
|
+
*
|
|
109
|
+
* Note: use a migration to drop or change an existing foreign key
|
|
110
|
+
*/
|
|
111
|
+
references?: { table: string };
|
|
112
|
+
nullable?: boolean;
|
|
113
|
+
/** Value stored on insert */
|
|
114
|
+
defaultValue?: (insertObj: any) => Promise<any>;
|
|
115
|
+
/** Value stored on update */
|
|
116
|
+
updateValue?: (updateObj: any) => Promise<any>;
|
|
117
|
+
/** Add conditions to query; called on every query of this table */
|
|
118
|
+
addToQuery?: (qb: QueryBuilder) => Promise<void>;
|
|
119
|
+
ui?: {
|
|
120
|
+
hidden?: boolean;
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export type SourceRecordOptions = {
|
|
125
|
+
doNotDeleteSourceRecordsFromDb?: boolean;
|
|
126
|
+
ui?: {
|
|
127
|
+
hideColumns?: boolean;
|
|
128
|
+
};
|
|
129
|
+
};
|
package/src/auth/TableAuth.ts
CHANGED
|
@@ -1,60 +1,70 @@
|
|
|
1
|
-
import { UserAuth } from '@proteinjs/user-auth'
|
|
2
|
-
import { Table } from '../Table'
|
|
1
|
+
import { UserAuth } from '@proteinjs/user-auth';
|
|
2
|
+
import { Table } from '../Table';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @public all users, including guests (do not need to be logged in), do not need any roles
|
|
6
6
|
* @authenticated - users need to be logged in, do not need any roles
|
|
7
7
|
* @roles string[] - authenticated users, having at least one of these roles
|
|
8
8
|
*/
|
|
9
|
-
export type Identity = 'public'|'authenticated'|string[]
|
|
9
|
+
export type Identity = 'public' | 'authenticated' | string[];
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* These Identities can perform these operations on this table.
|
|
13
13
|
* If omitted, defaults to requiring the 'admin' role (default deny)
|
|
14
14
|
*/
|
|
15
15
|
export type TableOperationsAuth = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
16
|
+
all?: Identity;
|
|
17
|
+
query?: Identity;
|
|
18
|
+
insert?: Identity;
|
|
19
|
+
update?: Identity;
|
|
20
|
+
delete?: Identity;
|
|
21
|
+
};
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Util to check which table operations a user can perform
|
|
25
25
|
*/
|
|
26
26
|
export class TableAuth {
|
|
27
|
-
private canAccess(
|
|
27
|
+
private canAccess(
|
|
28
|
+
table: Table<any>,
|
|
29
|
+
api: 'db' | 'service',
|
|
30
|
+
operation: 'query' | 'insert' | 'update' | 'delete'
|
|
31
|
+
): boolean {
|
|
28
32
|
const tableAuth = table.auth ? table.auth[api] : undefined;
|
|
29
|
-
if (!tableAuth || Object.keys(tableAuth).length == 0)
|
|
33
|
+
if (!tableAuth || Object.keys(tableAuth).length == 0) {
|
|
30
34
|
return UserAuth.hasRole('admin');
|
|
35
|
+
}
|
|
31
36
|
|
|
32
|
-
return
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
return (
|
|
38
|
+
tableAuth.all === 'public' ||
|
|
39
|
+
tableAuth[operation] === 'public' ||
|
|
40
|
+
(tableAuth.all === 'authenticated' && UserAuth.isLoggedIn()) ||
|
|
41
|
+
(tableAuth[operation] === 'authenticated' && UserAuth.isLoggedIn()) ||
|
|
42
|
+
(Array.isArray(tableAuth.all) && UserAuth.hasRoles(tableAuth.all, 'at least one')) ||
|
|
43
|
+
(Array.isArray(tableAuth[operation]) && UserAuth.hasRoles(tableAuth[operation] as string[], 'at least one'))
|
|
44
|
+
);
|
|
39
45
|
}
|
|
40
46
|
|
|
41
|
-
canQuery(table: Table<any>, api: 'db'|'service' = 'db'): void {
|
|
42
|
-
if (!this.canAccess(table, api, 'query'))
|
|
47
|
+
canQuery(table: Table<any>, api: 'db' | 'service' = 'db'): void {
|
|
48
|
+
if (!this.canAccess(table, api, 'query')) {
|
|
43
49
|
throw new Error(`User is not authorized to query table: ${table.name}`);
|
|
50
|
+
}
|
|
44
51
|
}
|
|
45
52
|
|
|
46
|
-
canInsert(table: Table<any>, api: 'db'|'service' = 'db'): void {
|
|
47
|
-
if (!this.canAccess(table, api, 'insert'))
|
|
53
|
+
canInsert(table: Table<any>, api: 'db' | 'service' = 'db'): void {
|
|
54
|
+
if (!this.canAccess(table, api, 'insert')) {
|
|
48
55
|
throw new Error(`User is not authorized to insert records into table: ${table.name}`);
|
|
56
|
+
}
|
|
49
57
|
}
|
|
50
58
|
|
|
51
|
-
canUpdate(table: Table<any>, api: 'db'|'service' = 'db'): void {
|
|
52
|
-
if (!this.canAccess(table, api, 'update'))
|
|
59
|
+
canUpdate(table: Table<any>, api: 'db' | 'service' = 'db'): void {
|
|
60
|
+
if (!this.canAccess(table, api, 'update')) {
|
|
53
61
|
throw new Error(`User is not authorized to update records in table: ${table.name}`);
|
|
62
|
+
}
|
|
54
63
|
}
|
|
55
64
|
|
|
56
|
-
canDelete(table: Table<any>, api: 'db'|'service' = 'db'): void {
|
|
57
|
-
if (!this.canAccess(table, api, 'query'))
|
|
65
|
+
canDelete(table: Table<any>, api: 'db' | 'service' = 'db'): void {
|
|
66
|
+
if (!this.canAccess(table, api, 'query')) {
|
|
58
67
|
throw new Error(`User is not authorized to delete records from table: ${table.name}`);
|
|
68
|
+
}
|
|
59
69
|
}
|
|
60
|
-
}
|
|
70
|
+
}
|
|
@@ -7,21 +7,23 @@ export class TableServiceAuth {
|
|
|
7
7
|
|
|
8
8
|
canAccess(methodName: string, args: any[]): boolean {
|
|
9
9
|
try {
|
|
10
|
-
const table: Table<any
|
|
11
|
-
if (!isTable(table))
|
|
10
|
+
const table: Table<any> | undefined = args[0];
|
|
11
|
+
if (!isTable(table)) {
|
|
12
12
|
throw new Error(`[DbServiceAuth] Expected first arg to be a table`);
|
|
13
|
-
|
|
13
|
+
}
|
|
14
|
+
|
|
14
15
|
const tableAuth = new TableAuth();
|
|
15
|
-
if (methodName === 'get' || methodName == 'query' || methodName == 'getRowCount')
|
|
16
|
+
if (methodName === 'get' || methodName == 'query' || methodName == 'getRowCount') {
|
|
16
17
|
tableAuth.canQuery(table as Table<any>, 'service');
|
|
17
|
-
else if (methodName === 'insert')
|
|
18
|
+
} else if (methodName === 'insert') {
|
|
18
19
|
tableAuth.canInsert(table as Table<any>, 'service');
|
|
19
|
-
else if (methodName === 'update')
|
|
20
|
+
} else if (methodName === 'update') {
|
|
20
21
|
tableAuth.canUpdate(table as Table<any>, 'service');
|
|
21
|
-
else if (methodName === 'delete')
|
|
22
|
+
} else if (methodName === 'delete') {
|
|
22
23
|
tableAuth.canDelete(table as Table<any>, 'service');
|
|
23
|
-
else
|
|
24
|
+
} else {
|
|
24
25
|
throw new Error(`User is not authorized to access unsupported Db service api: ${methodName}`);
|
|
26
|
+
}
|
|
25
27
|
} catch (error: any) {
|
|
26
28
|
this.logger.error(error.stack);
|
|
27
29
|
return false;
|
|
@@ -29,4 +31,4 @@ export class TableServiceAuth {
|
|
|
29
31
|
|
|
30
32
|
return true;
|
|
31
33
|
}
|
|
32
|
-
}
|
|
34
|
+
}
|
package/src/opt/moment.ts
CHANGED
|
@@ -6,12 +6,12 @@ import { ReferenceSerializerId } from '../serializers/ReferenceSerializer';
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* The object returned by Db functions for each field of type ReferenceColumn in a record.
|
|
9
|
-
* The reason for this is to make loading of reference records on-demand. For theoretically
|
|
9
|
+
* The reason for this is to make loading of reference records on-demand. For theoretically
|
|
10
10
|
* infinitely-nested references, this is necessary. For everything else, this is optimal.
|
|
11
|
-
*
|
|
11
|
+
*
|
|
12
12
|
* Note: updating the reference via set does not automatically persist those changes in the db.
|
|
13
13
|
* You still need to call Db.update on the record as you would when changing any other field on the record.
|
|
14
|
-
*
|
|
14
|
+
*
|
|
15
15
|
* Note: all instance members are internal state, made public for ReferenceArraySerializer.
|
|
16
16
|
* Only get() and set() should be used.
|
|
17
17
|
*/
|
|
@@ -21,14 +21,14 @@ export class Reference<T extends Record> implements CustomSerializableObject {
|
|
|
21
21
|
constructor(
|
|
22
22
|
public _table: string,
|
|
23
23
|
public _id?: string,
|
|
24
|
-
public _object?: T
|
|
24
|
+
public _object?: T
|
|
25
25
|
) {}
|
|
26
26
|
|
|
27
|
-
static fromObject<T extends Record>(table: string, object: T|(Partial<T> & { id: string })) {
|
|
27
|
+
static fromObject<T extends Record>(table: string, object: T | (Partial<T> & { id: string })) {
|
|
28
28
|
return new Reference<T>(table, object.id, object as T);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
async get(): Promise<T|undefined> {
|
|
31
|
+
async get(): Promise<T | undefined> {
|
|
32
32
|
if (!this._object && this._id) {
|
|
33
33
|
const table = tableByName(this._table);
|
|
34
34
|
const db = getDb();
|
|
@@ -42,4 +42,4 @@ export class Reference<T extends Record> implements CustomSerializableObject {
|
|
|
42
42
|
this._object = object;
|
|
43
43
|
this._id = object.id;
|
|
44
44
|
}
|
|
45
|
-
}
|
|
45
|
+
}
|
|
@@ -7,15 +7,15 @@ import { QueryBuilderFactory } from '../QueryBuilderFactory';
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* The object returned by Db functions for each field of type ReferenceArrayColumn in a record.
|
|
10
|
-
* The reason for this is to make loading of reference records on-demand. For theoretically
|
|
10
|
+
* The reason for this is to make loading of reference records on-demand. For theoretically
|
|
11
11
|
* infinitely-nested references, this is necessary. For everything else, this is optimal.
|
|
12
|
-
*
|
|
12
|
+
*
|
|
13
13
|
* Note: generally only get() needs to be called. Array operations performed on the returned array will be
|
|
14
14
|
* persisted in ReferenceArray.
|
|
15
|
-
*
|
|
15
|
+
*
|
|
16
16
|
* Note: updating items in the returned array does not automatically persist those changes in the db.
|
|
17
17
|
* You still need to call Db.update on the record as you would when changing any other field on the record.
|
|
18
|
-
*
|
|
18
|
+
*
|
|
19
19
|
* Note: all instance members are internal state, made public for ReferenceArraySerializer.
|
|
20
20
|
* Only get() and set() should be used.
|
|
21
21
|
*/
|
|
@@ -25,11 +25,11 @@ export class ReferenceArray<T extends Record> implements CustomSerializableObjec
|
|
|
25
25
|
constructor(
|
|
26
26
|
public _table: string,
|
|
27
27
|
public _ids: string[],
|
|
28
|
-
public _objects?: T[]
|
|
28
|
+
public _objects?: T[]
|
|
29
29
|
) {}
|
|
30
30
|
|
|
31
|
-
static fromObjects<T extends Record>(table: string, objects: (T|(Partial<T> & { id: string }))[]) {
|
|
32
|
-
const ids = objects.map(object => object.id);
|
|
31
|
+
static fromObjects<T extends Record>(table: string, objects: (T | (Partial<T> & { id: string }))[]) {
|
|
32
|
+
const ids = objects.map((object) => object.id);
|
|
33
33
|
return new ReferenceArray<T>(table, ids, objects as T[]);
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -53,4 +53,4 @@ export class ReferenceArray<T extends Record> implements CustomSerializableObjec
|
|
|
53
53
|
set(objects: T[]) {
|
|
54
54
|
this._objects = objects;
|
|
55
55
|
}
|
|
56
|
-
}
|
|
56
|
+
}
|
|
@@ -6,113 +6,133 @@ export class SchemaMetadata {
|
|
|
6
6
|
constructor(
|
|
7
7
|
public dbDriver: DbDriver,
|
|
8
8
|
private multiDbSupported = true
|
|
9
|
-
){}
|
|
9
|
+
) {}
|
|
10
10
|
|
|
11
11
|
async tableExists(table: Table<any>): Promise<boolean> {
|
|
12
|
-
|
|
12
|
+
const qb = new QueryBuilder('TABLES')
|
|
13
13
|
.condition({ field: 'TABLE_SCHEMA', operator: '=', value: this.dbDriver.getDbName() })
|
|
14
|
-
.condition({ field: 'TABLE_NAME', operator: '=', value: table.name })
|
|
15
|
-
;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
14
|
+
.condition({ field: 'TABLE_NAME', operator: '=', value: table.name });
|
|
15
|
+
const generateStatement = (config: ParameterizationConfig) => qb.toSql({ dbName: 'INFORMATION_SCHEMA', ...config });
|
|
16
|
+
const results = await this.dbDriver.runQuery(generateStatement);
|
|
17
|
+
return results.length > 0;
|
|
18
|
+
}
|
|
20
19
|
|
|
21
20
|
async columnExists(columnName: string, table: Table<any>): Promise<boolean> {
|
|
22
|
-
const qb = QueryBuilder.fromObject(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
const qb = QueryBuilder.fromObject(
|
|
22
|
+
{
|
|
23
|
+
TABLE_SCHEMA: this.multiDbSupported ? this.dbDriver.getDbName() : undefined,
|
|
24
|
+
TABLE_NAME: table.name,
|
|
25
|
+
COLUMN_NAME: columnName,
|
|
26
|
+
},
|
|
27
|
+
'COLUMNS'
|
|
28
|
+
);
|
|
29
|
+
const generateStatement = (config: ParameterizationConfig) => qb.toSql({ dbName: 'INFORMATION_SCHEMA', ...config });
|
|
30
|
+
const results = await this.dbDriver.runQuery(generateStatement);
|
|
31
|
+
return results.length > 0;
|
|
30
32
|
}
|
|
31
|
-
|
|
32
|
-
async getColumnMetadata(table: Table<any>): Promise<{[columnName: string]: { type: string
|
|
33
|
-
const qb = QueryBuilder.fromObject(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
|
|
34
|
+
async getColumnMetadata(table: Table<any>): Promise<{ [columnName: string]: { type: string; isNullable: boolean } }> {
|
|
35
|
+
const qb = QueryBuilder.fromObject(
|
|
36
|
+
{
|
|
37
|
+
TABLE_SCHEMA: this.multiDbSupported ? this.dbDriver.getDbName() : undefined,
|
|
38
|
+
TABLE_NAME: table.name,
|
|
39
|
+
},
|
|
40
|
+
'COLUMNS'
|
|
41
|
+
);
|
|
37
42
|
const generateStatement = (config: ParameterizationConfig) => qb.toSql({ dbName: 'INFORMATION_SCHEMA', ...config });
|
|
38
|
-
|
|
39
|
-
const columnMetadata: {[columnName: string]: { type: string
|
|
40
|
-
for (const row of results)
|
|
43
|
+
const results = await this.dbDriver.runQuery(generateStatement);
|
|
44
|
+
const columnMetadata: { [columnName: string]: { type: string; isNullable: boolean } } = {};
|
|
45
|
+
for (const row of results) {
|
|
41
46
|
columnMetadata[row['COLUMN_NAME']] = { type: row['DATA_TYPE'], isNullable: row['IS_NULLABLE'] === 'YES' };
|
|
42
|
-
|
|
47
|
+
}
|
|
48
|
+
|
|
43
49
|
return columnMetadata;
|
|
44
50
|
}
|
|
45
|
-
|
|
51
|
+
|
|
46
52
|
async getPrimaryKey(table: Table<any>): Promise<string[]> {
|
|
47
|
-
const qb = QueryBuilder.fromObject(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
const qb = QueryBuilder.fromObject(
|
|
54
|
+
{
|
|
55
|
+
TABLE_SCHEMA: this.multiDbSupported ? this.dbDriver.getDbName() : undefined,
|
|
56
|
+
TABLE_NAME: table.name,
|
|
57
|
+
CONSTRAINT_NAME: 'PRIMARY',
|
|
58
|
+
},
|
|
59
|
+
'KEY_COLUMN_USAGE'
|
|
60
|
+
);
|
|
52
61
|
const generateStatement = (config: ParameterizationConfig) => qb.toSql({ dbName: 'INFORMATION_SCHEMA' });
|
|
53
|
-
|
|
62
|
+
const results = await this.dbDriver.runQuery(generateStatement);
|
|
54
63
|
const primaryKey: string[] = [];
|
|
55
|
-
for (const row of results)
|
|
64
|
+
for (const row of results) {
|
|
56
65
|
primaryKey.push(row['COLUMN_NAME']);
|
|
57
|
-
|
|
66
|
+
}
|
|
67
|
+
|
|
58
68
|
return primaryKey;
|
|
59
69
|
}
|
|
60
|
-
|
|
61
|
-
async getForeignKeys(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
|
|
71
|
+
async getForeignKeys(
|
|
72
|
+
table: Table<any>
|
|
73
|
+
): Promise<{ [columnName: string]: { referencedTableName: string; referencedColumnName: string } }> {
|
|
74
|
+
const qb = QueryBuilder.fromObject(
|
|
75
|
+
{
|
|
76
|
+
TABLE_SCHEMA: this.multiDbSupported ? this.dbDriver.getDbName() : undefined,
|
|
77
|
+
TABLE_NAME: table.name,
|
|
78
|
+
},
|
|
79
|
+
'KEY_COLUMN_USAGE'
|
|
80
|
+
);
|
|
66
81
|
const generateStatement = (config: ParameterizationConfig) => qb.toSql({ dbName: 'INFORMATION_SCHEMA', ...config });
|
|
67
|
-
|
|
68
|
-
const foreignKeys: {[columnName: string]: { referencedTableName: string
|
|
82
|
+
const results = await this.dbDriver.runQuery(generateStatement);
|
|
83
|
+
const foreignKeys: { [columnName: string]: { referencedTableName: string; referencedColumnName: string } } = {};
|
|
69
84
|
for (const row of results) {
|
|
70
|
-
if (!row['REFERENCED_TABLE_NAME'])
|
|
85
|
+
if (!row['REFERENCED_TABLE_NAME']) {
|
|
71
86
|
continue;
|
|
72
|
-
|
|
87
|
+
}
|
|
88
|
+
|
|
73
89
|
foreignKeys[row['COLUMN_NAME']] = {
|
|
74
90
|
referencedTableName: row['REFERENCED_TABLE_NAME'],
|
|
75
91
|
referencedColumnName: row['REFERENCED_COLUMN_NAME'],
|
|
76
92
|
};
|
|
77
93
|
}
|
|
78
|
-
|
|
94
|
+
|
|
79
95
|
return foreignKeys;
|
|
80
96
|
}
|
|
81
|
-
|
|
97
|
+
|
|
82
98
|
async getUniqueColumns(table: Table<any>): Promise<string[]> {
|
|
83
|
-
const qb = QueryBuilder.fromObject(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
99
|
+
const qb = QueryBuilder.fromObject(
|
|
100
|
+
{
|
|
101
|
+
TABLE_SCHEMA: this.multiDbSupported ? this.dbDriver.getDbName() : undefined,
|
|
102
|
+
TABLE_NAME: table.name,
|
|
103
|
+
},
|
|
104
|
+
'KEY_COLUMN_USAGE'
|
|
105
|
+
);
|
|
87
106
|
const generateStatement = (config: ParameterizationConfig) => qb.toSql({ dbName: 'INFORMATION_SCHEMA', ...config });
|
|
88
|
-
|
|
107
|
+
const results = await this.dbDriver.runQuery(generateStatement);
|
|
89
108
|
const uniqueColumns: string[] = [];
|
|
90
109
|
for (const row of results) {
|
|
91
|
-
if (!(row['CONSTRAINT_NAME'] as string).endsWith('_unique'))
|
|
110
|
+
if (!(row['CONSTRAINT_NAME'] as string).endsWith('_unique')) {
|
|
92
111
|
continue;
|
|
93
|
-
|
|
112
|
+
}
|
|
113
|
+
|
|
94
114
|
uniqueColumns.push(row['COLUMN_NAME']);
|
|
95
115
|
}
|
|
96
|
-
|
|
116
|
+
|
|
97
117
|
return uniqueColumns;
|
|
98
118
|
}
|
|
99
|
-
|
|
100
|
-
async getIndexes(table: Table<any>): Promise<{[keyName: string]: string[]}> {
|
|
119
|
+
|
|
120
|
+
async getIndexes(table: Table<any>): Promise<{ [keyName: string]: string[] }> {
|
|
101
121
|
const qb = new QueryBuilder('STATISTICS')
|
|
102
122
|
.condition({ field: 'TABLE_SCHEMA', operator: '=', value: this.dbDriver.getDbName() })
|
|
103
123
|
.condition({ field: 'TABLE_NAME', operator: '=', value: table.name })
|
|
104
|
-
.sort([{ field: 'SEQ_IN_INDEX', desc: false }])
|
|
105
|
-
;
|
|
124
|
+
.sort([{ field: 'SEQ_IN_INDEX', desc: false }]);
|
|
106
125
|
const generateStatement = (config: ParameterizationConfig) => qb.toSql({ dbName: 'INFORMATION_SCHEMA', ...config });
|
|
107
|
-
|
|
126
|
+
const results: any[] = await this.dbDriver.runQuery(generateStatement);
|
|
108
127
|
const indexes: { [keyName: string]: string[] } = {};
|
|
109
128
|
for (const row of results) {
|
|
110
|
-
if (!indexes[row['INDEX_NAME']])
|
|
129
|
+
if (!indexes[row['INDEX_NAME']]) {
|
|
111
130
|
indexes[row['INDEX_NAME']] = [];
|
|
112
|
-
|
|
131
|
+
}
|
|
132
|
+
|
|
113
133
|
indexes[row['INDEX_NAME']].push(row['COLUMN_NAME']);
|
|
114
134
|
}
|
|
115
|
-
|
|
135
|
+
|
|
116
136
|
return indexes;
|
|
117
137
|
}
|
|
118
|
-
}
|
|
138
|
+
}
|
|
@@ -7,7 +7,7 @@ export interface SchemaOperations {
|
|
|
7
7
|
|
|
8
8
|
interface Index {
|
|
9
9
|
name?: string;
|
|
10
|
-
columns: string|string[];
|
|
10
|
+
columns: string | string[];
|
|
11
11
|
unique?: boolean;
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -41,4 +41,4 @@ export interface TableChanges {
|
|
|
41
41
|
columnsWithUniqueConstraintsToDrop: string[];
|
|
42
42
|
indexesToCreate: Index[];
|
|
43
43
|
indexesToDrop: Index[];
|
|
44
|
-
}
|
|
44
|
+
}
|