prostgles-server 4.2.159 → 4.2.161
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/dist/Auth/setEmailProvider.js +2 -2
- package/dist/Auth/setEmailProvider.js.map +1 -1
- package/lib/Auth/AuthHandler.ts +436 -0
- package/lib/Auth/AuthTypes.ts +280 -0
- package/lib/Auth/getSafeReturnURL.ts +35 -0
- package/lib/Auth/sendEmail.ts +83 -0
- package/lib/Auth/setAuthProviders.ts +128 -0
- package/lib/Auth/setEmailProvider.ts +85 -0
- package/lib/Auth/setupAuthRoutes.ts +161 -0
- package/lib/DBEventsManager.ts +178 -0
- package/lib/DBSchemaBuilder.ts +225 -0
- package/lib/DboBuilder/DboBuilder.ts +319 -0
- package/lib/DboBuilder/DboBuilderTypes.ts +361 -0
- package/lib/DboBuilder/QueryBuilder/Functions.ts +1153 -0
- package/lib/DboBuilder/QueryBuilder/QueryBuilder.ts +288 -0
- package/lib/DboBuilder/QueryBuilder/getJoinQuery.ts +263 -0
- package/lib/DboBuilder/QueryBuilder/getNewQuery.ts +271 -0
- package/lib/DboBuilder/QueryBuilder/getSelectQuery.ts +136 -0
- package/lib/DboBuilder/QueryBuilder/prepareHaving.ts +22 -0
- package/lib/DboBuilder/QueryStreamer.ts +250 -0
- package/lib/DboBuilder/TableHandler/DataValidator.ts +428 -0
- package/lib/DboBuilder/TableHandler/TableHandler.ts +205 -0
- package/lib/DboBuilder/TableHandler/delete.ts +115 -0
- package/lib/DboBuilder/TableHandler/insert.ts +183 -0
- package/lib/DboBuilder/TableHandler/insertTest.ts +78 -0
- package/lib/DboBuilder/TableHandler/onDeleteFromFileTable.ts +62 -0
- package/lib/DboBuilder/TableHandler/runInsertUpdateQuery.ts +134 -0
- package/lib/DboBuilder/TableHandler/update.ts +126 -0
- package/lib/DboBuilder/TableHandler/updateBatch.ts +49 -0
- package/lib/DboBuilder/TableHandler/updateFile.ts +48 -0
- package/lib/DboBuilder/TableHandler/upsert.ts +34 -0
- package/lib/DboBuilder/ViewHandler/ViewHandler.ts +393 -0
- package/lib/DboBuilder/ViewHandler/count.ts +38 -0
- package/lib/DboBuilder/ViewHandler/find.ts +153 -0
- package/lib/DboBuilder/ViewHandler/getExistsCondition.ts +73 -0
- package/lib/DboBuilder/ViewHandler/getExistsFilters.ts +74 -0
- package/lib/DboBuilder/ViewHandler/getInfo.ts +32 -0
- package/lib/DboBuilder/ViewHandler/getTableJoinQuery.ts +84 -0
- package/lib/DboBuilder/ViewHandler/parseComplexFilter.ts +96 -0
- package/lib/DboBuilder/ViewHandler/parseFieldFilter.ts +105 -0
- package/lib/DboBuilder/ViewHandler/parseJoinPath.ts +208 -0
- package/lib/DboBuilder/ViewHandler/prepareSortItems.ts +163 -0
- package/lib/DboBuilder/ViewHandler/prepareWhere.ts +90 -0
- package/lib/DboBuilder/ViewHandler/size.ts +37 -0
- package/lib/DboBuilder/ViewHandler/subscribe.ts +118 -0
- package/lib/DboBuilder/ViewHandler/validateViewRules.ts +70 -0
- package/lib/DboBuilder/dboBuilderUtils.ts +222 -0
- package/lib/DboBuilder/getColumns.ts +114 -0
- package/lib/DboBuilder/getCondition.ts +201 -0
- package/lib/DboBuilder/getSubscribeRelatedTables.ts +190 -0
- package/lib/DboBuilder/getTablesForSchemaPostgresSQL.ts +426 -0
- package/lib/DboBuilder/insertNestedRecords.ts +355 -0
- package/lib/DboBuilder/parseUpdateRules.ts +187 -0
- package/lib/DboBuilder/prepareShortestJoinPaths.ts +186 -0
- package/lib/DboBuilder/runSQL.ts +182 -0
- package/lib/DboBuilder/runTransaction.ts +50 -0
- package/lib/DboBuilder/sqlErrCodeToMsg.ts +254 -0
- package/lib/DboBuilder/uploadFile.ts +69 -0
- package/lib/Event_Trigger_Tags.ts +118 -0
- package/lib/FileManager/FileManager.ts +358 -0
- package/lib/FileManager/getValidatedFileType.ts +69 -0
- package/lib/FileManager/initFileManager.ts +187 -0
- package/lib/FileManager/upload.ts +62 -0
- package/lib/FileManager/uploadStream.ts +79 -0
- package/lib/Filtering.ts +463 -0
- package/lib/JSONBValidation/validate_jsonb_schema_sql.ts +502 -0
- package/lib/JSONBValidation/validation.ts +143 -0
- package/lib/Logging.ts +127 -0
- package/lib/PostgresNotifListenManager.ts +143 -0
- package/lib/Prostgles.ts +485 -0
- package/lib/ProstglesTypes.ts +196 -0
- package/lib/PubSubManager/PubSubManager.ts +609 -0
- package/lib/PubSubManager/addSub.ts +138 -0
- package/lib/PubSubManager/addSync.ts +141 -0
- package/lib/PubSubManager/getCreatePubSubManagerError.ts +72 -0
- package/lib/PubSubManager/getPubSubManagerInitQuery.ts +662 -0
- package/lib/PubSubManager/initPubSubManager.ts +79 -0
- package/lib/PubSubManager/notifListener.ts +173 -0
- package/lib/PubSubManager/orphanTriggerCheck.ts +70 -0
- package/lib/PubSubManager/pushSubData.ts +55 -0
- package/lib/PublishParser/PublishParser.ts +162 -0
- package/lib/PublishParser/getFileTableRules.ts +124 -0
- package/lib/PublishParser/getSchemaFromPublish.ts +141 -0
- package/lib/PublishParser/getTableRulesWithoutFileTable.ts +177 -0
- package/lib/PublishParser/publishTypesAndUtils.ts +399 -0
- package/lib/RestApi.ts +127 -0
- package/lib/SchemaWatch/SchemaWatch.ts +90 -0
- package/lib/SchemaWatch/createSchemaWatchEventTrigger.ts +3 -0
- package/lib/SchemaWatch/getValidatedWatchSchemaType.ts +45 -0
- package/lib/SchemaWatch/getWatchSchemaTagList.ts +27 -0
- package/lib/SyncReplication.ts +557 -0
- package/lib/TableConfig/TableConfig.ts +468 -0
- package/lib/TableConfig/getColumnDefinitionQuery.ts +111 -0
- package/lib/TableConfig/getConstraintDefinitionQueries.ts +95 -0
- package/lib/TableConfig/getFutureTableSchema.ts +64 -0
- package/lib/TableConfig/getPGIndexes.ts +53 -0
- package/lib/TableConfig/getTableColumnQueries.ts +129 -0
- package/lib/TableConfig/initTableConfig.ts +326 -0
- package/lib/index.ts +13 -0
- package/lib/initProstgles.ts +319 -0
- package/lib/onSocketConnected.ts +102 -0
- package/lib/runClientRequest.ts +129 -0
- package/lib/shortestPath.ts +122 -0
- package/lib/typeTests/DBoGenerated.d.ts +320 -0
- package/lib/typeTests/dboTypeCheck.ts +81 -0
- package/lib/utils.ts +15 -0
- package/package.json +1 -1
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import { asName as _asName, AnyObject, TableInfo, ALLOWED_EXTENSION, ALLOWED_CONTENT_TYPE, isObject, JSONB, ColumnInfo } from "prostgles-types";
|
|
2
|
+
import { isPlainObject, JoinInfo, LocalParams } from "../DboBuilder/DboBuilder";
|
|
3
|
+
import { DB, DBHandlerServer, Prostgles } from "../Prostgles";
|
|
4
|
+
import { InsertRule, ValidateRowArgs } from "../PublishParser/PublishParser";
|
|
5
|
+
import { TableHandler } from "../DboBuilder/TableHandler/TableHandler";
|
|
6
|
+
import { uploadFile } from "../DboBuilder/uploadFile";
|
|
7
|
+
import { initTableConfig } from "./initTableConfig";
|
|
8
|
+
|
|
9
|
+
type ColExtraInfo = {
|
|
10
|
+
min?: string | number;
|
|
11
|
+
max?: string | number;
|
|
12
|
+
hint?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type I18N_Config<LANG_IDS> = {
|
|
16
|
+
[lang_id in keyof LANG_IDS]: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const parseI18N = <LANG_IDS, Def extends string | undefined>(params: {
|
|
20
|
+
config?: I18N_Config<LANG_IDS> | string;
|
|
21
|
+
lang?: keyof LANG_IDS | string;
|
|
22
|
+
defaultLang: keyof LANG_IDS | string;
|
|
23
|
+
defaultValue: Def;
|
|
24
|
+
}): Def | string => {
|
|
25
|
+
const { config, lang, defaultLang, defaultValue } = params;
|
|
26
|
+
if(config){
|
|
27
|
+
if(isPlainObject(config)){
|
|
28
|
+
//@ts-ignore
|
|
29
|
+
return config[lang] ?? config[defaultLang];
|
|
30
|
+
} else if(typeof config === "string"){
|
|
31
|
+
return config;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return defaultValue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type BaseTableDefinition<LANG_IDS = AnyObject> = {
|
|
39
|
+
info?: {
|
|
40
|
+
label?: string | I18N_Config<LANG_IDS>;
|
|
41
|
+
}
|
|
42
|
+
dropIfExistsCascade?: boolean;
|
|
43
|
+
dropIfExists?: boolean;
|
|
44
|
+
hooks?: {
|
|
45
|
+
/**
|
|
46
|
+
* Hook used to run custom logic before inserting a row.
|
|
47
|
+
* The returned row must satisfy the table schema
|
|
48
|
+
*/
|
|
49
|
+
getPreInsertRow?: (args: GetPreInsertRowArgs) => Promise<{ row: AnyObject; onInserted: Promise<void>; }>;
|
|
50
|
+
};
|
|
51
|
+
triggers?: {
|
|
52
|
+
[triggerName: string]: {
|
|
53
|
+
/**
|
|
54
|
+
* Use "before" when you need to change the data before the action
|
|
55
|
+
*/
|
|
56
|
+
type: "before" | "after" | "instead of";
|
|
57
|
+
actions: ("insert" | "update" | "delete")[];
|
|
58
|
+
forEach: "statement" | "row";
|
|
59
|
+
/**
|
|
60
|
+
* @example
|
|
61
|
+
* DECLARE
|
|
62
|
+
x_rec record;
|
|
63
|
+
BEGIN
|
|
64
|
+
raise notice '=operation: % =', TG_OP;
|
|
65
|
+
IF (TG_OP = 'UPDATE' OR TG_OP = 'DELETE') THEN
|
|
66
|
+
FOR x_rec IN SELECT * FROM old_table LOOP
|
|
67
|
+
raise notice 'OLD: %', x_rec;
|
|
68
|
+
END loop;
|
|
69
|
+
END IF;
|
|
70
|
+
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
|
71
|
+
FOR x_rec IN SELECT * FROM new_table LOOP
|
|
72
|
+
raise notice 'NEW: %', x_rec;
|
|
73
|
+
END loop;
|
|
74
|
+
END IF;
|
|
75
|
+
|
|
76
|
+
RETURN NULL;
|
|
77
|
+
END;
|
|
78
|
+
*/
|
|
79
|
+
query: string;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
type LookupTableDefinition<LANG_IDS> = {
|
|
85
|
+
isLookupTable: {
|
|
86
|
+
values: {
|
|
87
|
+
[id_value: string]: {} | {
|
|
88
|
+
[lang_id in keyof LANG_IDS]: string
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export type BaseColumn<LANG_IDS> = {
|
|
95
|
+
/**
|
|
96
|
+
* Will add these values to .getColumns() result
|
|
97
|
+
*/
|
|
98
|
+
info?: ColExtraInfo;
|
|
99
|
+
|
|
100
|
+
label?: string | Partial<{ [lang_id in keyof LANG_IDS]: string; }>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
type SQLDefColumn = {
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Raw sql statement used in creating/adding column
|
|
107
|
+
*/
|
|
108
|
+
sqlDefinition?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export type BaseColumnTypes = {
|
|
112
|
+
defaultValue?: any;
|
|
113
|
+
nullable?: boolean;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
type TextColumn = BaseColumnTypes & {
|
|
117
|
+
isText: true;
|
|
118
|
+
/**
|
|
119
|
+
* Value will be trimmed before update/insert
|
|
120
|
+
*/
|
|
121
|
+
trimmed?: boolean;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Value will be lower cased before update/insert
|
|
125
|
+
*/
|
|
126
|
+
lowerCased?: boolean;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export type JSONBColumnDef = (BaseColumnTypes & {
|
|
130
|
+
/**
|
|
131
|
+
* If the new schema CHECK fails old rows the update old rows using this function
|
|
132
|
+
*/
|
|
133
|
+
// onMigrationFail?: <T>(failedRow: T) => AnyObject | Promise<AnyObject>;
|
|
134
|
+
}) & ({
|
|
135
|
+
jsonbSchema: JSONB.JSONBSchema;
|
|
136
|
+
jsonbSchemaType?: undefined;
|
|
137
|
+
} | {
|
|
138
|
+
jsonbSchema?: undefined;
|
|
139
|
+
jsonbSchemaType: JSONB.ObjectType["type"];
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Allows referencing media to this table.
|
|
144
|
+
* Requires this table to have a primary key AND a valid fileTable config
|
|
145
|
+
*/
|
|
146
|
+
type MediaColumn = ({
|
|
147
|
+
|
|
148
|
+
name: string;
|
|
149
|
+
label?: string;
|
|
150
|
+
files: "one" | "many";
|
|
151
|
+
} & (
|
|
152
|
+
{
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept
|
|
156
|
+
*/
|
|
157
|
+
allowedContentType?: Record<Partial<("audio/*" | "video/*" | "image/*" | "text/*" | ALLOWED_CONTENT_TYPE)>, 1>
|
|
158
|
+
} |
|
|
159
|
+
{
|
|
160
|
+
allowedExtensions?: Record<Partial<ALLOWED_EXTENSION>, 1>
|
|
161
|
+
}
|
|
162
|
+
));
|
|
163
|
+
|
|
164
|
+
type ReferencedColumn = {
|
|
165
|
+
/**
|
|
166
|
+
* Will create a lookup table that this column will reference
|
|
167
|
+
*/
|
|
168
|
+
references?: BaseColumnTypes & {
|
|
169
|
+
tableName: string;
|
|
170
|
+
/**
|
|
171
|
+
* Defaults to id
|
|
172
|
+
*/
|
|
173
|
+
columnName?: string;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
type JoinDef = {
|
|
178
|
+
sourceTable: string;
|
|
179
|
+
targetTable: string;
|
|
180
|
+
on: JoinInfo["paths"][number]["on"];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Used in specifying a join path to a table. This column name can then be used in select
|
|
185
|
+
*/
|
|
186
|
+
type NamedJoinColumn = {
|
|
187
|
+
label?: string;
|
|
188
|
+
joinDef: JoinDef[];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
type Enum<T extends string | number = any> = {
|
|
192
|
+
enum: T[] | readonly T[];
|
|
193
|
+
nullable?: boolean;
|
|
194
|
+
defaultValue?: T;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
export type ColumnConfig<LANG_IDS = { en: 1 }> = string | StrictUnion<NamedJoinColumn | MediaColumn | (BaseColumn<LANG_IDS> & (SQLDefColumn | ReferencedColumn | TextColumn | JSONBColumnDef | Enum))>;
|
|
198
|
+
|
|
199
|
+
export type ColumnConfigs<LANG_IDS = { en: 1 }> = {
|
|
200
|
+
sql: string | BaseColumn<LANG_IDS> & SQLDefColumn;
|
|
201
|
+
join: BaseColumn<LANG_IDS> & NamedJoinColumn;
|
|
202
|
+
media: BaseColumn<LANG_IDS> & MediaColumn;
|
|
203
|
+
referenced: BaseColumn<LANG_IDS> & ReferencedColumn;
|
|
204
|
+
text: BaseColumn<LANG_IDS> & TextColumn;
|
|
205
|
+
jsonb: BaseColumn<LANG_IDS> & JSONBColumnDef;
|
|
206
|
+
enum: BaseColumn<LANG_IDS> & Enum;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
type UnionKeys<T> = T extends T ? keyof T : never;
|
|
210
|
+
type StrictUnionHelper<T, TAll> = T extends any ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;
|
|
211
|
+
export type StrictUnion<T> = StrictUnionHelper<T, T>
|
|
212
|
+
|
|
213
|
+
export const CONSTRAINT_TYPES = ["PRIMARY KEY", "UNIQUE", "CHECK"] as const;
|
|
214
|
+
export type TableDefinition<LANG_IDS> = {
|
|
215
|
+
onMount?: (params: { dbo: DBHandlerServer; _db: DB }) => Promise<void | { onUnmount: () => void; }>;
|
|
216
|
+
columns?: {
|
|
217
|
+
[column_name: string]: ColumnConfig<LANG_IDS>
|
|
218
|
+
},
|
|
219
|
+
constraints?:
|
|
220
|
+
| string[]
|
|
221
|
+
| {
|
|
222
|
+
[constraint_name: string]:
|
|
223
|
+
| string
|
|
224
|
+
| {
|
|
225
|
+
type: typeof CONSTRAINT_TYPES[number];
|
|
226
|
+
dropIfExists?: boolean;
|
|
227
|
+
/**
|
|
228
|
+
* E.g.:
|
|
229
|
+
* colname
|
|
230
|
+
* col1, col2
|
|
231
|
+
* col1 > col3
|
|
232
|
+
*/
|
|
233
|
+
content: string;
|
|
234
|
+
}
|
|
235
|
+
// & ({
|
|
236
|
+
// }
|
|
237
|
+
// | {
|
|
238
|
+
// type: "FOREIGN KEY",
|
|
239
|
+
// columns: string[];
|
|
240
|
+
// ftable: string;
|
|
241
|
+
// fcols: string[];
|
|
242
|
+
// }
|
|
243
|
+
// )
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Similar to unique constraints but expressions are allowed inside definition
|
|
248
|
+
*/
|
|
249
|
+
replaceUniqueIndexes?: boolean;
|
|
250
|
+
indexes?: {
|
|
251
|
+
[index_name: string]: {
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* If true then will drop any existing index with this name
|
|
255
|
+
* Overrides replaceUniqueIndexes
|
|
256
|
+
*/
|
|
257
|
+
replace?: boolean;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Causes the system to check for duplicate values in the table when the index is created (if data already exist) and each time data is added.
|
|
261
|
+
* Attempts to insert or update data which would result in duplicate entries will generate an error.
|
|
262
|
+
*/
|
|
263
|
+
unique?: boolean;
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* When this option is used, PostgreSQL will build the index without taking any locks that prevent
|
|
267
|
+
* concurrent inserts, updates, or deletes on the table; whereas a standard index build locks out writes (but not reads) on the table until it's done.
|
|
268
|
+
* There are several caveats to be aware of when using this option — see Building Indexes Concurrently.
|
|
269
|
+
*/
|
|
270
|
+
concurrently?: boolean;
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Table name
|
|
274
|
+
*/
|
|
275
|
+
// on?: string;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Column list
|
|
279
|
+
* @example: col1, col2
|
|
280
|
+
*/
|
|
281
|
+
columns: string;
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Where clause without the "where"
|
|
285
|
+
* Used to create a partial index. A partial index is an index that contains entries for only a portion of a table
|
|
286
|
+
* Another possible application is to use WHERE with UNIQUE to enforce uniqueness over a subset of a table
|
|
287
|
+
*/
|
|
288
|
+
where?: string;
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* The name of the index method to be used.
|
|
292
|
+
* Choices are btree, hash, gist, and gin. The default method is btree.
|
|
293
|
+
*/
|
|
294
|
+
using?: "btree" | "hash" | "gist" | "gin";
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
type GetPreInsertRowArgs = Omit<ValidateRowArgs, "localParams"> & {
|
|
300
|
+
// preValidate: InsertRule["preValidate"];
|
|
301
|
+
validate: InsertRule["validate"];
|
|
302
|
+
localParams: LocalParams | undefined;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Helper utility to create lookup tables for TEXT columns
|
|
307
|
+
*/
|
|
308
|
+
export type TableConfig<LANG_IDS = { en: 1 }> = {
|
|
309
|
+
[table_name: string]: BaseTableDefinition<LANG_IDS> & (TableDefinition<LANG_IDS> | LookupTableDefinition<LANG_IDS>);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Will be run between initSQL and fileTable
|
|
314
|
+
*/
|
|
315
|
+
export default class TableConfigurator<LANG_IDS = { en: 1 }> {
|
|
316
|
+
|
|
317
|
+
instanceId = Date.now() + Math.random();
|
|
318
|
+
|
|
319
|
+
config: TableConfig<LANG_IDS> = {};
|
|
320
|
+
get dbo(): DBHandlerServer {
|
|
321
|
+
if (!this.prostgles.dbo) throw "this.prostgles.dbo missing"
|
|
322
|
+
return this.prostgles.dbo
|
|
323
|
+
}
|
|
324
|
+
get db(): DB {
|
|
325
|
+
if (!this.prostgles.db) throw "this.prostgles.db missing"
|
|
326
|
+
return this.prostgles.db
|
|
327
|
+
}
|
|
328
|
+
prostgles: Prostgles
|
|
329
|
+
|
|
330
|
+
constructor(prostgles: Prostgles) {
|
|
331
|
+
this.config = (prostgles.opts.tableConfig as any) ?? {};
|
|
332
|
+
this.prostgles = prostgles;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
destroy = async () => {
|
|
336
|
+
for await(const { onUnmount } of Object.values(this.tableOnMounts)){
|
|
337
|
+
try {
|
|
338
|
+
await onUnmount();
|
|
339
|
+
} catch (error) {
|
|
340
|
+
console.error(error);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
tableOnMounts: Record<string, { onUnmount: () => void; }> = {};
|
|
346
|
+
setTableOnMounts = async () => {
|
|
347
|
+
this.tableOnMounts = {};
|
|
348
|
+
for (const [tableName, tableConfig] of Object.entries(this.config)) {
|
|
349
|
+
if("onMount" in tableConfig && tableConfig.onMount){
|
|
350
|
+
const cleanup = await tableConfig.onMount({ dbo: this.dbo, _db: this.db });
|
|
351
|
+
if(cleanup){
|
|
352
|
+
this.tableOnMounts[tableName] = cleanup;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
getColumnConfig = (tableName: string, colName: string): ColumnConfig | undefined => {
|
|
359
|
+
const tconf = this.config?.[tableName];
|
|
360
|
+
if (tconf && "columns" in tconf) {
|
|
361
|
+
return tconf.columns?.[colName];
|
|
362
|
+
}
|
|
363
|
+
return undefined;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
getTableInfo = (params: { tableName: string; lang?: string }): TableInfo["info"] | undefined => {
|
|
367
|
+
const tconf = this.config?.[params.tableName];
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
label: parseI18N<LANG_IDS, string>({ config: tconf?.info?.label, lang: params.lang, defaultLang: "en", defaultValue: params.tableName })
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
getColInfo = (params: { col: string, table: string, lang?: string }): (ColExtraInfo & { label?: string; } & Pick<ColumnInfo, "jsonbSchema">) | undefined => {
|
|
375
|
+
const colConf = this.getColumnConfig(params.table, params.col);
|
|
376
|
+
let result: Partial<ReturnType<typeof this.getColInfo>> = undefined;
|
|
377
|
+
if (colConf) {
|
|
378
|
+
|
|
379
|
+
if (isObject(colConf)) {
|
|
380
|
+
const { jsonbSchema, jsonbSchemaType, info } = colConf;
|
|
381
|
+
result = {
|
|
382
|
+
...(result ?? {}),
|
|
383
|
+
...info,
|
|
384
|
+
...((jsonbSchema || jsonbSchemaType) && { jsonbSchema: { nullable: colConf.nullable, ...(jsonbSchema || { type: jsonbSchemaType }) } })
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Get labels from TableConfig if specified
|
|
389
|
+
*/
|
|
390
|
+
if (colConf.label) {
|
|
391
|
+
const { lang } = params;
|
|
392
|
+
const lbl = colConf?.label;
|
|
393
|
+
if (["string", "object"].includes(typeof lbl)) {
|
|
394
|
+
if (typeof lbl === "string") {
|
|
395
|
+
result ??= {};
|
|
396
|
+
result.label = lbl
|
|
397
|
+
} else if (lang && (lbl?.[lang as "en"] || lbl?.en)) {
|
|
398
|
+
result ??= {};
|
|
399
|
+
result.label = (lbl?.[lang as "en"]) || lbl?.en;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
return result;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
checkColVal = (params: { col: string, table: string, value: any }): void => {
|
|
414
|
+
const conf = this.getColInfo(params);
|
|
415
|
+
if (conf) {
|
|
416
|
+
const { value } = params;
|
|
417
|
+
const { min, max } = conf;
|
|
418
|
+
if (min !== undefined && value !== undefined && value < min) throw `${params.col} must be greater than ${min}`
|
|
419
|
+
if (max !== undefined && value !== undefined && value > max) throw `${params.col} must be less than ${max}`
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
getJoinInfo = (sourceTable: string, targetTable: string): JoinInfo | undefined => {
|
|
424
|
+
if (
|
|
425
|
+
this.config &&
|
|
426
|
+
sourceTable in this.config &&
|
|
427
|
+
this.config[sourceTable] &&
|
|
428
|
+
"columns" in this.config[sourceTable]!
|
|
429
|
+
) {
|
|
430
|
+
const td = this.config[sourceTable]!;
|
|
431
|
+
if ("columns" in td && td.columns?.[targetTable]) {
|
|
432
|
+
const cd = td.columns[targetTable];
|
|
433
|
+
if (isObject(cd) && "joinDef" in cd) {
|
|
434
|
+
if(!cd.joinDef) throw "cd.joinDef missing"
|
|
435
|
+
const { joinDef } = cd;
|
|
436
|
+
const res: JoinInfo = {
|
|
437
|
+
expectOne: false,
|
|
438
|
+
paths: joinDef.map(({ sourceTable, targetTable: table, on }) => ({
|
|
439
|
+
source: sourceTable,
|
|
440
|
+
target: targetTable,
|
|
441
|
+
table,
|
|
442
|
+
on
|
|
443
|
+
})),
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return res;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return undefined;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
getPreInsertRow = async (tableHandler: TableHandler, args: Pick<GetPreInsertRowArgs, "localParams" | "row" | "validate" | "dbx">): Promise<AnyObject> => {
|
|
454
|
+
const tableHook = this.config?.[tableHandler.name]?.hooks?.getPreInsertRow;
|
|
455
|
+
if(tableHandler.is_media){
|
|
456
|
+
return uploadFile.bind(tableHandler)(args) as Promise<AnyObject>;
|
|
457
|
+
}
|
|
458
|
+
if(tableHook){
|
|
459
|
+
return tableHook(args)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return args.row;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
prevInitQueryHistory?: string[];
|
|
466
|
+
initialising = false;
|
|
467
|
+
init = initTableConfig.bind(this);
|
|
468
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { asName, pickKeys } from "prostgles-types";
|
|
2
|
+
import { DB } from "../Prostgles";
|
|
3
|
+
import { asValue } from "../PubSubManager/PubSubManager";
|
|
4
|
+
import { VALIDATE_SCHEMA_FUNCNAME } from "../JSONBValidation/validate_jsonb_schema_sql";
|
|
5
|
+
import { BaseColumnTypes, ColumnConfig } from "./TableConfig";
|
|
6
|
+
import pgPromise from "pg-promise";
|
|
7
|
+
|
|
8
|
+
type Args = {
|
|
9
|
+
column: string;
|
|
10
|
+
colConf: ColumnConfig;
|
|
11
|
+
db: DB;
|
|
12
|
+
table: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Column create statement for a given config
|
|
17
|
+
*/
|
|
18
|
+
export const getColumnDefinitionQuery = async ({ colConf: colConfRaw, column, db, table }: Args): Promise<string | undefined> => {
|
|
19
|
+
const colConf = typeof colConfRaw === "string"? { sqlDefinition: colConfRaw } : colConfRaw;
|
|
20
|
+
const colNameEsc = asName(column);
|
|
21
|
+
const getColTypeDef = (colConf: BaseColumnTypes, pgType: "TEXT" | "JSONB") => {
|
|
22
|
+
const { nullable, defaultValue } = colConf;
|
|
23
|
+
return `${pgType} ${!nullable ? " NOT NULL " : ""} ${defaultValue ? ` DEFAULT ${asValue(defaultValue)} ` : ""}`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const jsonbSchema =
|
|
27
|
+
("jsonbSchema" in colConf && colConf.jsonbSchema) ? { jsonbSchema: colConf.jsonbSchema, jsonbSchemaType: undefined } :
|
|
28
|
+
("jsonbSchemaType" in colConf && colConf.jsonbSchemaType) ? { jsonbSchema: undefined, jsonbSchemaType: colConf.jsonbSchemaType } :
|
|
29
|
+
undefined;
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if ("references" in colConf && colConf.references) {
|
|
33
|
+
|
|
34
|
+
const { tableName: lookupTable, columnName: lookupCol = "id" } = colConf.references;
|
|
35
|
+
return ` ${colNameEsc} ${getColTypeDef(colConf.references, "TEXT")} REFERENCES ${lookupTable} (${lookupCol}) `;
|
|
36
|
+
|
|
37
|
+
} else if ("sqlDefinition" in colConf && colConf.sqlDefinition) {
|
|
38
|
+
|
|
39
|
+
return ` ${colNameEsc} ${colConf.sqlDefinition} `;
|
|
40
|
+
|
|
41
|
+
} else if ("isText" in colConf && colConf.isText) {
|
|
42
|
+
let checks = "";
|
|
43
|
+
const colChecks: string[] = [];
|
|
44
|
+
if (colConf.lowerCased) {
|
|
45
|
+
colChecks.push(`${colNameEsc} = LOWER(${colNameEsc})`)
|
|
46
|
+
}
|
|
47
|
+
if (colConf.trimmed) {
|
|
48
|
+
colChecks.push(`${colNameEsc} = BTRIM(${colNameEsc})`)
|
|
49
|
+
}
|
|
50
|
+
if (colChecks.length) {
|
|
51
|
+
checks = `CHECK (${colChecks.join(" AND ")})`
|
|
52
|
+
}
|
|
53
|
+
return ` ${colNameEsc} ${getColTypeDef(colConf, "TEXT")} ${checks}`;
|
|
54
|
+
|
|
55
|
+
} else if (jsonbSchema) {
|
|
56
|
+
|
|
57
|
+
const jsonbSchemaStr = asValue({
|
|
58
|
+
...pickKeys(colConf, ["enum", "nullable", "info"]),
|
|
59
|
+
...(jsonbSchema.jsonbSchemaType ? { type: jsonbSchema.jsonbSchemaType } : jsonbSchema.jsonbSchema)
|
|
60
|
+
}) + "::TEXT";
|
|
61
|
+
|
|
62
|
+
/** Validate default value against jsonbSchema */
|
|
63
|
+
const validationQuery = `SELECT ${VALIDATE_SCHEMA_FUNCNAME}(${jsonbSchemaStr}, ${asValue(colConf.defaultValue) + "::JSONB"}, ${asValue({ table, column })}) as v`;
|
|
64
|
+
if (colConf.defaultValue) {
|
|
65
|
+
|
|
66
|
+
const failedDefault = (err?: any) => {
|
|
67
|
+
return { msg: `Default value (${colConf.defaultValue}) for ${table}.${column} does not satisfy the jsonb constraint check: ${validationQuery}`, err };
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const row = await db.oneOrNone(validationQuery);
|
|
71
|
+
if (!row?.v) {
|
|
72
|
+
throw "Error";
|
|
73
|
+
}
|
|
74
|
+
} catch (e) {
|
|
75
|
+
throw failedDefault(e);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return ` ${colNameEsc} ${getColTypeDef(colConf, "JSONB")} CHECK(${VALIDATE_SCHEMA_FUNCNAME}(${jsonbSchemaStr}, ${colNameEsc}, ${asValue({ table, column })} ))`;
|
|
80
|
+
|
|
81
|
+
} else if ("enum" in colConf) {
|
|
82
|
+
if (!colConf.enum?.length) throw new Error("colConf.enum Must not be empty");
|
|
83
|
+
const type = colConf.enum.every(v => Number.isFinite(v)) ? "NUMERIC" : "TEXT";
|
|
84
|
+
const checks = colConf.enum.map(v => `${colNameEsc} = ${asValue(v)}`).join(" OR ");
|
|
85
|
+
return ` ${colNameEsc} ${type} ${colConf.nullable ? "" : "NOT NULL"} ${"defaultValue" in colConf ? ` DEFAULT ${asValue(colConf.defaultValue)}` : ""} CHECK(${checks})`;
|
|
86
|
+
|
|
87
|
+
} else {
|
|
88
|
+
return undefined;
|
|
89
|
+
// throw "Unknown column config: " + JSON.stringify(colConf);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
export type ColumnMinimalInfo = {
|
|
95
|
+
table_name: string;
|
|
96
|
+
table_schema: string;
|
|
97
|
+
column_name: string;
|
|
98
|
+
column_default: string | null;
|
|
99
|
+
udt_name: string;
|
|
100
|
+
nullable: boolean;
|
|
101
|
+
};
|
|
102
|
+
export const getTableColumns = ({ db, table }: { db: DB | pgPromise.ITask<{}>; table: string;}): Promise<ColumnMinimalInfo[]> => {
|
|
103
|
+
return db.manyOrNone(`
|
|
104
|
+
SELECT table_name,
|
|
105
|
+
table_schema, column_name,
|
|
106
|
+
column_default, udt_name,
|
|
107
|
+
is_nullable = 'YES' as nullable
|
|
108
|
+
FROM information_schema.columns
|
|
109
|
+
WHERE table_name = $1
|
|
110
|
+
`, [table]);
|
|
111
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import pgPromise from "pg-promise";
|
|
2
|
+
import { asName } from "prostgles-types";
|
|
3
|
+
import { DB } from "../Prostgles";
|
|
4
|
+
import { asValue } from "../PubSubManager/PubSubManager";
|
|
5
|
+
import { TableConfig } from "./TableConfig";
|
|
6
|
+
|
|
7
|
+
type Args = {
|
|
8
|
+
tableName: string;
|
|
9
|
+
tableConf: TableConfig[string]
|
|
10
|
+
// tableConf: BaseTableDefinition<LANG_IDS> & (TableDefinition<LANG_IDS> | LookupTableDefinition<LANG_IDS>)
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type ConstraintDef = {
|
|
14
|
+
/**
|
|
15
|
+
* Named constraints are used to show a relevant error message
|
|
16
|
+
*/
|
|
17
|
+
name?: string;
|
|
18
|
+
content: string;
|
|
19
|
+
alterQuery: string;
|
|
20
|
+
};
|
|
21
|
+
export const getConstraintDefinitionQueries = ({ tableConf, tableName }: Args): ConstraintDef[] | undefined => {
|
|
22
|
+
|
|
23
|
+
if ("constraints" in tableConf && tableConf.constraints) {
|
|
24
|
+
const { constraints } = tableConf;
|
|
25
|
+
if(!constraints){
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if(Array.isArray(constraints)) {
|
|
30
|
+
return constraints.map(c => ({ content: c, alterQuery: `ALTER TABLE ${asName(tableName)} ADD ${c}`}));
|
|
31
|
+
|
|
32
|
+
} else {
|
|
33
|
+
const constraintNames = Object.keys(constraints);
|
|
34
|
+
return constraintNames.map(constraintName => {
|
|
35
|
+
const _cnstr = constraints[constraintName]!;
|
|
36
|
+
const constraintDef = typeof _cnstr === "string"? _cnstr : `${_cnstr.type} (${_cnstr.content})`;
|
|
37
|
+
|
|
38
|
+
/** Drop constraints with the same name */
|
|
39
|
+
// const existingConstraint = constraints.some(c => c.conname === constraintName);
|
|
40
|
+
// if(existingConstraint){
|
|
41
|
+
// if(canDrop) queries.push(`ALTER TABLE ${asName(tableName)} DROP CONSTRAINT ${asName(constraintName)};`);
|
|
42
|
+
// }
|
|
43
|
+
|
|
44
|
+
const alterQuery = `ALTER TABLE ${asName(tableName)} ADD CONSTRAINT ${asName(constraintName)} ${constraintDef};`;
|
|
45
|
+
|
|
46
|
+
return { name: constraintName, alterQuery, content: constraintDef };
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type ColConstraint = {
|
|
53
|
+
name: string;
|
|
54
|
+
table: string;
|
|
55
|
+
type: "c" | "p" | "u" | "f";
|
|
56
|
+
cols: Array<string>;
|
|
57
|
+
definition: string;
|
|
58
|
+
schema: string;
|
|
59
|
+
}
|
|
60
|
+
type ColConstraintsArgs = {
|
|
61
|
+
db: DB | pgPromise.ITask<{}>;
|
|
62
|
+
table?: string;
|
|
63
|
+
column?: string;
|
|
64
|
+
types?: ColConstraint["type"][];
|
|
65
|
+
}
|
|
66
|
+
export const getColConstraintsQuery = ({ column, table, types }: Omit<ColConstraintsArgs, "db">) => {
|
|
67
|
+
let query = `
|
|
68
|
+
SELECT *
|
|
69
|
+
FROM (
|
|
70
|
+
SELECT distinct c.conname as name, c.contype as type,
|
|
71
|
+
pg_get_constraintdef(c.oid) as definition,
|
|
72
|
+
nsp.nspname as schema,
|
|
73
|
+
(SELECT r.relname from pg_class r where r.oid = c.conrelid) as "table",
|
|
74
|
+
(SELECT array_agg(attname::text) from pg_attribute
|
|
75
|
+
where attrelid = c.conrelid and ARRAY[attnum] <@ c.conkey) as cols
|
|
76
|
+
-- (SELECT array_agg(attname::text) from pg_attribute
|
|
77
|
+
-- where attrelid = c.confrelid and ARRAY[attnum] <@ c.confkey) as fcols,
|
|
78
|
+
-- (SELECT r.relname from pg_class r where r.oid = c.confrelid) as ftable
|
|
79
|
+
FROM pg_catalog.pg_constraint c
|
|
80
|
+
INNER JOIN pg_catalog.pg_class rel
|
|
81
|
+
ON rel.oid = c.conrelid
|
|
82
|
+
INNER JOIN pg_catalog.pg_namespace nsp
|
|
83
|
+
ON nsp.oid = connamespace
|
|
84
|
+
) t
|
|
85
|
+
WHERE TRUE
|
|
86
|
+
`;
|
|
87
|
+
if (table) query += `\nAND "table" = ${asValue(table)}`;
|
|
88
|
+
if (column) query += `\nAND cols @> ARRAY[${asValue(column)}]`;
|
|
89
|
+
if (types?.length) query += `\nAND type IN (${types.map(v => asValue(v)).join(", ")})`;
|
|
90
|
+
return query;
|
|
91
|
+
}
|
|
92
|
+
export const getColConstraints = ({ db, column, table, types }: ColConstraintsArgs ): Promise<ColConstraint[]> => {
|
|
93
|
+
|
|
94
|
+
return db.manyOrNone(getColConstraintsQuery({ column, table, types }));
|
|
95
|
+
}
|