prostgles-server 2.0.187 → 2.0.188

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 (75) hide show
  1. package/dist/DboBuilder/insert.d.ts +5 -0
  2. package/dist/DboBuilder/insert.d.ts.map +1 -0
  3. package/dist/DboBuilder/insert.js +129 -0
  4. package/dist/DboBuilder/insert.js.map +1 -0
  5. package/dist/DboBuilder/insertDataParse.d.ts +11 -0
  6. package/dist/DboBuilder/insertDataParse.d.ts.map +1 -0
  7. package/dist/DboBuilder/insertDataParse.js +283 -0
  8. package/dist/DboBuilder/insertDataParse.js.map +1 -0
  9. package/dist/DboBuilder.d.ts +10 -5
  10. package/dist/DboBuilder.d.ts.map +1 -1
  11. package/dist/DboBuilder.js +105 -365
  12. package/dist/DboBuilder.js.map +1 -1
  13. package/dist/FileManager.d.ts.map +1 -1
  14. package/dist/FileManager.js +17 -12
  15. package/dist/FileManager.js.map +1 -1
  16. package/dist/Prostgles.d.ts +1 -1
  17. package/dist/Prostgles.d.ts.map +1 -1
  18. package/dist/Prostgles.js.map +1 -1
  19. package/dist/QueryBuilder.d.ts.map +1 -1
  20. package/dist/QueryBuilder.js +7 -2
  21. package/dist/QueryBuilder.js.map +1 -1
  22. package/dist/TableConfig.d.ts +1 -4
  23. package/dist/TableConfig.d.ts.map +1 -1
  24. package/dist/TableConfig.js.map +1 -1
  25. package/lib/DboBuilder/insert.d.ts +5 -0
  26. package/lib/DboBuilder/insert.d.ts.map +1 -0
  27. package/lib/DboBuilder/insert.js +128 -0
  28. package/lib/DboBuilder/insert.ts +138 -0
  29. package/lib/DboBuilder/insertDataParse.d.ts +11 -0
  30. package/lib/DboBuilder/insertDataParse.d.ts.map +1 -0
  31. package/lib/DboBuilder/insertDataParse.js +282 -0
  32. package/lib/DboBuilder/insertDataParse.ts +355 -0
  33. package/lib/DboBuilder.d.ts +10 -5
  34. package/lib/DboBuilder.d.ts.map +1 -1
  35. package/lib/DboBuilder.js +105 -365
  36. package/lib/DboBuilder.ts +121 -440
  37. package/lib/FileManager.d.ts.map +1 -1
  38. package/lib/FileManager.js +17 -12
  39. package/lib/FileManager.ts +18 -13
  40. package/lib/Prostgles.d.ts +1 -1
  41. package/lib/Prostgles.d.ts.map +1 -1
  42. package/lib/Prostgles.ts +664 -652
  43. package/lib/QueryBuilder.d.ts.map +1 -1
  44. package/lib/QueryBuilder.js +7 -2
  45. package/lib/QueryBuilder.ts +12 -7
  46. package/lib/TableConfig.d.ts +1 -4
  47. package/lib/TableConfig.d.ts.map +1 -1
  48. package/lib/TableConfig.ts +2 -6
  49. package/package.json +2 -2
  50. package/tests/client/PID.txt +1 -1
  51. package/tests/client/tsconfig.json +1 -1
  52. package/tests/client_only_queries.d.ts +1 -1
  53. package/tests/client_only_queries.d.ts.map +1 -1
  54. package/tests/client_only_queries.ts +1 -1
  55. package/tests/isomorphic_queries.d.ts +1 -1
  56. package/tests/isomorphic_queries.d.ts.map +1 -1
  57. package/tests/isomorphic_queries.js +48 -1
  58. package/tests/isomorphic_queries.ts +65 -4
  59. package/tests/manual_test/DBoGenerated.d.ts +398 -0
  60. package/tests/manual_test/index.d.ts +2 -0
  61. package/tests/manual_test/index.d.ts.map +1 -0
  62. package/tests/{config_test2 → manual_test}/index.html +14 -23
  63. package/tests/{config_test2 → manual_test}/index.js +21 -15
  64. package/tests/{config_test2 → manual_test}/index.ts +22 -17
  65. package/tests/{config_test2 → manual_test}/init.sql +36 -5
  66. package/tests/manual_test/package-lock.json +2483 -0
  67. package/tests/{config_test2 → manual_test}/package.json +6 -7
  68. package/tests/manual_test/tsconfig.json +21 -0
  69. package/tests/server/DBoGenerated.d.ts +70 -0
  70. package/tests/server/index.js +29 -2
  71. package/tests/server/index.ts +30 -4
  72. package/tests/server/init.sql +25 -0
  73. package/tests/server/package-lock.json +3 -3
  74. package/tests/config_test2/DBoGenerated.d.ts +0 -135
  75. package/tests/config_test2/tsconfig.json +0 -21
package/lib/Prostgles.ts CHANGED
@@ -30,70 +30,70 @@ type DbConnection = string | pg.IConnectionParameters<pg.IClient>;
30
30
  type DbConnectionOpts = pg.IDefaults;
31
31
  export const TABLE_METHODS = ["update", "find", "findOne", "insert", "delete", "upsert"] as const;
32
32
  function getDbConnection(dbConnection: DbConnection, options: DbConnectionOpts | undefined, debugQueries = false, onNotice: ProstglesInitOptions["onNotice"]): { db: DB, pgp: PGP } {
33
- let pgp: PGP = pgPromise({
34
-
35
- promiseLib: promise,
36
- ...(debugQueries? {
37
- query: function (e) {
38
- console.log({psql: e.query, params: e.params});
39
- },
40
- } : {}),
41
- ...((onNotice || debugQueries)? {
42
- connect: function (client, dc, isFresh) {
43
- if (isFresh && !client.listeners('notice').length) {
44
- client.on('notice', function (msg) {
45
- if(onNotice){
46
- onNotice(msg, get(msg, "message"));
47
- } else {
48
- console.log("notice: %j", get(msg, "message"));
49
- }
50
- });
51
- }
52
- if (isFresh && !client.listeners('error').length) {
53
- client.on('error', function (msg) {
54
- if(onNotice){
55
- onNotice(msg, get(msg, "message"));
56
- } else {
57
- console.log("error: %j", get(msg, "message"));
58
- }
59
- });
60
- }
61
- },
62
- } : {})
63
- });
64
- pgp.pg.defaults.max = 70;
33
+ let pgp: PGP = pgPromise({
34
+
35
+ promiseLib: promise,
36
+ ...(debugQueries ? {
37
+ query: function (e) {
38
+ console.log({ psql: e.query, params: e.params });
39
+ },
40
+ } : {}),
41
+ ...((onNotice || debugQueries) ? {
42
+ connect: function (client, dc, isFresh) {
43
+ if (isFresh && !client.listeners('notice').length) {
44
+ client.on('notice', function (msg) {
45
+ if (onNotice) {
46
+ onNotice(msg, get(msg, "message"));
47
+ } else {
48
+ console.log("notice: %j", get(msg, "message"));
49
+ }
50
+ });
51
+ }
52
+ if (isFresh && !client.listeners('error').length) {
53
+ client.on('error', function (msg) {
54
+ if (onNotice) {
55
+ onNotice(msg, get(msg, "message"));
56
+ } else {
57
+ console.log("error: %j", get(msg, "message"));
58
+ }
59
+ });
60
+ }
61
+ },
62
+ } : {})
63
+ });
64
+ pgp.pg.defaults.max = 70;
65
65
 
66
- // /* Casts count/sum/max to bigint. Needs rework to remove casting "+count" and other issues; */
67
- // pgp.pg.types.setTypeParser(20, BigInt);
66
+ // /* Casts count/sum/max to bigint. Needs rework to remove casting "+count" and other issues; */
67
+ // pgp.pg.types.setTypeParser(20, BigInt);
68
68
 
69
- if(options){
70
- Object.assign(pgp.pg.defaults, options);
71
- }
72
-
73
- return {
74
- db: pgp(dbConnection),
75
- pgp
76
- };
69
+ if (options) {
70
+ Object.assign(pgp.pg.defaults, options);
71
+ }
72
+
73
+ return {
74
+ db: pgp(dbConnection),
75
+ pgp
76
+ };
77
77
  }
