prostgles-server 2.0.182 → 2.0.185

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.
@@ -1,63 +1,89 @@
1
- import { getKeys, asName } from "prostgles-types";
2
- import { JoinInfo } from "./DboBuilder";
1
+ import { getKeys, asName, AnyObject, TableInfo } from "prostgles-types";
2
+ import { isPlainObject, JoinInfo } from "./DboBuilder";
3
3
  import { ALLOWED_EXTENSION, ALLOWED_CONTENT_TYPE } from "./FileManager";
4
4
  import { DB, DBHandlerServer, Prostgles } from "./Prostgles";
5
5
  import { asValue } from "./PubSubManager";
6
6
 
7
7
  type ColExtraInfo = {
8
- min?: string | number;
9
- max?: string | number;
10
- hint?: string;
8
+ min?: string | number;
9
+ max?: string | number;
10
+ hint?: string;
11
11
  };
12
12
 
13
- type BaseTableDefinition = {
14
- dropIfExistsCascade?: boolean;
15
- dropIfExists?: boolean;
13
+ export type I18N_Config<LANG_IDS> = {
14
+ [lang_id in keyof LANG_IDS]: string;
15
+ }
16
+
17
+ export const parseI18N = <LANG_IDS, Def extends string | undefined>(params: {
18
+ config?: I18N_Config<LANG_IDS> | string;
19
+ lang?: keyof LANG_IDS | string;
20
+ defaultLang: keyof LANG_IDS | string;
21
+ defaultValue: Def;
22
+ }): Def | string => {
23
+ const { config, lang, defaultLang, defaultValue } = params;
24
+ if(config){
25
+ if(isPlainObject(config)){
26
+ //@ts-ignore
27
+ return config[lang] ?? config[defaultLang];
28
+ } else if(typeof config === "string"){
29
+ return config;
30
+ }
31
+ }
32
+
33
+ return defaultValue;
34
+ }
35
+
36
+ type BaseTableDefinition<LANG_IDS = AnyObject> = {
37
+ info?: {
38
+ label?: string | I18N_Config<LANG_IDS>;
39
+ }
40
+ dropIfExistsCascade?: boolean;
41
+ dropIfExists?: boolean;
16
42
  }
17
43
 
18
44
  type LookupTableDefinition<LANG_IDS> = {
19
- isLookupTable: {
20
- values: {
21
- [id_value: string]: {} | {
22
- [lang_id in keyof LANG_IDS]: string
23
- }
24
- }
45
+ isLookupTable: {
46
+ values: {
47
+ [id_value: string]: {} | {
48
+ [lang_id in keyof LANG_IDS]: string
49
+ }
25
50
  }
51
+ }
26
52
  }
27
53
 
28
54
  type BaseColumn<LANG_IDS> = {
29
- /**
30
- * Will add these values to .getColumns() result
31
- */
32
- info?: ColExtraInfo;
55
+ /**
56
+ * Will add these values to .getColumns() result
57
+ */
58
+ info?: ColExtraInfo;
33
59
 
34
- label?: string | Partial<{ [lang_id in keyof LANG_IDS]: string; }>;
60
+ label?: string | Partial<{ [lang_id in keyof LANG_IDS]: string; }>;
35
61
  }
36
62
 
37
63
  type SQLDefColumn = {
38
64
 
39
- /**
40
- * Raw sql statement used in creating/adding column
41
- */
42
- sqlDefinition?: string;
65
+ /**
66
+ * Raw sql statement used in creating/adding column
67
+ */
68
+ sqlDefinition?: string;
43
69
  }
44
70
 
45
71
  type TextColDef = {
46
- defaultValue?: string;
47
- nullable?: boolean;
72
+ defaultValue?: string;
73
+ nullable?: boolean;
48
74
  }
49
75
 
50
76
  type TextColumn = TextColDef & {
51
- isText: true;
52
- /**
53
- * Value will be trimmed before update/insert
54
- */
55
- trimmed?: boolean;
56
-
57
- /**
58
- * Value will be lower cased before update/insert
59
- */
60
- lowerCased?: boolean;
77
+ isText: true;
78
+ /**
79
+ * Value will be trimmed before update/insert
80
+ */
81
+ trimmed?: boolean;
82
+
83
+ /**
84
+ * Value will be lower cased before update/insert
85
+ */
86
+ lowerCased?: boolean;
61
87
  }
62
88
 
63
89
  /**
@@ -65,361 +91,371 @@ type TextColumn = TextColDef & {
65
91
  * Requires this table to have a primary key AND a valid fileTable config
66
92
  */
67
93
  type MediaColumn = ({
68
-
69
- name: string;
70
- label?: string;
71
- files: "one" | "many";
94
+
95
+ name: string;
96
+ label?: string;
97
+ files: "one" | "many";
72
98
  } & (
73
99
  {
74
100
 
75
- /**
76
- * https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept
77
- */
78
- allowedContentType?: Record<Partial<("audio/*" | "video/*" | "image/*" | "text/*" | ALLOWED_CONTENT_TYPE)>, 1>
101
+ /**
102
+ * https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept
103
+ */
104
+ allowedContentType?: Record<Partial<("audio/*" | "video/*" | "image/*" | "text/*" | ALLOWED_CONTENT_TYPE)>, 1>
79
105
  } |
80
- {
81
- allowedExtensions?: Record<Partial<ALLOWED_EXTENSION>, 1>
106
+ {
107
+ allowedExtensions?: Record<Partial<ALLOWED_EXTENSION>, 1>
82
108
  }
83
- ));
109
+ ));
84
110
 
85
111
  type ReferencedColumn = {
86
112
 
87
- /**
88
- * Will create a lookup table that this column will reference
89
- */
90
- references?: TextColDef & {
113
+ /**
114
+ * Will create a lookup table that this column will reference
115
+ */
116
+ references?: TextColDef & {
91
117
 
92
118
 
93
- tableName: string;
119
+ tableName: string;
94
120
 
95
- /**
96
- * Defaults to id
97
- */
98
- columnName?: string;
99
- }
121
+ /**
122
+ * Defaults to id
123
+ */
124
+ columnName?: string;
125
+ }
100
126
  }
101
127
 
102
128
  type JoinDef = {
103
- sourceTable: string;
104
- targetTable: string;
129
+ sourceTable: string;
130
+ targetTable: string;
105
131
 
106
- /**
107
- * E.g.: [sourceCol: string, targetCol: string][];
108
- */
109
- on: [string, string][];
132
+ /**
133
+ * E.g.: [sourceCol: string, targetCol: string][];
134
+ */
135
+ on: [string, string][];
110
136
  }
111
137
 
112
138
  /**
113
139
  * Used in specifying a join path to a table. This column name can then be used in select
114
140
  */
115
141
  type NamedJoinColumn = {
116
- label?: string;
117
- joinDef: JoinDef[];
142
+ label?: string;
143
+ joinDef: JoinDef[];
118
144
  }
119
145
 
120
146
  type ColumnConfig<LANG_IDS = { en: 1 }> = NamedJoinColumn | MediaColumn | (BaseColumn<LANG_IDS> & (SQLDefColumn | ReferencedColumn | TextColumn))
121
147
 
122
148
  type TableDefinition<LANG_IDS> = {
123
- columns: {
124
- [column_name: string]: ColumnConfig<LANG_IDS>
125
- },
126
- constraints?: {
127
- [constraint_name: string]: string
128
- },
129
-
130
- /**
131
- * Similar to unique constraints but expressions are allowed inside definition
132
- */
133
- replaceUniqueIndexes?: boolean;
134
- indexes?: {
135
- [index_name: string]: {
136
-
137
- /**
138
- * Overrides replaceUniqueIndexes
139
- */
140
- replace?: boolean;
141
-
142
- /**
143
- * 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.
144
- * Attempts to insert or update data which would result in duplicate entries will generate an error.
145
- */
146
- unique?: boolean;
147
-
148
- /**
149
- * When this option is used, PostgreSQL will build the index without taking any locks that prevent
150
- * 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.
151
- * There are several caveats to be aware of when using this option — see Building Indexes Concurrently.
152
- */
153
- concurrently?: boolean;
154
-
155
- /**
156
- * Table name
157
- */
158
- // on?: string;
159
-
160
- /**
161
- * Raw sql statement used excluding parentheses. e.g.: column_name
162
- */
163
- definition: string;
164
-
165
- /**
166
- * The name of the index method to be used.
167
- * Choices are btree, hash, gist, and gin. The default method is btree.
168
- */
169
- using?: "btree" | "hash" | "gist" | "gin"
170
- }
149
+ columns?: {
150
+ [column_name: string]: ColumnConfig<LANG_IDS>
151
+ },
152
+ constraints?: {
153
+ [constraint_name: string]: string
154
+ },
155
+
156
+ /**
157
+ * Similar to unique constraints but expressions are allowed inside definition
158
+ */
159
+ replaceUniqueIndexes?: boolean;
160
+ indexes?: {
161
+ [index_name: string]: {
162
+
163
+ /**
164
+ * Overrides replaceUniqueIndexes
165
+ */
166
+ replace?: boolean;
167
+
168
+ /**
169
+ * 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.
170
+ * Attempts to insert or update data which would result in duplicate entries will generate an error.
171
+ */
172
+ unique?: boolean;
173
+
174
+ /**
175
+ * When this option is used, PostgreSQL will build the index without taking any locks that prevent
176
+ * 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.
177
+ * There are several caveats to be aware of when using this option — see Building Indexes Concurrently.
178
+ */
179
+ concurrently?: boolean;
180
+
181
+ /**
182
+ * Table name
183
+ */
184
+ // on?: string;
185
+
186
+ /**
187
+ * Raw sql statement used excluding parentheses. e.g.: column_name
188
+ */
189
+ definition: string;
190
+
191
+ /**
192
+ * The name of the index method to be used.
193
+ * Choices are btree, hash, gist, and gin. The default method is btree.
194
+ */
195
+ using?: "btree" | "hash" | "gist" | "gin"
171
196
  }
197
+ }
172
198
  }
173
199
 
174
200
  /**
175
201
  * Helper utility to create lookup tables for TEXT columns
176
202
  */
177
203
  export type TableConfig<LANG_IDS = { en: 1 }> = {
178
- [table_name: string]: BaseTableDefinition & (TableDefinition<LANG_IDS> | LookupTableDefinition<LANG_IDS>);
204
+ [table_name: string]: BaseTableDefinition<LANG_IDS> & (TableDefinition<LANG_IDS> | LookupTableDefinition<LANG_IDS>);
179
205
  }
180
206
 
181
207
  /**
182
208
  * Will be run between initSQL and fileTable
183
209
  */
184
- export default class TableConfigurator {
185
-
186
- config?: TableConfig;
187
- get dbo(): DBHandlerServer {
188
- if(!this.prostgles.dbo) throw "this.prostgles.dbo missing"
189
- return this.prostgles.dbo
190
- };
191
- get db(): DB {
192
- if(!this.prostgles.db) throw "this.prostgles.db missing"
193
- return this.prostgles.db
194
- };
195
- // sidKeyName: string;
196
- prostgles: Prostgles
197
-
198
- constructor(prostgles: Prostgles){
199
- this.config = prostgles.opts.tableConfig;
200
- this.prostgles = prostgles;
210
+ export default class TableConfigurator<LANG_IDS = { en: 1 }> {
211
+
212
+ config?: TableConfig<LANG_IDS>;
213
+ get dbo(): DBHandlerServer {
214
+ if (!this.prostgles.dbo) throw "this.prostgles.dbo missing"
215
+ return this.prostgles.dbo
216
+ };
217
+ get db(): DB {
218
+ if (!this.prostgles.db) throw "this.prostgles.db missing"
219
+ return this.prostgles.db
220
+ };
221
+ // sidKeyName: string;
222
+ prostgles: Prostgles
223
+
224
+ constructor(prostgles: Prostgles) {
225
+ this.config = prostgles.opts.tableConfig as any;
226
+ this.prostgles = prostgles;
227
+ }
228
+
229
+ getColumnConfig = (tableName: string, colName: string): ColumnConfig | undefined => {
230
+ const tconf = this.config?.[tableName];
231
+ if (tconf && "columns" in tconf) {
232
+ return tconf.columns?.[colName];
201
233
  }
234
+ return undefined;
235
+ }
202
236
 
203
- getColumnConfig = (tableName: string, colName: string): ColumnConfig | undefined => {
204
- const tconf = this.config?.[tableName];
205
- if(tconf && "columns" in tconf){
206
- return tconf.columns[colName];
207
- }
208
- return undefined;
237
+ getTableInfo = (params: { tableName: string; lang?: string }): TableInfo["info"] | undefined => {
238
+ const tconf = this.config?.[params.tableName];
239
+
240
+ return {
241
+ label: parseI18N<LANG_IDS, string>({ config: tconf?.info?.label, lang: params.lang, defaultLang: "en", defaultValue: params.tableName })
209
242
  }
243
+ }
210
244
 
211
- getColInfo = (params: {col: string, table: string, lang?: string }): (ColExtraInfo & { label?: string }) | undefined => {
212
- const colConf = this.getColumnConfig(params.table, params.col);
213
- let result: (ColExtraInfo & { label?: string }) | undefined = undefined;
214
- if(colConf){
245
+ getColInfo = (params: { col: string, table: string, lang?: string }): (ColExtraInfo & { label?: string }) | undefined => {
246
+ const colConf = this.getColumnConfig(params.table, params.col);
247
+ let result: (ColExtraInfo & { label?: string }) | undefined = undefined;
248
+ if (colConf) {
215
249
 
216
- if("info" in colConf){
217
- result = {
218
- ...(result ?? {}),
219
- ...colConf?.info
220
- }
221
- }
222
-
223
- /**
224
- * Get labels from TableConfig if specified
225
- */
226
- if(colConf.label){
227
- const { lang } = params;
228
- const lbl = colConf?.label;
229
- if(["string", "object"].includes(typeof lbl)){
230
- if(typeof lbl === "string") {
231
- result ??= {};
232
- result.label = lbl
233
- } else if(lang && (lbl?.[lang as "en"] || lbl?.en)) {
234
- result ??= {};
235
- result.label = (lbl?.[lang as "en"]) || lbl?.en;
236
- }
237
- }
238
-
239
- }
250
+ if ("info" in colConf) {
251
+ result = {
252
+ ...(result ?? {}),
253
+ ...colConf?.info
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Get labels from TableConfig if specified
259
+ */
260
+ if (colConf.label) {
261
+ const { lang } = params;
262
+ const lbl = colConf?.label;
263
+ if (["string", "object"].includes(typeof lbl)) {
264
+ if (typeof lbl === "string") {
265
+ result ??= {};
266
+ result.label = lbl
267
+ } else if (lang && (lbl?.[lang as "en"] || lbl?.en)) {
268
+ result ??= {};
269
+ result.label = (lbl?.[lang as "en"]) || lbl?.en;
270
+ }
240
271
  }
241
272
 
242
-
243
- return result;
273
+ }
244
274
  }
245
275
 
246
- checkColVal = (params: {col: string, table: string, value: any }): void => {
247
- const conf = this.getColInfo(params);
248
- if(conf){
249
- const { value } = params;
250
- const { min, max } = conf;
251
- if(min !== undefined && value !== undefined && value < min) throw `${params.col} must be less than ${min}`
252
- if(max !== undefined && value !== undefined && value > max) throw `${params.col} must be greater than ${max}`
253
- }
254
- }
255
276
 
256
- getJoinInfo = (sourceTable: string, targetTable: string): JoinInfo | undefined => {
257
- if(
258
- this.config &&
259
- sourceTable in this.config &&
260
- this.config[sourceTable] &&
261
- "columns" in this.config[sourceTable]
262
- ){
263
- const td = this.config[sourceTable];
264
- if("columns" in td && td.columns[targetTable]){
265
- const cd = td.columns[targetTable];
266
- if("joinDef" in cd){
267
- const { joinDef } = cd;
268
- const res: JoinInfo = {
269
- expectOne: false,
270
- paths: joinDef.map(({ sourceTable, targetTable: table, on }) => ({
271
- source: sourceTable,
272
- target: targetTable,
273
- table,
274
- on
275
- })),
276
- }
277
-
278
- return res;
279
- }
280
- }
277
+ return result;
278
+ }
279
+
280
+ checkColVal = (params: { col: string, table: string, value: any }): void => {
281
+ const conf = this.getColInfo(params);
282
+ if (conf) {
283
+ const { value } = params;
284
+ const { min, max } = conf;
285
+ if (min !== undefined && value !== undefined && value < min) throw `${params.col} must be less than ${min}`
286
+ if (max !== undefined && value !== undefined && value > max) throw `${params.col} must be greater than ${max}`
287
+ }
288
+ }
289
+
290
+ getJoinInfo = (sourceTable: string, targetTable: string): JoinInfo | undefined => {
291
+ if (
292
+ this.config &&
293
+ sourceTable in this.config &&
294
+ this.config[sourceTable] &&
295
+ "columns" in this.config[sourceTable]
296
+ ) {
297
+ const td = this.config[sourceTable];
298
+ if ("columns" in td && td.columns?.[targetTable]) {
299
+ const cd = td.columns[targetTable];
300
+ if ("joinDef" in cd) {
301
+ const { joinDef } = cd;
302
+ const res: JoinInfo = {
303
+ expectOne: false,
304
+ paths: joinDef.map(({ sourceTable, targetTable: table, on }) => ({
305
+ source: sourceTable,
306
+ target: targetTable,
307
+ table,
308
+ on
309
+ })),
310
+ }
311
+
312
+ return res;
281
313
  }
282
- return undefined;
314
+ }
283
315
  }
284
-
285
- async init(){
286
- let queries: string[] = [];
287
-
288
- if(!this.config || !this.prostgles.pgp) throw "config or pgp missing"
289
- /* Create lookup tables */
290
- Object.keys(this.config).map(tableName => {
291
- const tableConf = this.config![tableName];
292
- const { dropIfExists = false, dropIfExistsCascade = false } = tableConf;
293
- if(dropIfExistsCascade){
294
- queries.push(`DROP TABLE IF EXISTS ${tableName} CASCADE;`);
295
- } else if(dropIfExists){
296
- queries.push(`DROP TABLE IF EXISTS ${tableName} ;`);
297
- }
298
- if("isLookupTable" in tableConf && Object.keys(tableConf.isLookupTable?.values).length){
299
- const rows = Object.keys(tableConf.isLookupTable?.values).map(id => ({ id, ...(tableConf.isLookupTable?.values[id]) }));
300
- if(dropIfExists || dropIfExistsCascade || !this.dbo?.[tableName]){
301
- const keys = Object.keys(rows[0]).filter(k => k !== "id");
302
- queries.push(`CREATE TABLE IF NOT EXISTS ${tableName} (
316
+ return undefined;
317
+ }
318
+
319
+ async init() {
320
+ let queries: string[] = [];
321
+
322
+ if (!this.config || !this.prostgles.pgp) throw "config or pgp missing"
323
+ /* Create lookup tables */
324
+ Object.keys(this.config).map(tableName => {
325
+ const tableConf = this.config![tableName];
326
+ const { dropIfExists = false, dropIfExistsCascade = false } = tableConf;
327
+ if (dropIfExistsCascade) {
328
+ queries.push(`DROP TABLE IF EXISTS ${tableName} CASCADE;`);
329
+ } else if (dropIfExists) {
330
+ queries.push(`DROP TABLE IF EXISTS ${tableName} ;`);
331
+ }
332
+ if ("isLookupTable" in tableConf && Object.keys(tableConf.isLookupTable?.values).length) {
333
+ const rows = Object.keys(tableConf.isLookupTable?.values).map(id => ({ id, ...(tableConf.isLookupTable?.values[id]) }));
334
+ if (dropIfExists || dropIfExistsCascade || !this.dbo?.[tableName]) {
335
+ const keys = Object.keys(rows[0]).filter(k => k !== "id");
336
+ queries.push(`CREATE TABLE IF NOT EXISTS ${tableName} (
303
337
  id TEXT PRIMARY KEY
304
- ${keys.length? (", " + keys.map(k => asName(k) + " TEXT ").join(", ")) : ""}
338
+ ${keys.length ? (", " + keys.map(k => asName(k) + " TEXT ").join(", ")) : ""}
305
339
  );`);
306
340
 
307
- rows.map(row => {
308
- const values = this.prostgles.pgp!.helpers.values(row)
309
- queries.push(this.prostgles.pgp!.as.format(`INSERT INTO ${tableName} (${["id", ...keys].map(t => asName(t)).join(", ")}) ` + " VALUES ${values:raw} ;", { values} ))
310
- });
311
- // console.log("Created lookup table " + tableName)
312
- }
341
+ rows.map(row => {
342
+ const values = this.prostgles.pgp!.helpers.values(row)
343
+ queries.push(this.prostgles.pgp!.as.format(`INSERT INTO ${tableName} (${["id", ...keys].map(t => asName(t)).join(", ")}) ` + " VALUES ${values:raw} ;", { values }))
344
+ });
345
+ // console.log("Created lookup table " + tableName)
346
+ }
347
+ }
348
+ });
349
+
350
+ if (queries.length) {
351
+ const q = queries.join("\n");
352
+ console.log("TableConfig: \n", q)
353
+ await this.db.multi(q);
354
+ await this.prostgles.refreshDBO()
355
+ }
356
+ queries = [];
357
+
358
+ /* Create referenced columns */
359
+ await Promise.all(Object.keys(this.config).map(async tableName => {
360
+ const tableConf = this.config![tableName];
361
+ if ("columns" in tableConf) {
362
+ const getColDef = (name: string, colConf: ColumnConfig): string => {
363
+ const colNameEsc = asName(name);
364
+ const getTextDef = (colConf: TextColDef) => {
365
+ const { nullable, defaultValue } = colConf;
366
+ return ` TEXT ${!nullable ? " NOT NULL " : ""} ${defaultValue ? ` DEFAULT ${asValue(defaultValue)} ` : ""}`
367
+ }
368
+ if ("references" in colConf && colConf.references) {
369
+
370
+ const { tableName: lookupTable, columnName: lookupCol = "id" } = colConf.references;
371
+ return ` ${colNameEsc} ${getTextDef(colConf.references)} REFERENCES ${lookupTable} (${lookupCol}) `;
372
+
373
+ } else if ("sqlDefinition" in colConf && colConf.sqlDefinition) {
374
+
375
+ return ` ${colNameEsc} ${colConf.sqlDefinition} `;
376
+
377
+ } else if ("isText" in colConf && colConf.isText) {
378
+ let checks = "", cArr = [];
379
+ if (colConf.lowerCased) {
380
+ cArr.push(`${colNameEsc} = LOWER(${colNameEsc})`)
313
381
  }
314
- });
315
-
316
- if(queries.length){
317
- const q = queries.join("\n");
318
- console.log("TableConfig: \n", q)
319
- await this.db.multi(q);
320
- await this.prostgles.refreshDBO()
382
+ if (colConf.trimmed) {
383
+ cArr.push(`${colNameEsc} = BTRIM(${colNameEsc})`)
384
+ }
385
+ if (cArr.length) {
386
+ checks = `CHECK (${cArr.join(" AND ")})`
387
+ }
388
+ return ` ${colNameEsc} ${getTextDef(colConf)} ${checks}`;
389
+ } else {
390
+ throw "Unknown column config: " + JSON.stringify(colConf);
391
+ }
321
392
  }
322
- queries = [];
323
-
324
- /* Create referenced columns */
325
- await Promise.all(Object.keys(this.config).map(async tableName => {
326
- const tableConf = this.config![tableName];
327
- if("columns" in tableConf){
328
- const getColDef = (name: string, colConf: ColumnConfig): string => {
329
- const colNameEsc = asName(name);
330
- const getTextDef = (colConf: TextColDef) => {
331
- const { nullable, defaultValue } = colConf;
332
- return ` TEXT ${!nullable? " NOT NULL " : ""} ${defaultValue? ` DEFAULT ${asValue(defaultValue)} ` : "" }`
333
- }
334
- if("references" in colConf && colConf.references){
335
-
336
- const { tableName: lookupTable, columnName: lookupCol = "id" } = colConf.references;
337
- return ` ${colNameEsc} ${getTextDef(colConf.references)} REFERENCES ${lookupTable} (${lookupCol}) `;
338
-
339
- } else if("sqlDefinition" in colConf && colConf.sqlDefinition){
340
-
341
- return ` ${colNameEsc} ${colConf.sqlDefinition} `;
342
-
343
- } else if("isText" in colConf && colConf.isText){
344
- let checks = "", cArr = [];
345
- if(colConf.lowerCased){
346
- cArr.push(`${colNameEsc} = LOWER(${colNameEsc})`)
347
- }
348
- if(colConf.trimmed){
349
- cArr.push(`${colNameEsc} = BTRIM(${colNameEsc})`)
350
- }
351
- if(cArr.length){
352
- checks = `CHECK (${cArr.join(" AND ")})`
353
- }
354
- return ` ${colNameEsc} ${getTextDef(colConf)} ${checks}`;
355
- } else {
356
- throw "Unknown column config: " + JSON.stringify(colConf);
357
- }
358
- }
359
-
360
- const colDefs: string[] = [];
361
- Object.keys(tableConf.columns).filter(c => !("joinDef" in tableConf.columns[c])).map(colName => {
362
- const colConf = tableConf.columns[colName];
363
-
364
- if(!this.dbo[tableName]){
365
- colDefs.push(getColDef(colName, colConf))
366
- } else if(!colDefs.length && !this.dbo[tableName].columns?.find(c => colName === c.name)) {
367
-
368
- if("references" in colConf && colConf.references){
369
-
370
- const { tableName: lookupTable, } = colConf.references;
371
- queries.push(`
372
- ALTER TABLE ${asName(tableName)}
373
- ADD COLUMN ${getColDef(colName, colConf)};
374
- `)
375
- console.log(`TableConfigurator: ${tableName}(${colName})` + " referenced lookup table " + lookupTable);
376
393
 
377
- } else if("sqlDefinition" in colConf && colConf.sqlDefinition){
378
-
379
- queries.push(`
394
+ const colDefs: string[] = [];
395
+ if (tableConf.columns) {
396
+ getKeys(tableConf?.columns).filter(c => !("joinDef" in tableConf.columns![c])).map(colName => {
397
+ const colConf = tableConf.columns![colName];
398
+
399
+ if (!this.dbo[tableName]) {
400
+ colDefs.push(getColDef(colName, colConf))
401
+ } else if (!colDefs.length && !this.dbo[tableName].columns?.find(c => colName === c.name)) {
402
+
403
+ if ("references" in colConf && colConf.references) {
404
+
405
+ const { tableName: lookupTable, } = colConf.references;
406
+ queries.push(`
407
+ ALTER TABLE ${asName(tableName)}
408
+ ADD COLUMN ${getColDef(colName, colConf)};
409
+ `)
410
+ console.log(`TableConfigurator: ${tableName}(${colName})` + " referenced lookup table " + lookupTable);
411
+
412
+ } else if ("sqlDefinition" in colConf && colConf.sqlDefinition) {
413
+
414
+ queries.push(`
380
415
  ALTER TABLE ${asName(tableName)}
381
416
  ADD COLUMN ${getColDef(colName, colConf)};
382
417
  `)
383
- console.log(`TableConfigurator: created/added column ${tableName}(${colName}) ` + colConf.sqlDefinition)
384
- }
385
- }
386
- });
418
+ console.log(`TableConfigurator: created/added column ${tableName}(${colName}) ` + colConf.sqlDefinition)
419
+ }
420
+ }
421
+ });
422
+ }
387
423
 
388
- if(colDefs.length){
389
- queries.push(`CREATE TABLE ${asName(tableName)} (
424
+ if (colDefs.length) {
425
+ queries.push(`CREATE TABLE ${asName(tableName)} (
390
426
  ${colDefs.join(", \n")}
391
427
  );`)
392
- console.error("TableConfigurator: Created table: \n" + queries[0])
393
- }
394
- }
395
- if("constraints" in tableConf && tableConf.constraints){
396
- getKeys(tableConf.constraints).map(constraintName => {
397
- queries.push(`ALTER TABLE ${asName(tableName)} ADD CONSTRAINT ${asName(constraintName)} ${tableConf.constraints![constraintName]} ;`);
398
- });
399
- }
400
- if("indexes" in tableConf && tableConf.indexes){
401
- getKeys(tableConf.indexes).map(indexName => {
402
- const { concurrently, unique, using, definition, replace } = tableConf.indexes![indexName];
403
- if(replace || typeof replace !== "boolean" && tableConf.replaceUniqueIndexes){
404
- queries.push(`DROP INDEX IF EXISTS ${asName(indexName)} ;`);
405
- }
406
- queries.push(`CREATE ${unique? "UNIQUE" : ""} ${!concurrently? "" : "CONCURRENTLY"} INDEX ${asName(indexName)} ON ${asName(tableName)} ${!using? "" : ("USING " + using)} (${definition}) ;`);
407
- });
408
- }
409
- }));
410
-
411
- if(queries.length){
412
- const q = queries.join("\n");
413
- console.log("TableConfig: \n", q)
414
- await this.db.multi(q);
428
+ console.error("TableConfigurator: Created table: \n" + queries[0])
415
429
  }
430
+ }
431
+ if ("constraints" in tableConf && tableConf.constraints) {
432
+ getKeys(tableConf.constraints).map(constraintName => {
433
+ queries.push(`ALTER TABLE ${asName(tableName)} ADD CONSTRAINT ${asName(constraintName)} ${tableConf.constraints![constraintName]} ;`);
434
+ });
435
+ }
436
+ if ("indexes" in tableConf && tableConf.indexes) {
437
+ getKeys(tableConf.indexes).map(indexName => {
438
+ const { concurrently, unique, using, definition, replace } = tableConf.indexes![indexName];
439
+ if (replace || typeof replace !== "boolean" && tableConf.replaceUniqueIndexes) {
440
+ queries.push(`DROP INDEX IF EXISTS ${asName(indexName)} ;`);
441
+ }
442
+ queries.push(`CREATE ${unique ? "UNIQUE" : ""} ${!concurrently ? "" : "CONCURRENTLY"} INDEX ${asName(indexName)} ON ${asName(tableName)} ${!using ? "" : ("USING " + using)} (${definition}) ;`);
443
+ });
444
+ }
445
+ }));
446
+
447
+ if (queries.length) {
448
+ const q = queries.join("\n");
449
+ console.log("TableConfig: \n", q)
450
+ await this.db.multi(q);
416
451
  }
452
+ }
417
453
  }
418
454
 
419
455
 
420
- async function columnExists(args: {tableName: string; colName: string; db: DB }){
421
- const { db, tableName, colName } = args;
422
- return Boolean((await db.oneOrNone(`
456
+ async function columnExists(args: { tableName: string; colName: string; db: DB }) {
457
+ const { db, tableName, colName } = args;
458
+ return Boolean((await db.oneOrNone(`
423
459
  SELECT column_name, table_name
424
460
  FROM information_schema.columns
425
461
  WHERE table_name=${asValue(tableName)} and column_name=${asValue(colName)}