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.
Files changed (107) hide show
  1. package/dist/Auth/setEmailProvider.js +2 -2
  2. package/dist/Auth/setEmailProvider.js.map +1 -1
  3. package/lib/Auth/AuthHandler.ts +436 -0
  4. package/lib/Auth/AuthTypes.ts +280 -0
  5. package/lib/Auth/getSafeReturnURL.ts +35 -0
  6. package/lib/Auth/sendEmail.ts +83 -0
  7. package/lib/Auth/setAuthProviders.ts +128 -0
  8. package/lib/Auth/setEmailProvider.ts +85 -0
  9. package/lib/Auth/setupAuthRoutes.ts +161 -0
  10. package/lib/DBEventsManager.ts +178 -0
  11. package/lib/DBSchemaBuilder.ts +225 -0
  12. package/lib/DboBuilder/DboBuilder.ts +319 -0
  13. package/lib/DboBuilder/DboBuilderTypes.ts +361 -0
  14. package/lib/DboBuilder/QueryBuilder/Functions.ts +1153 -0
  15. package/lib/DboBuilder/QueryBuilder/QueryBuilder.ts +288 -0
  16. package/lib/DboBuilder/QueryBuilder/getJoinQuery.ts +263 -0
  17. package/lib/DboBuilder/QueryBuilder/getNewQuery.ts +271 -0
  18. package/lib/DboBuilder/QueryBuilder/getSelectQuery.ts +136 -0
  19. package/lib/DboBuilder/QueryBuilder/prepareHaving.ts +22 -0
  20. package/lib/DboBuilder/QueryStreamer.ts +250 -0
  21. package/lib/DboBuilder/TableHandler/DataValidator.ts +428 -0
  22. package/lib/DboBuilder/TableHandler/TableHandler.ts +205 -0
  23. package/lib/DboBuilder/TableHandler/delete.ts +115 -0
  24. package/lib/DboBuilder/TableHandler/insert.ts +183 -0
  25. package/lib/DboBuilder/TableHandler/insertTest.ts +78 -0
  26. package/lib/DboBuilder/TableHandler/onDeleteFromFileTable.ts +62 -0
  27. package/lib/DboBuilder/TableHandler/runInsertUpdateQuery.ts +134 -0
  28. package/lib/DboBuilder/TableHandler/update.ts +126 -0
  29. package/lib/DboBuilder/TableHandler/updateBatch.ts +49 -0
  30. package/lib/DboBuilder/TableHandler/updateFile.ts +48 -0
  31. package/lib/DboBuilder/TableHandler/upsert.ts +34 -0
  32. package/lib/DboBuilder/ViewHandler/ViewHandler.ts +393 -0
  33. package/lib/DboBuilder/ViewHandler/count.ts +38 -0
  34. package/lib/DboBuilder/ViewHandler/find.ts +153 -0
  35. package/lib/DboBuilder/ViewHandler/getExistsCondition.ts +73 -0
  36. package/lib/DboBuilder/ViewHandler/getExistsFilters.ts +74 -0
  37. package/lib/DboBuilder/ViewHandler/getInfo.ts +32 -0
  38. package/lib/DboBuilder/ViewHandler/getTableJoinQuery.ts +84 -0
  39. package/lib/DboBuilder/ViewHandler/parseComplexFilter.ts +96 -0
  40. package/lib/DboBuilder/ViewHandler/parseFieldFilter.ts +105 -0
  41. package/lib/DboBuilder/ViewHandler/parseJoinPath.ts +208 -0
  42. package/lib/DboBuilder/ViewHandler/prepareSortItems.ts +163 -0
  43. package/lib/DboBuilder/ViewHandler/prepareWhere.ts +90 -0
  44. package/lib/DboBuilder/ViewHandler/size.ts +37 -0
  45. package/lib/DboBuilder/ViewHandler/subscribe.ts +118 -0
  46. package/lib/DboBuilder/ViewHandler/validateViewRules.ts +70 -0
  47. package/lib/DboBuilder/dboBuilderUtils.ts +222 -0
  48. package/lib/DboBuilder/getColumns.ts +114 -0
  49. package/lib/DboBuilder/getCondition.ts +201 -0
  50. package/lib/DboBuilder/getSubscribeRelatedTables.ts +190 -0
  51. package/lib/DboBuilder/getTablesForSchemaPostgresSQL.ts +426 -0
  52. package/lib/DboBuilder/insertNestedRecords.ts +355 -0
  53. package/lib/DboBuilder/parseUpdateRules.ts +187 -0
  54. package/lib/DboBuilder/prepareShortestJoinPaths.ts +186 -0
  55. package/lib/DboBuilder/runSQL.ts +182 -0
  56. package/lib/DboBuilder/runTransaction.ts +50 -0
  57. package/lib/DboBuilder/sqlErrCodeToMsg.ts +254 -0
  58. package/lib/DboBuilder/uploadFile.ts +69 -0
  59. package/lib/Event_Trigger_Tags.ts +118 -0
  60. package/lib/FileManager/FileManager.ts +358 -0
  61. package/lib/FileManager/getValidatedFileType.ts +69 -0
  62. package/lib/FileManager/initFileManager.ts +187 -0
  63. package/lib/FileManager/upload.ts +62 -0
  64. package/lib/FileManager/uploadStream.ts +79 -0
  65. package/lib/Filtering.ts +463 -0
  66. package/lib/JSONBValidation/validate_jsonb_schema_sql.ts +502 -0
  67. package/lib/JSONBValidation/validation.ts +143 -0
  68. package/lib/Logging.ts +127 -0
  69. package/lib/PostgresNotifListenManager.ts +143 -0
  70. package/lib/Prostgles.ts +485 -0
  71. package/lib/ProstglesTypes.ts +196 -0
  72. package/lib/PubSubManager/PubSubManager.ts +609 -0
  73. package/lib/PubSubManager/addSub.ts +138 -0
  74. package/lib/PubSubManager/addSync.ts +141 -0
  75. package/lib/PubSubManager/getCreatePubSubManagerError.ts +72 -0
  76. package/lib/PubSubManager/getPubSubManagerInitQuery.ts +662 -0
  77. package/lib/PubSubManager/initPubSubManager.ts +79 -0
  78. package/lib/PubSubManager/notifListener.ts +173 -0
  79. package/lib/PubSubManager/orphanTriggerCheck.ts +70 -0
  80. package/lib/PubSubManager/pushSubData.ts +55 -0
  81. package/lib/PublishParser/PublishParser.ts +162 -0
  82. package/lib/PublishParser/getFileTableRules.ts +124 -0
  83. package/lib/PublishParser/getSchemaFromPublish.ts +141 -0
  84. package/lib/PublishParser/getTableRulesWithoutFileTable.ts +177 -0
  85. package/lib/PublishParser/publishTypesAndUtils.ts +399 -0
  86. package/lib/RestApi.ts +127 -0
  87. package/lib/SchemaWatch/SchemaWatch.ts +90 -0
  88. package/lib/SchemaWatch/createSchemaWatchEventTrigger.ts +3 -0
  89. package/lib/SchemaWatch/getValidatedWatchSchemaType.ts +45 -0
  90. package/lib/SchemaWatch/getWatchSchemaTagList.ts +27 -0
  91. package/lib/SyncReplication.ts +557 -0
  92. package/lib/TableConfig/TableConfig.ts +468 -0
  93. package/lib/TableConfig/getColumnDefinitionQuery.ts +111 -0
  94. package/lib/TableConfig/getConstraintDefinitionQueries.ts +95 -0
  95. package/lib/TableConfig/getFutureTableSchema.ts +64 -0
  96. package/lib/TableConfig/getPGIndexes.ts +53 -0
  97. package/lib/TableConfig/getTableColumnQueries.ts +129 -0
  98. package/lib/TableConfig/initTableConfig.ts +326 -0
  99. package/lib/index.ts +13 -0
  100. package/lib/initProstgles.ts +319 -0
  101. package/lib/onSocketConnected.ts +102 -0
  102. package/lib/runClientRequest.ts +129 -0
  103. package/lib/shortestPath.ts +122 -0
  104. package/lib/typeTests/DBoGenerated.d.ts +320 -0
  105. package/lib/typeTests/dboTypeCheck.ts +81 -0
  106. package/lib/utils.ts +15 -0
  107. 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
+ }