78
78
 
79
79
  export const JOIN_TYPES = ["one-many", "many-one", "one-one", "many-many"] as const;
80
80
  export type Join = {
81
- tables: [string, string];
82
- on: { [key: string]: string };
83
- type: typeof JOIN_TYPES[number];
81
+ tables: [string, string];
82
+ on: { [key: string]: string }[]; // Allow multi references to table
83
+ type: typeof JOIN_TYPES[number];
84
84
  };
85
85
  export type Joins = Join[] | "inferred";
86
86
 
87
87
 
88
88
 
89
89
  type Keywords = {
90
- $and: string;
91
- $or: string;
92
- $not: string;
90
+ $and: string;
91
+ $or: string;
92
+ $not: string;
93
93
  };
94
94
 
95
95
  export type DeepPartial<T> = {
96
- [P in keyof T]?: DeepPartial<T[P]>;
96
+ [P in keyof T]?: DeepPartial<T[P]>;
97
97
  };
98
98
  // export type I18N_CONFIG<LANG_IDS = { en: 1, fr: 1 }> = {
99
99
  // fallbackLang: keyof LANG_IDS;
@@ -107,23 +107,23 @@ export type DeepPartial<T> = {
107
107
  // }
108
108
 
109
109
  type ExpressApp = {
110
- get: (
111
- routePath: string,
112
- cb: (
113
- req: {
114
- params: { name: string },
115
- cookies: { sid: string }
116
- },
117
- res: {
118
- redirect: (redirectUrl: string) => any;
119
- contentType: (type: string) => void;
120
- sendFile: (fileName: string, opts?: { root: string }) => any;
121
- status: (code: number) => {
122
- json: (response: AnyObject) => any;
123
- }
124
- }
125
- ) => any
110
+ get: (
111
+ routePath: string,
112
+ cb: (
113
+ req: {
114
+ params: { name: string },
115
+ cookies: { sid: string }
116
+ },
117
+ res: {
118
+ redirect: (redirectUrl: string) => any;
119
+ contentType: (type: string) => void;
120
+ sendFile: (fileName: string, opts?: { root: string }) => any;
121
+ status: (code: number) => {
122
+ json: (response: AnyObject) => any;
123
+ }
124
+ }
126
125
  ) => any
126
+ ) => any
127
127
  };
128
128
 
129
129
  /**
@@ -143,87 +143,99 @@ type ExpressApp = {
143
143
  * 2. create a lookup table lookup_media_{referencedTable} that joins referencedTable to the media table
144
144
  */
145
145
  export type FileTableConfig = {
146
- tableName?: string; /* defaults to 'media' */
147
-
148
- /**
149
- * GET path used in serving media. defaults to /${tableName}
150
- */
151
- fileServeRoute?: string;
152
-
153
- awsS3Config?: S3Config;
154
- localConfig?: LocalConfig;
155
- // {
156
- // region: string;
157
- // bucket: string;
158
- // accessKeyId: string;
159
- // secretAccessKey: string;
160
- // },
161
- expressApp: ExpressApp;
162
- referencedTables?: {
163
- [tableName: string]: "one" | "many"
164
- },
165
- imageOptions?: ImageOptions
146
+ tableName?: string; /* defaults to 'media' */
147
+
148
+ /**
149
+ * GET path used in serving media. defaults to /${tableName}
150
+ */
151
+ fileServeRoute?: string;
152
+
153
+ awsS3Config?: S3Config;
154
+ localConfig?: LocalConfig;
155
+ // {
156
+ // region: string;
157
+ // bucket: string;
158
+ // accessKeyId: string;
159
+ // secretAccessKey: string;
160
+ // },
161
+ expressApp: ExpressApp;
162
+ referencedTables?: { // minFiles: number; maxFiles: number;
163
+ [tableName: string]:
164
+ | "one" | "many"
165
+
166
+ /**
167
+ * If defined then will try to create (if necessary) a column in the files table which will reference this table's primary key (must have one)
168
+ * */
169
+ // | { type: "lookup_table"; minFilesPerRow: number; maxFilesPerRow: number; maxFileSizeMB: number; }
170
+
171
+ /**
172
+ * If defined then will try to create (if necessary) this column which will reference files_table(id)
173
+ * Prostgles UI will use these hints (obtained through tableHandler.getInfo())
174
+ * */
175
+ // | { type: "column", referenceColumns: Record<string, { maxFileSizeMB: number; }> }
176
+ },
177
+ imageOptions?: ImageOptions
166
178
  };
167
179
 
168
180
  export type ProstglesInitOptions<S = void> = {
169
- dbConnection: DbConnection;
170
- dbOptions?: DbConnectionOpts;
171
- tsGeneratedTypesDir?: string;
172
- io?: any;
173
- publish?: Publish<S>;
174
- publishMethods?: PublishMethods<S>;
175
- publishRawSQL?(params: PublishParams<S>): ( (boolean | "*") | Promise<(boolean | "*")>);
176
- joins?: Joins;
177
- schema?: string;
178
- sqlFilePath?: string;
179
- onReady(dbo: DBOFullyTyped<S>, db: DB): void;
180
- transactions?: string | boolean;
181
- wsChannelNamePrefix?: string;
182
- onSocketConnect?(socket: PRGLIOSocket, dbo: DBOFullyTyped<S>, db?: DB): any;
183
- onSocketDisconnect?(socket: PRGLIOSocket, dbo: DBOFullyTyped<S>, db?: DB): any;
184
- auth?: Auth<S>;
185
- DEBUG_MODE?: boolean;
186
- watchSchemaType?:
187
-
188
- /**
189
- * Will set database event trigger for schema changes. Requires superuser
190
- * Default
191
- */
192
- | "DDL_trigger"
193
-
194
- /**
195
- * Will check client queries for schema changes
196
- * fallback if DDL not possible
197
- */
198
- | "prostgles_queries"
199
-
200
- /**
201
- * Schema checked for changes every 'checkIntervalMillis" milliseconds
202
- */
203
- | { checkIntervalMillis: number };
204
-
205
- watchSchema?:
206
-
207
- /**
208
- * If true then DBoGenerated.d.ts will be updated and "onReady" will be called with new schema on both client and server
209
- */
210
- | boolean
211
-
212
- /**
213
- * Will only rewrite the DBoGenerated.d.ts found in tsGeneratedTypesDir
214
- * This is meant to be used in development when server restarts on file change
215
- */
216
- | "hotReloadMode"
217
-
218
- /**
219
- * Function called when schema changes. Nothing else triggered
220
- */
221
- | ((event: { command: string; query: string }) => void)
222
-
223
- keywords?: Keywords;
224
- onNotice?: (notice: AnyObject, message?: string) => void;
225
- fileTable?: FileTableConfig;
226
- tableConfig?: TableConfig;
181
+ dbConnection: DbConnection;
182
+ dbOptions?: DbConnectionOpts;
183
+ tsGeneratedTypesDir?: string;
184
+ io?: any;
185
+ publish?: Publish<S>;
186
+ publishMethods?: PublishMethods<S>;
187
+ publishRawSQL?(params: PublishParams<S>): ((boolean | "*") | Promise<(boolean | "*")>);
188
+ joins?: Joins;
189
+ schema?: string;
190
+ sqlFilePath?: string;
191
+ onReady(dbo: DBOFullyTyped<S>, db: DB): void;
192
+ transactions?: string | boolean;
193
+ wsChannelNamePrefix?: string;
194
+ onSocketConnect?(socket: PRGLIOSocket, dbo: DBOFullyTyped<S>, db?: DB): any;
195
+ onSocketDisconnect?(socket: PRGLIOSocket, dbo: DBOFullyTyped<S>, db?: DB): any;
196
+ auth?: Auth<S>;
197
+ DEBUG_MODE?: boolean;
198
+ watchSchemaType?:
199
+
200
+ /**
201
+ * Will set database event trigger for schema changes. Requires superuser
202
+ * Default
203
+ */
204
+ | "DDL_trigger"
205
+
206
+ /**
207
+ * Will check client queries for schema changes
208
+ * fallback if DDL not possible
209
+ */
210
+ | "prostgles_queries"
211
+
212
+ /**
213
+ * Schema checked for changes every 'checkIntervalMillis" milliseconds
214
+ */
215
+ | { checkIntervalMillis: number };
216
+
217
+ watchSchema?:
218
+
219
+ /**
220
+ * If true then DBoGenerated.d.ts will be updated and "onReady" will be called with new schema on both client and server
221
+ */
222
+ | boolean
223
+
224
+ /**
225
+ * Will only rewrite the DBoGenerated.d.ts found in tsGeneratedTypesDir
226
+ * This is meant to be used in development when server restarts on file change
227
+ */
228
+ | "hotReloadMode"
229
+
230
+ /**
231
+ * Function called when schema changes. Nothing else triggered
232
+ */
233
+ | ((event: { command: string; query: string }) => void)
234
+
235
+ keywords?: Keywords;
236
+ onNotice?: (notice: AnyObject, message?: string) => void;
237
+ fileTable?: FileTableConfig;
238
+ tableConfig?: TableConfig;
227
239
  }
228
240
 
229
241
  /*
@@ -235,572 +247,572 @@ export type ProstglesInitOptions<S = void> = {
235
247
  */
236
248
 
237
249
  export type OnReady = {
238
- dbo: DBHandlerServer;
239
- db: DB;
250
+ dbo: DBHandlerServer;
251
+ db: DB;
240
252
  }
241
253
 
242
254
  const DEFAULT_KEYWORDS = {
243
- $filter: "$filter",
244
- $and: "$and",
245
- $or: "$or",
246
- $not: "$not"
255
+ $filter: "$filter",
256
+ $and: "$and",
257
+ $or: "$or",
258
+ $not: "$not"
247
259
  };
248
260
 
249
261
  import * as fs from 'fs';
250
262
  import { DBOFullyTyped } from "./DBSchemaBuilder";
251
263
  export class Prostgles {
252
264
 
253
- opts: ProstglesInitOptions = {
254
- DEBUG_MODE: false,
255
- dbConnection: {
256
- host: "localhost",
257
- port: 5432,
258
- application_name: "prostgles_app"
259
- },
260
- onReady: () => {},
261
- schema: "public",
262
- watchSchema: false,
263
- watchSchemaType: "DDL_trigger",
264
- };
265
-
266
- // dbConnection: DbConnection = {
267
- // host: "localhost",
268
- // port: 5432,
269
- // application_name: "prostgles_app"
270
- // };
271
- // dbOptions: DbConnectionOpts;
272
- db?: DB;
273
- pgp?: PGP;
274
- dbo?: DBHandlerServer;
275
- _dboBuilder?: DboBuilder;
276
- get dboBuilder(): DboBuilder {
277
- if(!this._dboBuilder) throw "get dboBuilder: it's undefined"
278
- return this._dboBuilder;
265
+ opts: ProstglesInitOptions = {
266
+ DEBUG_MODE: false,
267
+ dbConnection: {
268
+ host: "localhost",
269
+ port: 5432,
270
+ application_name: "prostgles_app"
271
+ },
272
+ onReady: () => { },
273
+ schema: "public",
274
+ watchSchema: false,
275
+ watchSchemaType: "DDL_trigger",
276
+ };
277
+
278
+ // dbConnection: DbConnection = {
279
+ // host: "localhost",
280
+ // port: 5432,
281
+ // application_name: "prostgles_app"
282
+ // };
283
+ // dbOptions: DbConnectionOpts;
284
+ db?: DB;
285
+ pgp?: PGP;
286
+ dbo?: DBHandlerServer;
287
+ _dboBuilder?: DboBuilder;
288
+ get dboBuilder(): DboBuilder {
289
+ if (!this._dboBuilder) throw "get dboBuilder: it's undefined"
290
+ return this._dboBuilder;
291
+ }
292
+ set dboBuilder(d: DboBuilder) {
293
+ this._dboBuilder = d;
294
+ }
295
+ publishParser?: PublishParser;
296
+
297
+ authHandler?: AuthHandler;
298
+
299
+
300
+ keywords = DEFAULT_KEYWORDS;
301
+ private loaded = false;
302
+
303
+ dbEventsManager?: DBEventsManager;
304
+
305
+
306
+ fileManager?: FileManager;
307
+
308
+ tableConfigurator?: TableConfigurator;
309
+
310
+ isMedia(tableName: string) {
311
+ return this.opts?.fileTable?.tableName === tableName;
312
+ }
313
+
314
+ constructor(params: ProstglesInitOptions) {
315
+ if (!params) throw "ProstglesInitOptions missing";
316
+ if (!params.io) console.warn("io missing. WebSockets will not be set up");
317
+
318
+ // TODO: find an exact keyof T<->arr TS matching method
319
+ let config: Array<keyof ProstglesInitOptions> = [
320
+ "transactions", "joins", "tsGeneratedTypesDir",
321
+ "onReady", "dbConnection", "dbOptions", "publishMethods", "io",
322
+ "publish", "schema", "publishRawSQL", "wsChannelNamePrefix", "onSocketConnect",
323
+ "onSocketDisconnect", "sqlFilePath", "auth", "DEBUG_MODE", "watchSchema", "watchSchemaType",
324
+ "fileTable", "tableConfig"
325
+ ];
326
+ const unknownParams = Object.keys(params).filter((key: string) => !(config as string[]).includes(key))
327
+ if (unknownParams.length) {
328
+ console.error(`Unrecognised ProstglesInitOptions params: ${unknownParams.join()}`);
279
329
  }
280
- set dboBuilder(d: DboBuilder) {
281
- this._dboBuilder = d;
330
+
331
+ Object.assign(this.opts, params);
332
+
333
+ /* set defaults */
334
+ if (this.opts?.fileTable) {
335
+ this.opts.fileTable.tableName = this.opts?.fileTable?.tableName || "media";
282
336
  }
283
- publishParser?: PublishParser;
337
+ this.opts.schema = this.opts.schema || "public";
284
338
 
285
- authHandler?: AuthHandler;
339
+ this.keywords = {
340
+ ...DEFAULT_KEYWORDS,
341
+ ...params.keywords,
342
+ }
343
+ }
344
+
345
+ destroyed = false;
346
+
347
+ async onSchemaChange(event: { command: string; query: string }) {
348
+ const { watchSchema, watchSchemaType, onReady, tsGeneratedTypesDir } = this.opts;
349
+ if (watchSchema && this.loaded) {
350
+ console.log("Schema changed");
351
+ const { query } = event;
352
+ if (typeof query === "string" && query.includes(PubSubManager.EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID)) {
353
+ console.log("Schema change event excluded from triggers due to EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID");
354
+ return;
355
+ }
356
+
357
+ if (typeof watchSchema === "function") {
358
+ /* Only call the provided func */
359
+ watchSchema(event);
360
+
361
+ } else if (watchSchema === "hotReloadMode") {
362
+ if (tsGeneratedTypesDir) {
363
+ /* Hot reload integration. Will only touch tsGeneratedTypesDir */
364
+ console.log("watchSchema: Re-writing TS schema");
365
+
366
+ await this.refreshDBO();
367
+ this.writeDBSchema(true);
368
+ }
286
369
 
370
+ } else if (watchSchema === true || isPlainObject(watchSchemaType) && "checkIntervalMillis" in watchSchemaType) {
371
+ /* Full re-init. Sockets must reconnect */
372
+ console.log("watchSchema: Full re-initialisation")
373
+ this.init(onReady);
374
+ }
375
+ }
376
+ }
377
+
378
+ checkDb() {
379
+ if (!this.db || !this.db.connect) throw "something went wrong getting a db connection";
380
+ }
381
+
382
+ getTSFileName() {
383
+ const fileName = "DBoGenerated.d.ts" //`dbo_${this.schema}_types.ts`;
384
+ const fullPath = (this.opts.tsGeneratedTypesDir || "") + fileName;
385
+ return { fileName, fullPath }
386
+ }
387
+
388
+ private getFileText(fullPath: string, format = "utf8"): Promise<string> {
389
+ return new Promise((resolve, reject) => {
390
+ fs.readFile(fullPath, 'utf8', function (err, data) {
391
+ if (err) reject(err);
392
+ else resolve(data);
393
+ });
394
+ })
395
+ }
396
+
397
+ writeDBSchema(force = false) {
398
+
399
+ if (this.opts.tsGeneratedTypesDir) {
400
+ const { fullPath, fileName } = this.getTSFileName();
401
+ const header = `/* This file was generated by Prostgles \n` +
402
+ // `* ${(new Date).toUTCString()} \n`
403
+ `*/ \n\n `;
404
+ const fileContent = header + this.dboBuilder.tsTypesDefinition;
405
+ fs.readFile(fullPath, 'utf8', function (err, data) {
406
+ if (err || (force || data !== fileContent)) {
407
+ fs.writeFileSync(fullPath, fileContent);
408
+ console.log("Prostgles: Created typescript schema definition file: \n " + fileName)
409
+ }
410
+ });
411
+ } else if (force) {
412
+ console.error("Schema changed. tsGeneratedTypesDir needs to be set to reload server")
413
+ }
414
+ }
415
+
416
+ refreshDBO = async () => {
417
+ if (this._dboBuilder) this._dboBuilder.destroy();
418
+ this.dboBuilder = await DboBuilder.create(this as any) as any;
419
+ if (!this.dboBuilder) throw "this.dboBuilder"
420
+ this.dbo = this.dboBuilder.dbo as any;
421
+ return this.dbo;
422
+ }
423
+
424
+ isSuperUser = false;
425
+ schema_checkIntervalMillis?: NodeJS.Timeout;
426
+ async init(onReady: (dbo: DBOFullyTyped, db: DB) => any): Promise<{
427
+ db: DBOFullyTyped;
428
+ _db: DB;
429
+ pgp: PGP;
430
+ io?: any;
431
+ destroy: () => Promise<boolean>;
432
+ }> {
433
+ this.loaded = false;
434
+
435
+
436
+ if (this.opts.watchSchema === "hotReloadMode" && !this.opts.tsGeneratedTypesDir) {
437
+ throw "tsGeneratedTypesDir option is needed for watchSchema: hotReloadMode to work ";
438
+ } else if (
439
+ this.opts.watchSchema &&
440
+ typeof this.opts.watchSchemaType === "object" &&
441
+ "checkIntervalMillis" in this.opts.watchSchemaType &&
442
+ typeof this.opts.watchSchemaType.checkIntervalMillis === "number"
443
+ ) {
444
+
445
+ if (this.schema_checkIntervalMillis) {
446
+ clearInterval(this.schema_checkIntervalMillis);
447
+ this.schema_checkIntervalMillis = setInterval(async () => {
448
+ const dbuilder = await DboBuilder.create(this as any);
449
+ if (dbuilder.tsTypesDefinition !== this.dboBuilder.tsTypesDefinition) {
450
+ this.refreshDBO();
451
+ this.init(onReady);
452
+ }
453
+ }, this.opts.watchSchemaType.checkIntervalMillis)
454
+ }
455
+ }
287
456
 
288
- keywords = DEFAULT_KEYWORDS;
289
- private loaded = false;
457
+ /* 1. Connect to db */
458
+ if (!this.db) {
459
+ const { db, pgp } = getDbConnection(this.opts.dbConnection, this.opts.dbOptions, this.opts.DEBUG_MODE,
460
+ notice => {
461
+ if (this.opts.onNotice) this.opts.onNotice(notice);
462
+ if (this.dbEventsManager) {
463
+ this.dbEventsManager.onNotice(notice)
464
+ }
465
+ }
466
+ );
467
+ this.db = db;
468
+ this.pgp = pgp;
469
+ this.isSuperUser = await isSuperUser(db);
470
+ }
471
+ this.checkDb();
472
+ const db = this.db!;
473
+ const pgp = this.pgp!;
290
474
 
291
- dbEventsManager?: DBEventsManager;
475
+ /* 2. Execute any SQL file if provided */
476
+ if (this.opts.sqlFilePath) {
477
+ await this.runSQLFile(this.opts.sqlFilePath);
478
+ }
292
479
 
480
+ try {
293
481
 
294
- fileManager?: FileManager;
482
+ await this.refreshDBO();
483
+ if (this.opts.tableConfig) {
484
+ this.tableConfigurator = new TableConfigurator(this as any);
485
+ try {
486
+ await this.tableConfigurator.init();
487
+ } catch (e) {
488
+ console.error("TableConfigurator: ", e);
489
+ throw e;
490
+ }
491
+ }
295
492
 
296
- tableConfigurator?: TableConfigurator;
493
+ /* 3. Make DBO object from all tables and views */
494
+ await this.refreshDBO();
297
495
 
298
- isMedia(tableName: string){
299
- return this.opts?.fileTable?.tableName === tableName;
300
- }
496
+ /* Create media table if required */
497
+ if (this.opts.fileTable) {
498
+ const { awsS3Config, localConfig, imageOptions } = this.opts.fileTable;
499
+ await this.refreshDBO();
500
+ if (!awsS3Config && !localConfig) throw "fileTable missing param: Must provide awsS3Config OR localConfig";
501
+ //@ts-ignore
502
+ this.fileManager = new FileManager(awsS3Config || localConfig, imageOptions);
301
503
 
302
- constructor(params: ProstglesInitOptions){
303
- if(!params) throw "ProstglesInitOptions missing";
304
- if(!params.io) console.warn("io missing. WebSockets will not be set up");
305
-
306
- // TODO: find an exact keyof T<->arr TS matching method
307
- let config: Array<keyof ProstglesInitOptions> = [
308
- "transactions", "joins", "tsGeneratedTypesDir",
309
- "onReady", "dbConnection", "dbOptions", "publishMethods", "io",
310
- "publish", "schema", "publishRawSQL", "wsChannelNamePrefix", "onSocketConnect",
311
- "onSocketDisconnect", "sqlFilePath", "auth", "DEBUG_MODE", "watchSchema", "watchSchemaType",
312
- "fileTable", "tableConfig"
313
- ];
314
- const unknownParams = Object.keys(params).filter((key: string) => !(config as string[]).includes(key))
315
- if(unknownParams.length){
316
- console.error(`Unrecognised ProstglesInitOptions params: ${unknownParams.join()}`);
504
+ try {
505
+ await this.fileManager.init(this as any);
506
+ } catch (e) {
507
+ console.error("FileManager: ", e);
508
+ throw e;
317
509
  }
318
-
319
- Object.assign(this.opts, params);
510
+ }
511
+ await this.refreshDBO();
320
512
 
321
- /* set defaults */
322
- if(this.opts?.fileTable){
323
- this.opts.fileTable.tableName = this.opts?.fileTable?.tableName || "media";
324
- }
325
- this.opts.schema = this.opts.schema || "public";
326
513
 
327
- this.keywords = {
328
- ...DEFAULT_KEYWORDS,
329
- ...params.keywords,
330
- }
331
- }
514
+ if (this.opts.publish) {
332
515
 
333
- destroyed = false;
516
+ if (!this.opts.io) console.warn("IO missing. Publish has no effect without io");
334
517
 
335
- async onSchemaChange(event: { command: string; query: string }){
336
- const { watchSchema, watchSchemaType, onReady, tsGeneratedTypesDir } = this.opts;
337
- if(watchSchema && this.loaded){
338
- console.log("Schema changed");
339
- const { query } = event;
340
- if(typeof query === "string" && query.includes(PubSubManager.EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID)){
341
- console.log("Schema change event excluded from triggers due to EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID");
342
- return;
343
- }
344
-
345
- if(typeof watchSchema === "function"){
346
- /* Only call the provided func */
347
- watchSchema(event);
348
-
349
- } else if(watchSchema === "hotReloadMode") {
350
- if(tsGeneratedTypesDir) {
351
- /* Hot reload integration. Will only touch tsGeneratedTypesDir */
352
- console.log("watchSchema: Re-writing TS schema");
353
-
354
- await this.refreshDBO();
355
- this.writeDBSchema(true);
356
- }
357
-
358
- } else if(watchSchema === true || isPlainObject(watchSchemaType) && "checkIntervalMillis" in watchSchemaType){
359
- /* Full re-init. Sockets must reconnect */
360
- console.log("watchSchema: Full re-initialisation")
361
- this.init(onReady);
362
- }
363
- }
364
- }
518
+ /* 3.9 Check auth config */
519
+ this.authHandler = new AuthHandler(this as any);
520
+ await this.authHandler.init();
365
521
 
366
- checkDb(){
367
- if(!this.db || !this.db.connect) throw "something went wrong getting a db connection";
368
- }
522
+ this.publishParser = new PublishParser(this.opts.publish, this.opts.publishMethods, this.opts.publishRawSQL, this.dbo!, this.db, this as any);
523
+ this.dboBuilder.publishParser = this.publishParser;
369
524
 
370
- getTSFileName(){
371
- const fileName = "DBoGenerated.d.ts" //`dbo_${this.schema}_types.ts`;
372
- const fullPath = (this.opts.tsGeneratedTypesDir || "") + fileName;
373
- return { fileName, fullPath }
374
- }
525
+ /* 4. Set publish and auth listeners */
526
+ await this.setSocketEvents();
375
527
 
376
- private getFileText(fullPath: string, format = "utf8"): Promise<string>{
377
- return new Promise((resolve, reject) => {
378
- fs.readFile(fullPath, 'utf8', function(err, data) {
379
- if(err) reject(err);
380
- else resolve(data);
381
- });
382
- })
383
- }
528
+ } else if (this.opts.auth) throw "Auth config does not work without publish";
384
529
 
385
- writeDBSchema(force = false){
386
-
387
- if(this.opts.tsGeneratedTypesDir){
388
- const { fullPath, fileName } = this.getTSFileName();
389
- const header = `/* This file was generated by Prostgles \n` +
390
- // `* ${(new Date).toUTCString()} \n`
391
- `*/ \n\n `;
392
- const fileContent = header + this.dboBuilder.tsTypesDefinition;
393
- fs.readFile(fullPath, 'utf8', function(err, data) {
394
- if (err || (force || data !== fileContent)) {
395
- fs.writeFileSync(fullPath, fileContent);
396
- console.log("Prostgles: Created typescript schema definition file: \n " + fileName)
397
- }
398
- });
399
- } else if(force) {
400
- console.error("Schema changed. tsGeneratedTypesDir needs to be set to reload server")
401
- }
402
- }
530
+ // if(this.watchSchema){
531
+ // if(!(await isSuperUser(db))) throw "Cannot watchSchema without a super user schema. Set watchSchema=false or provide a super user";
532
+ // }
533
+
534
+ this.dbEventsManager = new DBEventsManager(db, pgp);
403
535
 
404
- refreshDBO = async () => {
405
- if(this._dboBuilder) this._dboBuilder.destroy();
406
- this.dboBuilder = await DboBuilder.create(this as any) as any;
407
- if(!this.dboBuilder) throw "this.dboBuilder"
408
- this.dbo = this.dboBuilder.dbo as any;
409
- return this.dbo;
410
- }
411
536
 
412
- isSuperUser = false;
413
- schema_checkIntervalMillis?: NodeJS.Timeout;
414
- async init(onReady: (dbo: DBOFullyTyped, db: DB) => any): Promise<{
415
- db: DBOFullyTyped;
416
- _db: DB;
417
- pgp: PGP;
418
- io?: any;
419
- destroy: () => Promise<boolean>;
420
- }> {
421
- this.loaded = false;
422
-
423
-
424
- if(this.opts.watchSchema === "hotReloadMode" && !this.opts.tsGeneratedTypesDir) {
425
- throw "tsGeneratedTypesDir option is needed for watchSchema: hotReloadMode to work ";
426
- } else if(
427
- this.opts.watchSchema &&
428
- typeof this.opts.watchSchemaType === "object" &&
429
- "checkIntervalMillis" in this.opts.watchSchemaType &&
430
- typeof this.opts.watchSchemaType.checkIntervalMillis === "number"
431
- ){
432
-
433
- if(this.schema_checkIntervalMillis){
434
- clearInterval(this.schema_checkIntervalMillis);
435
- this.schema_checkIntervalMillis = setInterval(async () => {
436
- const dbuilder = await DboBuilder.create(this as any);
437
- if(dbuilder.tsTypesDefinition !== this.dboBuilder.tsTypesDefinition){
438
- this.refreshDBO();
439
- this.init(onReady);
440
- }
441
- }, this.opts.watchSchemaType.checkIntervalMillis)
537
+ this.writeDBSchema();
538
+
539
+ /* 5. Finish init and provide DBO object */
540
+ try {
541
+ if (this.destroyed) {
542
+ console.trace(1)
543
+ }
544
+ onReady(this.dbo as any, this.db);
545
+ } catch (err) {
546
+ console.error("Prostgles: Error within onReady: \n", err)
547
+ }
548
+
549
+ this.loaded = true;
550
+ return {
551
+ db: this.dbo! as any,
552
+ _db: db,
553
+ pgp,
554
+ io: this.opts.io,
555
+ destroy: async () => {
556
+ console.log("destroying prgl instance")
557
+ this.destroyed = true;
558
+ if (this.opts.io) {
559
+ this.opts.io.on("connection", () => {
560
+ console.log("Socket connected to destroyed instance")
561
+ });
562
+ if (typeof this.opts.io.close === "function") {
563
+ this.opts.io.close();
564
+ console.log("this.io.close")
442
565
  }
566
+ }
567
+
568
+ this.dboBuilder?.destroy();
569
+ this.dbo = undefined;
570
+ this.db = undefined;
571
+ await db.$pool.end();
572
+ await sleep(1000);
573
+ return true;
443
574
  }
575
+ };
576
+ } catch (e) {
577
+ console.trace(e)
578
+ // @ts-ignore
579
+ throw "init issues: " + e.toString();
580
+ }
581
+ }
582
+
583
+ async runSQLFile(filePath: string) {
584
+
585
+ const fileContent = await this.getFileText(filePath);//.then(console.log);
586
+
587
+ return this.db?.multi(fileContent).then((data) => {
588
+ console.log("Prostgles: SQL file executed successfuly \n -> " + filePath);
589
+ return data
590
+ }).catch((err) => {
591
+ const { position, length } = err,
592
+ lines = fileContent.split("\n");
593
+ let errMsg = filePath + " error: ";
594
+
595
+ if (position && length && fileContent) {
596
+ const startLine = Math.max(0, fileContent.substring(0, position).split("\n").length - 2),
597
+ endLine = startLine + 3;
598
+
599
+ errMsg += "\n\n";
600
+ errMsg += lines.slice(startLine, endLine).map((txt, i) => `${startLine + i + 1} ${i === 1 ? "->" : " "} ${txt}`).join("\n");
601
+ errMsg += "\n\n";
602
+ }
603
+ console.error(errMsg, err);
604
+ throw err;
605
+ });
606
+ }
444
607
 
445
- /* 1. Connect to db */
446
- if(!this.db){
447
- const { db, pgp } = getDbConnection(this.opts.dbConnection, this.opts.dbOptions, this.opts.DEBUG_MODE,
448
- notice => {
449
- if(this.opts.onNotice) this.opts.onNotice(notice);
450
- if(this.dbEventsManager){
451
- this.dbEventsManager.onNotice(notice)
452
- }
453
- }
454
- );
455
- this.db = db;
456
- this.pgp = pgp;
457
- this.isSuperUser = await isSuperUser(db);
458
- }
459
- this.checkDb();
460
- const db = this.db!;
461
- const pgp = this.pgp!;
462
608
 
463
- /* 2. Execute any SQL file if provided */
464
- if(this.opts.sqlFilePath){
465
- await this.runSQLFile(this.opts.sqlFilePath);
466
- }
609
+ connectedSockets: any[] = [];
610
+ async setSocketEvents() {
611
+ this.checkDb();
467
612
 
468
- try {
613
+ if (!this.dbo) throw "dbo missing";
469
614
 
470
- await this.refreshDBO();
471
- if(this.opts.tableConfig){
472
- this.tableConfigurator = new TableConfigurator(this as any);
473
- try {
474
- await this.tableConfigurator.init();
475
- } catch(e){
476
- console.error("TableConfigurator: ",e);
477
- throw e;
478
- }
479
- }
615
+ let publishParser = new PublishParser(this.opts.publish, this.opts.publishMethods, this.opts.publishRawSQL, this.dbo, this.db!, this as any);
616
+ this.publishParser = publishParser;
480
617
 
481
- /* 3. Make DBO object from all tables and views */
482
- await this.refreshDBO();
483
-
484
- /* Create media table if required */
485
- if(this.opts.fileTable){
486
- const { awsS3Config, localConfig, imageOptions } = this.opts.fileTable;
487
- await this.refreshDBO();
488
- if(!awsS3Config && !localConfig) throw "fileTable missing param: Must provide awsS3Config OR localConfig";
489
- //@ts-ignore
490
- this.fileManager = new FileManager(awsS3Config || localConfig, imageOptions);
491
-
492
- try {
493
- await this.fileManager.init(this as any);
494
- } catch(e){
495
- console.error("FileManager: ",e);
496
- throw e;
497
- }
498
- }
499
- await this.refreshDBO();
500
-
501
-
502
- if(this.opts.publish){
503
-
504
- if(!this.opts.io) console.warn("IO missing. Publish has no effect without io");
505
-
506
- /* 3.9 Check auth config */
507
- this.authHandler = new AuthHandler(this as any);
508
- await this.authHandler.init();
509
-
510
- this.publishParser = new PublishParser(this.opts.publish, this.opts.publishMethods, this.opts.publishRawSQL, this.dbo!, this.db, this as any);
511
- this.dboBuilder.publishParser = this.publishParser;
512
-
513
- /* 4. Set publish and auth listeners */
514
- await this.setSocketEvents();
515
-
516
- } else if(this.opts.auth) throw "Auth config does not work without publish";
517
-
518
- // if(this.watchSchema){
519
- // if(!(await isSuperUser(db))) throw "Cannot watchSchema without a super user schema. Set watchSchema=false or provide a super user";
520
- // }
521
-
522
- this.dbEventsManager = new DBEventsManager(db, pgp);
523
-
524
-
525
- this.writeDBSchema();
526
-
527
- /* 5. Finish init and provide DBO object */
528
- try {
529
- if(this.destroyed) {
530
- console.trace(1)
531
- }
532
- onReady(this.dbo as any, this.db);
533
- } catch(err){
534
- console.error("Prostgles: Error within onReady: \n", err)
535
- }
618
+ if (!this.opts.io) return;
536
619
 
537
- this.loaded = true;
538
- return {
539
- db: this.dbo! as any,
540
- _db: db,
541
- pgp,
542
- io: this.opts.io,
543
- destroy: async () => {
544
- console.log("destroying prgl instance")
545
- this.destroyed = true;
546
- if(this.opts.io){
547
- this.opts.io.on("connection", () => {
548
- console.log("Socket connected to destroyed instance")
549
- });
550
- if(typeof this.opts.io.close === "function"){
551
- this.opts.io.close();
552
- console.log("this.io.close")
553
- }
554
- }
555
-
556
- this.dboBuilder?.destroy();
557
- this.dbo = undefined;
558
- this.db = undefined;
559
- await db.$pool.end();
560
- await sleep(1000);
561
- return true;
562
- }
563
- };
564
- } catch (e) {
565
- console.trace(e)
566
- // @ts-ignore
567
- throw "init issues: " + e.toString();
568
- }
620
+ /* Already initialised. Only reconnect sockets */
621
+ if (this.connectedSockets.length) {
622
+ this.connectedSockets.forEach((s: any) => {
623
+ s.emit(CHANNELS.SCHEMA_CHANGED);
624
+ this.pushSocketSchema(s);
625
+ });
626
+ return;
569
627
  }
570
628
 
571
- async runSQLFile(filePath: string){
572
-
573
- const fileContent = await this.getFileText(filePath);//.then(console.log);
574
-
575
- return this.db?.multi(fileContent).then((data)=>{
576
- console.log("Prostgles: SQL file executed successfuly \n -> " + filePath);
577
- return data
578
- }).catch((err) => {
579
- const { position, length } = err,
580
- lines = fileContent.split("\n");
581
- let errMsg = filePath + " error: ";
582
-
583
- if(position && length && fileContent){
584
- const startLine = Math.max(0, fileContent.substring(0, position).split("\n").length - 2),
585
- endLine = startLine + 3;
586
-
587
- errMsg += "\n\n";
588
- errMsg += lines.slice(startLine, endLine).map((txt, i) => `${startLine + i + 1} ${i === 1? "->" : " "} ${txt}`).join("\n");
589
- errMsg += "\n\n";
590
- }
591
- console.error(errMsg, err);
592
- throw err;
593
- });
594
- }
629
+ /* Initialise */
630
+ this.opts.io.on('connection', async (socket: PRGLIOSocket) => {
631
+ if (this.destroyed) {
632
+ console.log("Socket connected to destroyed instance");
633
+ socket.disconnect();
634
+ return
635
+ }
636
+ this.connectedSockets.push(socket);
637
+
638
+ if (!this.db || !this.dbo) throw "db/dbo missing";
639
+ let { dbo, db, pgp } = this;
595
640
 
641
+ try {
642
+ if (this.opts.onSocketConnect) await this.opts.onSocketConnect(socket, dbo as any, db);
596
643
 
597
- connectedSockets: any[] = [];
598
- async setSocketEvents(){
599
- this.checkDb();
600
644
 
601
- if(!this.dbo) throw "dbo missing";
645
+ /* RUN Client request from Publish.
646
+ Checks request against publish and if OK run it with relevant publish functions. Local (server) requests do not check the policy
647
+ */
648
+ socket.removeAllListeners(CHANNELS.DEFAULT)
649
+ socket.on(CHANNELS.DEFAULT, async ({ tableName, command, param1, param2, param3 }: SocketRequestParams, cb = (...callback: any[]) => { }) => {
602
650
 
603
- let publishParser = new PublishParser(this.opts.publish, this.opts.publishMethods, this.opts.publishRawSQL, this.dbo, this.db!, this as any);
604
- this.publishParser = publishParser;
651
+ try { /* Channel name will only include client-sent params so we ignore table_rules enforced params */
652
+ if (!socket || !this.authHandler || !this.publishParser || !this.dbo) {
653
+ console.error("socket or authhandler missing??!!")
654
+ throw "socket or authhandler missing??!!";
655
+ }
605
656
 
606
- if(!this.opts.io) return;
657
+ const clientInfo = await this.authHandler.getClientInfo({ socket });
658
+ let valid_table_command_rules = await this.publishParser.getValidatedRequestRule({ tableName, command, localParams: { socket } }, clientInfo);
659
+ if (valid_table_command_rules) {
660
+ //@ts-ignore
661
+ let res = await this.dbo[tableName][command]!(param1, param2, param3, valid_table_command_rules, { socket, has_rules: true });
662
+ cb(null, res);
663
+ } else throw `Invalid OR disallowed request: ${tableName}.${command} `;
664
+
665
+ } catch (err) {
666
+ // const _err_msg = err.toString();
667
+ // cb({ msg: _err_msg, err });
668
+ console.trace(err);
669
+ cb(err)
670
+ // console.warn("runPublishedRequest ERROR: ", err, socket._user);
671
+ }
672
+ });
607
673
 
608
- /* Already initialised. Only reconnect sockets */
609
- if(this.connectedSockets.length){
610
- this.connectedSockets.forEach((s: any) => {
611
- s.emit(CHANNELS.SCHEMA_CHANGED);
612
- this.pushSocketSchema(s);
613
- });
614
- return;
615
- }
616
-
617
- /* Initialise */
618
- this.opts.io.on('connection', async (socket: PRGLIOSocket) => {
619
- if(this.destroyed){
620
- console.log("Socket connected to destroyed instance");
621
- socket.disconnect();
622
- return
674
+ socket.on("disconnect", () => {
675
+ this.dbEventsManager?.removeNotice(socket);
676
+ this.dbEventsManager?.removeNotify(undefined, socket);
677
+ this.connectedSockets = this.connectedSockets.filter(s => s.id !== socket.id);
678
+ // subscriptions = subscriptions.filter(sub => sub.socket.id !== socket.id);
679
+ if (this.opts.onSocketDisconnect) {
680
+ this.opts.onSocketDisconnect(socket, dbo as any);
681
+ };
682
+ });
683
+
684
+ socket.removeAllListeners(CHANNELS.METHOD)
685
+ socket.on(CHANNELS.METHOD, async ({ method, params }: SocketMethodRequest, cb = (...callback: any) => { }) => {
686
+ try {
687
+ const methods = await this.publishParser?.getMethods(socket) as any;
688
+
689
+ if (!methods || !methods[method]) {
690
+ cb("Disallowed/missing method " + JSON.stringify(method));
691
+ } else {
692
+ try {
693
+ const res = await methods[method](...params);
694
+ cb(null, res);
695
+ } catch (err) {
696
+ makeSocketError(cb, err);
697
+ }
623
698
  }
624
- this.connectedSockets.push(socket);
625
-
626
- if(!this.db || !this.dbo) throw "db/dbo missing";
627
- let { dbo, db, pgp } = this;
628
-
629
- try {
630
- if(this.opts.onSocketConnect) await this.opts.onSocketConnect(socket, dbo as any, db);
631
-
632
-
633
- /* RUN Client request from Publish.
634
- Checks request against publish and if OK run it with relevant publish functions. Local (server) requests do not check the policy
635
- */
636
- socket.removeAllListeners(CHANNELS.DEFAULT)
637
- socket.on(CHANNELS.DEFAULT, async ({ tableName, command, param1, param2, param3 }: SocketRequestParams, cb = (...callback: any[]) => {} ) => {
638
-
639
- try { /* Channel name will only include client-sent params so we ignore table_rules enforced params */
640
- if(!socket || !this.authHandler || !this.publishParser || !this.dbo) {
641
- console.error("socket or authhandler missing??!!")
642
- throw "socket or authhandler missing??!!";
643
- }
644
-
645
- const clientInfo = await this.authHandler.getClientInfo({ socket });
646
- let valid_table_command_rules = await this.publishParser.getValidatedRequestRule({ tableName, command, localParams: { socket } }, clientInfo);
647
- if(valid_table_command_rules){
648
- //@ts-ignore
649
- let res = await this.dbo[tableName][command]!(param1, param2, param3, valid_table_command_rules, { socket, has_rules: true });
650
- cb(null, res);
651
- } else throw `Invalid OR disallowed request: ${tableName}.${command} `;
652
-
653
- } catch(err) {
654
- // const _err_msg = err.toString();
655
- // cb({ msg: _err_msg, err });
656
- console.trace(err);
657
- cb(err)
658
- // console.warn("runPublishedRequest ERROR: ", err, socket._user);
659
- }
660
- });
661
-
662
- socket.on("disconnect", () => {
663
- this.dbEventsManager?.removeNotice(socket);
664
- this.dbEventsManager?.removeNotify(undefined, socket);
665
- this.connectedSockets = this.connectedSockets.filter(s => s.id !== socket.id);
666
- // subscriptions = subscriptions.filter(sub => sub.socket.id !== socket.id);
667
- if(this.opts.onSocketDisconnect){
668
- this.opts.onSocketDisconnect(socket, dbo as any);
669
- };
670
- });
671
-
672
- socket.removeAllListeners(CHANNELS.METHOD)
673
- socket.on(CHANNELS.METHOD, async ({ method, params }: SocketMethodRequest, cb = (...callback: any) => {} ) => {
674
- try {
675
- const methods = await this.publishParser?.getMethods(socket) as any;
676
-
677
- if(!methods || !methods[method]){
678
- cb("Disallowed/missing method " + JSON.stringify(method));
679
- } else {
680
- try {
681
- const res = await methods[method](...params);
682
- cb(null, res);
683
- } catch(err){
684
- makeSocketError(cb, err);
685
- }
686
- }
687
- } catch(err) {
688
- makeSocketError(cb, err);
689
- console.warn("method ERROR: ", err, socket._user);
690
- }
691
- });
692
-
693
- this.pushSocketSchema(socket);
694
- } catch(e) {
695
- console.trace("setSocketEvents: ", e)
696
- }
699
+ } catch (err) {
700
+ makeSocketError(cb, err);
701
+ console.warn("method ERROR: ", err, socket._user);
702
+ }
703
+ });
704
+
705
+ this.pushSocketSchema(socket);
706
+ } catch (e) {
707
+ console.trace("setSocketEvents: ", e)
708
+ }
709
+ });
710
+ }
711
+
712
+ pushSocketSchema = async (socket: any) => {
713
+
714
+ let auth: any = await this.authHandler?.makeSocketAuth(socket) || {};
715
+
716
+ // let needType = this.publishRawSQL && typeof this.publishRawSQL === "function";
717
+ // let DATA_TYPES = !needType? [] : await this.db.any("SELECT oid, typname FROM pg_type");
718
+ // let USER_TABLES = !needType? [] : await this.db.any("SELECT relid, relname FROM pg_catalog.pg_statio_user_tables");
719
+
720
+ const { dbo, db, pgp, publishParser } = this;
721
+ let fullSchema: {
722
+ schema: TableSchemaForClient;
723
+ tables: DBSchemaTable[];
724
+ } | undefined;
725
+ let publishValidationError;
726
+ let rawSQL = false;
727
+
728
+ try {
729
+ if (!publishParser) throw "publishParser undefined";
730
+ fullSchema = await publishParser.getSchemaFromPublish(socket);
731
+ } catch (e) {
732
+ publishValidationError = "Server Error: PUBLISH VALIDATION ERROR";
733
+ console.error(`\nProstgles PUBLISH VALIDATION ERROR (after socket connected):\n ->`, e);
734
+ }
735
+ socket.prostgles = socket.prostgles || {};
736
+ socket.prostgles.schema = fullSchema?.schema;
737
+ /* RUN Raw sql from client IF PUBLISHED
738
+ */
739
+
740
+ if (this.opts.publishRawSQL && typeof this.opts.publishRawSQL === "function") {
741
+ const canRunSQL = async () => {
742
+ const publishParams = await this.publishParser?.getPublishParams({ socket })
743
+ let res = await this.opts.publishRawSQL?.(publishParams as any);
744
+ return Boolean(res && typeof res === "boolean" || res === "*");
745
+ }
746
+
747
+ if (await canRunSQL()) {
748
+ socket.removeAllListeners(CHANNELS.SQL)
749
+ socket.on(CHANNELS.SQL, async ({ query, params, options }: SQLRequest, cb = (...callback: any) => { }) => {
750
+
751
+ if (!this.dbo?.sql) throw "Internal error: sql handler missing";
752
+
753
+ this.dbo.sql(query, params, options, { socket }).then(res => {
754
+ cb(null, res)
755
+ }).catch(err => {
756
+ makeSocketError(cb, err);
757
+ })
697
758
  });
759
+ if (db) {
760
+ // let allTablesViews = await db.any(STEP2_GET_ALL_TABLES_AND_COLUMNS);
761
+ // fullSchema = allTablesViews;
762
+ rawSQL = true;
763
+ } else console.error("db missing");
764
+ }
698
765
  }
699
766
 
700
- pushSocketSchema = async (socket: any) => {
701
-
702
- let auth: any = await this.authHandler?.makeSocketAuth(socket) || {};
703
-
704
- // let needType = this.publishRawSQL && typeof this.publishRawSQL === "function";
705
- // let DATA_TYPES = !needType? [] : await this.db.any("SELECT oid, typname FROM pg_type");
706
- // let USER_TABLES = !needType? [] : await this.db.any("SELECT relid, relname FROM pg_catalog.pg_statio_user_tables");
707
-
708
- const { dbo, db, pgp, publishParser } = this;
709
- let fullSchema: {
710
- schema: TableSchemaForClient;
711
- tables: DBSchemaTable[];
712
- } | undefined;
713
- let publishValidationError;
714
- let rawSQL = false;
715
-
716
- try {
717
- if(!publishParser) throw "publishParser undefined";
718
- fullSchema = await publishParser.getSchemaFromPublish(socket);
719
- } catch(e){
720
- publishValidationError = "Server Error: PUBLISH VALIDATION ERROR";
721
- console.error(`\nProstgles PUBLISH VALIDATION ERROR (after socket connected):\n ->`, e);
722
- }
723
- socket.prostgles = socket.prostgles || {};
724
- socket.prostgles.schema = fullSchema?.schema;
725
- /* RUN Raw sql from client IF PUBLISHED
726
- */
727
-
728
- if(this.opts.publishRawSQL && typeof this.opts.publishRawSQL === "function"){
729
- const canRunSQL = async () => {
730
- const publishParams = await this.publishParser?.getPublishParams({ socket })
731
- let res = await this.opts.publishRawSQL?.(publishParams as any);
732
- return Boolean(res && typeof res === "boolean" || res === "*");
733
- }
734
-
735
- if(await canRunSQL()){
736
- socket.removeAllListeners(CHANNELS.SQL)
737
- socket.on(CHANNELS.SQL, async ({ query, params, options }: SQLRequest, cb = (...callback: any) => {}) => {
738
-
739
- if(!this.dbo?.sql) throw "Internal error: sql handler missing";
740
-
741
- this.dbo.sql(query, params, options, { socket }).then(res => {
742
- cb(null, res)
743
- }).catch(err => {
744
- makeSocketError(cb, err);
745
- })
746
- });
747
- if(db){
748
- // let allTablesViews = await db.any(STEP2_GET_ALL_TABLES_AND_COLUMNS);
749
- // fullSchema = allTablesViews;
750
- rawSQL = true;
751
- } else console.error("db missing");
752
- }
767
+ const { schema, tables } = fullSchema ?? { schema: {}, tables: [] };
768
+ let joinTables2: string[][] = [];
769
+ if (this.opts.joins) {
770
+ // joinTables = Array.from(new Set(flat(this.dboBuilder.getJoins().map(j => j.tables)).filter(t => schema[t])));
771
+ let _joinTables2 = this.dboBuilder.getJoinPaths()
772
+ .filter(jp =>
773
+ ![jp.t1, jp.t2].find(t => !schema[t] || !schema[t].findOne)
774
+ ).map(jp => [jp.t1, jp.t2].sort());
775
+ _joinTables2.map(jt => {
776
+ if (!joinTables2.find(_jt => _jt.join() === jt.join())) {
777
+ joinTables2.push(jt);
753
778
  }
779
+ });
780
+ }
754
781
 
755
- const { schema, tables } = fullSchema ?? { schema: {}, tables: [] };
756
- let joinTables2: string[][] = [];
757
- if(this.opts.joins){
758
- // joinTables = Array.from(new Set(flat(this.dboBuilder.getJoins().map(j => j.tables)).filter(t => schema[t])));
759
- let _joinTables2 = this.dboBuilder.getJoinPaths()
760
- .filter(jp =>
761
- ![jp.t1, jp.t2].find(t => !schema[t] || !schema[t].findOne)
762
- ).map(jp => [jp.t1, jp.t2].sort());
763
- _joinTables2.map(jt => {
764
- if(!joinTables2.find(_jt => _jt.join() === jt.join())){
765
- joinTables2.push(jt);
766
- }
767
- });
768
- }
769
-
770
- const methods = await publishParser?.getMethods(socket);
771
- const clientSchema: ClientSchema = {
772
- schema,
773
- methods: getKeys(methods),
774
- tableSchema: tables,
775
- rawSQL,
776
- joinTables: joinTables2,
777
- auth,
778
- version,
779
- err: publishValidationError
780
- }
781
- socket.emit(CHANNELS.SCHEMA, clientSchema);
782
+ const methods = await publishParser?.getMethods(socket);
783
+ const clientSchema: ClientSchema = {
784
+ schema,
785
+ methods: getKeys(methods),
786
+ tableSchema: tables,
787
+ rawSQL,
788
+ joinTables: joinTables2,
789
+ auth,
790
+ version,
791
+ err: publishValidationError
782
792
  }
793
+ socket.emit(CHANNELS.SCHEMA, clientSchema);
794
+ }
783
795
  }
784
- function makeSocketError(cb: Function, err: any){
785
- const err_msg = (err instanceof Error)?
786
- err.toString() :
787
- isPlainObject(err)?
788
- JSON.stringify(err, null, 2) :
789
- (err as any).toString(),
790
- e = { err_msg, err };
791
- cb(e);
796
+ function makeSocketError(cb: Function, err: any) {
797
+ const err_msg = (err instanceof Error) ?
798
+ err.toString() :
799
+ isPlainObject(err) ?
800
+ JSON.stringify(err, null, 2) :
801
+ (err as any).toString(),
802
+ e = { err_msg, err };
803
+ cb(e);
792
804
  }
793
805
 
794
806
  type SocketRequestParams = {
795
- tableName: string;
796
- command: typeof TABLE_METHODS[number];
797
- param1: any;
798
- param2: any;
799
- param3: any;
807
+ tableName: string;
808
+ command: typeof TABLE_METHODS[number];
809
+ param1: any;
810
+ param2: any;
811
+ param3: any;
800
812
  }
801
813
  type SocketMethodRequest = {
802
- method: string;
803
- params: any;
814
+ method: string;
815
+ params: any;
804
816
  }
805
817
 
806
818
  // const ALL_PUBLISH_METHODS = ["update", "upsert", "delete", "insert", "find", "findOne", "subscribe", "unsubscribe", "sync", "unsync", "remove"];
@@ -811,14 +823,14 @@ type SocketMethodRequest = {
811
823
  // let res = arr.reduce(function (farr, toFlatten) {
812
824
  // return farr.concat(Array.isArray(toFlatten) ? flat(toFlatten) : toFlatten);
813
825
  // }, []);
814
-
826
+
815
827
  // return res;
816
828
  // }
817
829
 
818
830
 
819
831
 
820
- export async function isSuperUser(db: DB): Promise<boolean>{
821
- return db.oneOrNone("select usesuper from pg_user where usename = CURRENT_USER;").then(r => r.usesuper);
832
+ export async function isSuperUser(db: DB): Promise<boolean> {
833
+ return db.oneOrNone("select usesuper from pg_user where usename = CURRENT_USER;").then(r => r.usesuper);
822
834
  }
823
835
 
824
836