prostgles-server 4.2.41 → 4.2.42

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 (82) hide show
  1. package/dist/DboBuilder/DboBuilder.d.ts +4 -1
  2. package/dist/DboBuilder/DboBuilder.d.ts.map +1 -1
  3. package/dist/DboBuilder/DboBuilder.js +7 -18
  4. package/dist/DboBuilder/DboBuilder.js.map +1 -1
  5. package/dist/DboBuilder/QueryStreamer.d.ts +1 -1
  6. package/dist/DboBuilder/QueryStreamer.d.ts.map +1 -1
  7. package/dist/DboBuilder/QueryStreamer.js +2 -1
  8. package/dist/DboBuilder/QueryStreamer.js.map +1 -1
  9. package/dist/DboBuilder/dboBuilderUtils.d.ts +1 -1
  10. package/dist/DboBuilder/dboBuilderUtils.d.ts.map +1 -1
  11. package/dist/DboBuilder/dboBuilderUtils.js +3 -3
  12. package/dist/DboBuilder/dboBuilderUtils.js.map +1 -1
  13. package/dist/DboBuilder/runSQL.d.ts +0 -7
  14. package/dist/DboBuilder/runSQL.d.ts.map +1 -1
  15. package/dist/DboBuilder/runSQL.js +3 -18
  16. package/dist/DboBuilder/runSQL.js.map +1 -1
  17. package/dist/PostgresNotifListenManager.d.ts +1 -1
  18. package/dist/PostgresNotifListenManager.d.ts.map +1 -1
  19. package/dist/PostgresNotifListenManager.js +1 -1
  20. package/dist/PostgresNotifListenManager.js.map +1 -1
  21. package/dist/Prostgles.d.ts +8 -20
  22. package/dist/Prostgles.d.ts.map +1 -1
  23. package/dist/Prostgles.js +6 -57
  24. package/dist/Prostgles.js.map +1 -1
  25. package/dist/PubSubManager/PubSubManager.d.ts +13 -20
  26. package/dist/PubSubManager/PubSubManager.d.ts.map +1 -1
  27. package/dist/PubSubManager/PubSubManager.js +74 -80
  28. package/dist/PubSubManager/PubSubManager.js.map +1 -1
  29. package/dist/PubSubManager/addSub.js +0 -59
  30. package/dist/PubSubManager/addSub.js.map +1 -1
  31. package/dist/PubSubManager/getInitQuery.d.ts.map +1 -1
  32. package/dist/PubSubManager/getInitQuery.js +6 -6
  33. package/dist/PubSubManager/getInitQuery.js.map +1 -1
  34. package/dist/PubSubManager/initPubSubManager.d.ts.map +1 -1
  35. package/dist/PubSubManager/initPubSubManager.js +13 -35
  36. package/dist/PubSubManager/initPubSubManager.js.map +1 -1
  37. package/dist/PubSubManager/notifListener.d.ts.map +1 -1
  38. package/dist/PubSubManager/notifListener.js +5 -5
  39. package/dist/PubSubManager/notifListener.js.map +1 -1
  40. package/dist/SchemaWatch/SCHEMA_WATCH_QUERIES.d.ts +2 -0
  41. package/dist/SchemaWatch/SCHEMA_WATCH_QUERIES.d.ts.map +1 -0
  42. package/dist/SchemaWatch/SCHEMA_WATCH_QUERIES.js +53 -0
  43. package/dist/SchemaWatch/SCHEMA_WATCH_QUERIES.js.map +1 -0
  44. package/dist/SchemaWatch/SchemaWatch.d.ts +19 -0
  45. package/dist/SchemaWatch/SchemaWatch.d.ts.map +1 -0
  46. package/dist/SchemaWatch/SchemaWatch.js +71 -0
  47. package/dist/SchemaWatch/SchemaWatch.js.map +1 -0
  48. package/dist/SchemaWatch/getValidatedWatchSchemaType.d.ts +14 -0
  49. package/dist/SchemaWatch/getValidatedWatchSchemaType.d.ts.map +1 -0
  50. package/dist/SchemaWatch/getValidatedWatchSchemaType.js +38 -0
  51. package/dist/SchemaWatch/getValidatedWatchSchemaType.js.map +1 -0
  52. package/dist/SchemaWatch/getWatchSchemaTagList.d.ts +3 -0
  53. package/dist/SchemaWatch/getWatchSchemaTagList.d.ts.map +1 -0
  54. package/dist/SchemaWatch/getWatchSchemaTagList.js +29 -0
  55. package/dist/SchemaWatch/getWatchSchemaTagList.js.map +1 -0
  56. package/dist/SchemaWatch.d.ts +26 -9
  57. package/dist/SchemaWatch.d.ts.map +1 -1
  58. package/dist/SchemaWatch.js +116 -25
  59. package/dist/SchemaWatch.js.map +1 -1
  60. package/dist/initProstgles.d.ts.map +1 -1
  61. package/dist/initProstgles.js +3 -2
  62. package/dist/initProstgles.js.map +1 -1
  63. package/lib/DboBuilder/DboBuilder.ts +12 -21
  64. package/lib/DboBuilder/QueryStreamer.ts +2 -2
  65. package/lib/DboBuilder/dboBuilderUtils.ts +1 -1
  66. package/lib/DboBuilder/runSQL.ts +1 -18
  67. package/lib/PostgresNotifListenManager.ts +1 -1
  68. package/lib/Prostgles.ts +8 -69
  69. package/lib/PubSubManager/PubSubManager.ts +75 -83
  70. package/lib/PubSubManager/addSub.ts +0 -63
  71. package/lib/PubSubManager/getInitQuery.ts +7 -7
  72. package/lib/PubSubManager/initPubSubManager.ts +16 -41
  73. package/lib/PubSubManager/notifListener.ts +6 -6
  74. package/lib/PublishParser/PublishParser.ts +1 -1
  75. package/lib/SchemaWatch/SchemaWatch.ts +79 -0
  76. package/lib/SchemaWatch/getValidatedWatchSchemaType.ts +45 -0
  77. package/lib/SchemaWatch/getWatchSchemaTagList.ts +27 -0
  78. package/lib/initProstgles.ts +6 -4
  79. package/package.json +1 -1
  80. package/tests/server/package-lock.json +1 -1
  81. package/lib/SchemaWatch.ts +0 -40
  82. package/lib/SchemaWatchManager.ts +0 -72
@@ -3,9 +3,9 @@
3
3
  * Licensed under the MIT License. See LICENSE in the project root for license information.
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
 
6
- import { DBHandlerServer, DboBuilder, PRGLIOSocket, TableInfo, TableOrViewInfo, canEXECUTE } from "../DboBuilder/DboBuilder";
6
+ import { DBHandlerServer, DboBuilder, PRGLIOSocket, TableInfo, TableOrViewInfo, getCanExecute } from "../DboBuilder/DboBuilder";
7
7
  import { PostgresNotifListenManager } from "../PostgresNotifListenManager";
8
- import { DB, isSuperUser } from "../Prostgles";
8
+ import { DB, getIsSuperUser } from "../Prostgles";
9
9
  import { addSync } from "./addSync";
10
10
  import { initPubSubManager } from "./initPubSubManager";
11
11
 
@@ -122,7 +122,6 @@ export type PubSubManagerOptions = {
122
122
  dboBuilder: DboBuilder;
123
123
  wsChannelNamePrefix?: string;
124
124
  pgChannelName?: string;
125
- onSchemaChange?: (event: { command: string; query: string }) => void;
126
125
  }
127
126
 
128
127
  export type Subscription = Pick<SubscriptionParams,
@@ -147,20 +146,24 @@ export type Subscription = Pick<SubscriptionParams,
147
146
  }[];
148
147
  }
149
148
 
149
+ /**
150
+ * Used to facilitate table subscribe and sync
151
+ */
150
152
  export class PubSubManager {
151
153
  static DELIMITER = '|$prstgls$|' as const;
152
154
 
153
155
  static EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID = "prostgles internal query that should be excluded from schema watch " as const;
154
156
 
155
157
  public static canCreate = async (db: DB) => {
156
- const canExecute = await canEXECUTE(db);
157
- const isSuperUs = await isSuperUser(db);
158
+ const canExecute = await getCanExecute(db);
159
+ const isSuperUs = await getIsSuperUser(db);
158
160
  return { canExecute, isSuperUs, yes: canExecute && isSuperUs };
159
161
  }
160
162
 
161
163
  public static create = async (options: PubSubManagerOptions) => {
162
- const res = new PubSubManager(options);
163
- return await res.init();
164
+ const instance = new PubSubManager(options);
165
+ const result = await initPubSubManager.bind(instance)();
166
+ return result;
164
167
  }
165
168
 
166
169
  get db(): DB {
@@ -177,17 +180,14 @@ export class PubSubManager {
177
180
  subs: Subscription[] = [];
178
181
  syncs: SyncParams[] = [];
179
182
  socketChannelPreffix: string;
180
- onSchemaChange?: ((event: { command: string; query: string }) => void) = undefined;
181
-
182
183
  postgresNotifListenManager?: PostgresNotifListenManager;
183
184
 
184
185
  private constructor(options: PubSubManagerOptions) {
185
- const { wsChannelNamePrefix, onSchemaChange, dboBuilder } = options;
186
+ const { wsChannelNamePrefix, dboBuilder } = options;
186
187
  if (!dboBuilder.db || !dboBuilder.dbo) {
187
188
  throw 'MISSING: db_pg, db';
188
189
  }
189
190
 
190
- this.onSchemaChange = onSchemaChange;
191
191
  this.dboBuilder = dboBuilder;
192
192
 
193
193
  this.socketChannelPreffix = wsChannelNamePrefix || "_psqlWS_";
@@ -195,20 +195,6 @@ export class PubSubManager {
195
195
  log("Created PubSubManager");
196
196
  }
197
197
 
198
- NOTIF_TYPE = {
199
- data: "data_has_changed",
200
- data_trigger_change: "data_watch_triggers_have_changed",
201
- schema: "schema_has_changed"
202
- } as const;
203
- NOTIF_CHANNEL = {
204
- preffix: 'prostgles_' as const,
205
- getFull: (appID?: string) => {
206
- const finalAppId = appID ?? this.appID;
207
- if (!finalAppId) throw "No appID";
208
- return this.NOTIF_CHANNEL.preffix + finalAppId;
209
- }
210
- }
211
-
212
198
  /**
213
199
  * Used facilitate concurrent prostgles connections to the same database
214
200
  */
@@ -225,13 +211,10 @@ export class PubSubManager {
225
211
  }
226
212
  this.subs = [];
227
213
  this.syncs = [];
228
- if (!this.postgresNotifListenManager) {
229
- throw "this.postgresNotifListenManager missing"
230
- }
231
- this.postgresNotifListenManager.destroy();
214
+ this.postgresNotifListenManager?.destroy();
232
215
  }
233
216
 
234
- canContinue = () => {
217
+ getIsDestroyed = () => {
235
218
  if (this.destroyed) {
236
219
  console.trace("Could not start destroyed instance");
237
220
  return false
@@ -241,13 +224,12 @@ export class PubSubManager {
241
224
 
242
225
  appChecking = false;
243
226
  checkedListenerTableCond?: string[];
244
- init = initPubSubManager.bind(this);
245
227
 
246
228
  initialiseEventTriggers = async () => {
247
229
  if (!this.appID) throw "prepareTriggers failed: this.appID missing";
248
230
 
249
231
  const { watchSchema } = this.dboBuilder.prostgles.opts;
250
- if (watchSchema && !(await isSuperUser(this.db))) {
232
+ if (watchSchema && !(await getIsSuperUser(this.db))) {
251
233
  console.warn("prostgles watchSchema requires superuser db user. Will not watch using event triggers")
252
234
  }
253
235
 
@@ -299,52 +281,7 @@ export class PubSubManager {
299
281
  DELETE FROM prostgles.app_triggers
300
282
  WHERE app_id NOT IN (SELECT id FROM prostgles.apps);
301
283
 
302
- /* DROP the old buggy schema watch trigger */
303
- IF EXISTS (
304
- SELECT 1 FROM pg_catalog.pg_event_trigger
305
- WHERE evtname = 'prostgles_schema_watch_trigger'
306
- ) AND is_super_user IS TRUE
307
- THEN
308
- DROP EVENT TRIGGER IF EXISTS prostgles_schema_watch_trigger;
309
- END IF;
310
-
311
- ev_trg_needed := EXISTS (
312
- SELECT 1 FROM prostgles.apps
313
- WHERE watching_schema_tag_names IS NOT NULL
314
- );
315
- ev_trg_exists := EXISTS (
316
- SELECT 1 FROM pg_catalog.pg_event_trigger
317
- WHERE evtname = ${asValue(DB_OBJ_NAMES.schema_watch_trigger)}
318
- );
319
-
320
- /* DROP stale event trigger */
321
- IF
322
- is_super_user IS TRUE
323
- AND ev_trg_needed IS FALSE
324
- AND ev_trg_exists IS TRUE
325
- THEN
326
-
327
- SELECT format(
328
- $$ DROP EVENT TRIGGER IF EXISTS %I ; $$
329
- , ${asValue(DB_OBJ_NAMES.schema_watch_trigger)}
330
- )
331
- INTO q;
332
- EXECUTE q;
333
-
334
- /* CREATE event trigger */
335
- ELSIF
336
- is_super_user IS TRUE
337
- AND ev_trg_needed IS TRUE
338
- AND ev_trg_exists IS FALSE
339
- THEN
340
-
341
- DROP EVENT TRIGGER IF EXISTS ${DB_OBJ_NAMES.schema_watch_trigger};
342
- CREATE EVENT TRIGGER ${DB_OBJ_NAMES.schema_watch_trigger} ON ddl_command_end
343
- WHEN TAG IN (\${EVENT_TRIGGER_TAGS:csv})
344
- EXECUTE PROCEDURE ${DB_OBJ_NAMES.schema_watch_func}();
345
-
346
- END IF;
347
-
284
+ ${SCHEMA_WATCH_EVENT_TRIGGER_QUERY}
348
285
 
349
286
  END
350
287
  $do$;
@@ -367,11 +304,6 @@ export class PubSubManager {
367
304
  }
368
305
  }
369
306
 
370
- isReady() {
371
- if (!this.postgresNotifListenManager) throw "this.postgresNotifListenManager missing";
372
- return this.postgresNotifListenManager.isListening();
373
- }
374
-
375
307
  getClientSubs(client: Pick<Subscription, "localFuncs" | "socket_id" | "channel_name">): Subscription[] {
376
308
  return this.subs.filter(s => {
377
309
  return s.channel_name === client.channel_name && (matchesLocalFuncs(client.localFuncs, s.localFuncs) || client.socket_id && s.socket_id === client.socket_id)
@@ -577,6 +509,66 @@ export class PubSubManager {
577
509
  }
578
510
  }
579
511
 
512
+ const SCHEMA_WATCH_EVENT_TRIGGER_QUERY = `
513
+ /* DROP the old buggy schema watch trigger */
514
+ IF EXISTS (
515
+ SELECT 1 FROM pg_catalog.pg_event_trigger
516
+ WHERE evtname = 'prostgles_schema_watch_trigger'
517
+ ) AND is_super_user IS TRUE
518
+ THEN
519
+ DROP EVENT TRIGGER IF EXISTS prostgles_schema_watch_trigger;
520
+ END IF;
521
+
522
+ ev_trg_needed := EXISTS (
523
+ SELECT 1 FROM prostgles.apps
524
+ WHERE watching_schema_tag_names IS NOT NULL
525
+ );
526
+ ev_trg_exists := EXISTS (
527
+ SELECT 1 FROM pg_catalog.pg_event_trigger
528
+ WHERE evtname = ${asValue(DB_OBJ_NAMES.schema_watch_trigger)}
529
+ );
530
+
531
+ /* DROP stale event trigger */
532
+ IF
533
+ is_super_user IS TRUE
534
+ AND ev_trg_needed IS FALSE
535
+ AND ev_trg_exists IS TRUE
536
+ THEN
537
+
538
+ SELECT format(
539
+ $$ DROP EVENT TRIGGER IF EXISTS %I ; $$
540
+ , ${asValue(DB_OBJ_NAMES.schema_watch_trigger)}
541
+ )
542
+ INTO q;
543
+ EXECUTE q;
544
+
545
+ /* CREATE event trigger */
546
+ ELSIF
547
+ is_super_user IS TRUE
548
+ AND ev_trg_needed IS TRUE
549
+ AND ev_trg_exists IS FALSE
550
+ THEN
551
+
552
+ DROP EVENT TRIGGER IF EXISTS ${DB_OBJ_NAMES.schema_watch_trigger};
553
+ CREATE EVENT TRIGGER ${DB_OBJ_NAMES.schema_watch_trigger} ON ddl_command_end
554
+ WHEN TAG IN (\${EVENT_TRIGGER_TAGS:csv})
555
+ EXECUTE PROCEDURE ${DB_OBJ_NAMES.schema_watch_func}();
556
+
557
+ END IF;
558
+ `
559
+
560
+ export const NOTIF_TYPE = {
561
+ data: "data_has_changed",
562
+ data_trigger_change: "data_watch_triggers_have_changed",
563
+ schema: "schema_has_changed"
564
+ } as const;
565
+ export const NOTIF_CHANNEL = {
566
+ preffix: 'prostgles_' as const,
567
+ getFull: (appID: string | undefined) => {
568
+ if (!appID) throw "No appID";
569
+ return NOTIF_CHANNEL.preffix + appID;
570
+ }
571
+ }
580
572
 
581
573
  export const parseCondition = (condition: string): string => condition && condition.trim().length ? condition : "TRUE"
582
574
 
@@ -127,66 +127,3 @@ export async function addSub(this: PubSubManager, subscriptionParams: Omit<AddSu
127
127
 
128
128
  return result;
129
129
  }
130
-
131
- // const _upsertSub = async function(
132
- // this: PubSubManager,
133
- // newSubData: {
134
- // table_name: string;
135
- // condition: string;
136
- // is_ready: boolean;
137
- // channel_name: string;
138
- // viewOptions: SubscriptionParams["viewOptions"];
139
- // },
140
- // consumer: Pick<SubscriptionParams, "channel_name" | "socket" | "func">,
141
- // isReadyOverride: boolean | undefined
142
- // ){
143
- // const { table_name, condition: _cond, is_ready = false, viewOptions } = newSubData;
144
- // const { channel_name, func, socket } = consumer;
145
- // const condition = parseCondition(_cond);
146
- // const newSub: SubscriptionParams = {
147
- // socket,
148
- // table_name,
149
- // filter,
150
- // params,
151
- // table_rules,
152
- // channel_name,
153
- // parentSubParams,
154
- // func: func ?? undefined,
155
- // socket_id: socket?.id,
156
- // throttle: validated_throttle,
157
- // is_throttling: null,
158
- // last_throttled: 0,
159
- // is_ready,
160
- // };
161
-
162
- // this.subs[table_name] = this.subs[table_name] ?? {};
163
- // this.subs[table_name]![condition] = this.subs[table_name]![condition] ?? { subs: [] };
164
- // this.subs[table_name]![condition]!.subs = this.subs[table_name]![condition]!.subs ?? [];
165
-
166
- // // console.log("1034 upsertSub", this.subs)
167
- // const sub_idx = this.subs[table_name]![condition]!.subs.findIndex(s =>
168
- // s.channel_name === channel_name &&
169
- // (
170
- // socket && s.socket_id === socket.id ||
171
- // func && s.func === func
172
- // ) &&
173
- // JSON.stringify(s.viewOptions) === JSON.stringify((subscriptionParams.viewOptions)
174
- // ));
175
- // if (sub_idx < 0) {
176
- // this.subs[table_name]![condition]!.subs.push(newSub);
177
- // if (socket) {
178
- // const chnUnsub = channel_name + "unsubscribe";
179
- // socket.removeAllListeners(chnUnsub);
180
- // socket.once(chnUnsub, (_data: any, cb: BasicCallback) => {
181
- // const res = this.onSocketDisconnected({ socket, subChannel: channel_name });
182
- // cb(null, { res });
183
- // });
184
- // }
185
- // } else {
186
- // this.subs[table_name]![condition]!.subs[sub_idx] = newSub;
187
- // }
188
-
189
- // if (isReadyOverride ?? is_ready) {
190
- // this.pushSubData(newSub);
191
- // }
192
- // };
@@ -1,5 +1,5 @@
1
1
 
2
- import { asValue, PubSubManager } from "./PubSubManager";
2
+ import { asValue, NOTIF_CHANNEL, NOTIF_TYPE, PubSubManager } from "./PubSubManager";
3
3
  const { version } = require("../../package.json");
4
4
 
5
5
  export const DB_OBJ_NAMES = {
@@ -289,11 +289,11 @@ BEGIN
289
289
  LOOP
290
290
 
291
291
  PERFORM pg_notify(
292
- ${asValue(this.NOTIF_CHANNEL.preffix)} || v_trigger.app_id ,
292
+ ${asValue(NOTIF_CHANNEL.preffix)} || v_trigger.app_id ,
293
293
  LEFT(concat_ws(
294
294
  ${asValue(PubSubManager.DELIMITER)},
295
295
 
296
- ${asValue(this.NOTIF_TYPE.data)},
296
+ ${asValue(NOTIF_TYPE.data)},
297
297
  COALESCE(TG_TABLE_NAME, 'MISSING'),
298
298
  COALESCE(TG_OP, 'MISSING'),
299
299
  CASE WHEN has_errors
@@ -487,10 +487,10 @@ BEGIN
487
487
  SELECT * FROM prostgles.apps
488
488
  LOOP
489
489
  PERFORM pg_notify(
490
- ${asValue(this.NOTIF_CHANNEL.preffix)} || app.id,
490
+ ${asValue(NOTIF_CHANNEL.preffix)} || app.id,
491
491
  LEFT(concat_ws(
492
492
  ${asValue(PubSubManager.DELIMITER)},
493
- ${asValue(this.NOTIF_TYPE.data_trigger_change)},
493
+ ${asValue(NOTIF_TYPE.data_trigger_change)},
494
494
  json_build_object(
495
495
  'TG_OP', TG_OP,
496
496
  'duration', (EXTRACT(EPOCH FROM now()) * 1000) - start_time,
@@ -551,10 +551,10 @@ BEGIN
551
551
  WHERE tg_tag = ANY(watching_schema_tag_names)
552
552
  LOOP
553
553
  PERFORM pg_notify(
554
- ${asValue(this.NOTIF_CHANNEL.preffix)} || app.id,
554
+ ${asValue(NOTIF_CHANNEL.preffix)} || app.id,
555
555
  LEFT(concat_ws(
556
556
  ${asValue(PubSubManager.DELIMITER)},
557
- ${asValue(this.NOTIF_TYPE.schema)}, tg_tag , TG_event, curr_query
557
+ ${asValue(NOTIF_TYPE.schema)}, tg_tag , TG_event, curr_query
558
558
  ), 7999/4)
559
559
  );
560
560
  END LOOP;
@@ -1,50 +1,23 @@
1
- import { getKeys, isObject } from "prostgles-types";
2
1
  import { PostgresNotifListenManager } from "../PostgresNotifListenManager";
3
- import { asValue, log, PubSubManager } from "./PubSubManager";
2
+ import { getWatchSchemaTagList } from "../SchemaWatch/getWatchSchemaTagList";
3
+ import { asValue, log, NOTIF_CHANNEL, PubSubManager } from "./PubSubManager";
4
4
  const REALTIME_TRIGGER_CHECK_QUERY = "prostgles-server internal query used to manage realtime triggers" as const;
5
5
  import { getInitQuery } from "./getInitQuery";
6
- import { ProstglesInitOptions } from "../Prostgles";
7
- import { EVENT_TRIGGER_TAGS } from "../Event_Trigger_Tags";
8
-
9
- const getWatchSchemaTagList = (watchSchema: ProstglesInitOptions["watchSchema"]) => {
10
- if(!watchSchema) return undefined;
11
-
12
- if(watchSchema === "*"){
13
- return EVENT_TRIGGER_TAGS.slice(0);
14
- }
15
- if (isObject(watchSchema) && typeof watchSchema !== "function"){
16
- const watchSchemaKeys = getKeys(watchSchema);
17
- const isInclusive = Object.values(watchSchema).every(v => v);
18
- return EVENT_TRIGGER_TAGS
19
- .slice(0)
20
- .filter(v => {
21
- const matches = watchSchemaKeys.includes(v);
22
- return isInclusive? matches : !matches;
23
- });
24
- }
25
-
26
- const coreTags: typeof EVENT_TRIGGER_TAGS[number][] = [
27
- 'COMMENT', 'CREATE TABLE', 'ALTER TABLE', 'DROP TABLE', 'CREATE VIEW',
28
- 'DROP VIEW', 'ALTER VIEW', 'CREATE TABLE AS', 'SELECT INTO', 'CREATE POLICY'
29
- ];
30
- return coreTags;
31
- }
32
6
 
33
7
  export async function initPubSubManager(this: PubSubManager): Promise<PubSubManager | undefined> {
34
- if (!this.canContinue()) return undefined;
8
+ if (!this.getIsDestroyed()) return undefined;
35
9
 
36
10
  let tries = 5;
37
11
  try {
38
12
 
39
13
  const initQuery = await getInitQuery.bind(this)()
40
14
  await this.db.any(initQuery);
41
- if (!this.canContinue()) return;
42
-
15
+ if (!this.getIsDestroyed()) return;
43
16
 
44
17
  /* Prepare App id */
45
18
  if (!this.appID) {
46
19
  const check_frequency_ms = this.appCheckFrequencyMS;
47
- const watching_schema_tag_names = this.onSchemaChange? getWatchSchemaTagList(this.dboBuilder.prostgles.opts.watchSchema) : null;
20
+ const watching_schema_tag_names = this.dboBuilder.prostgles.schemaWatch?.type.watchType !== "NONE" ? getWatchSchemaTagList(this.dboBuilder.prostgles.opts.watchSchema) : null;
48
21
  const raw = await this.db.one(
49
22
  "INSERT INTO prostgles.apps (check_frequency_ms, watching_schema_tag_names, application_name) \
50
23
  VALUES($1, $2, current_setting('application_name')) \
@@ -65,6 +38,14 @@ export async function initPubSubManager(this: PubSubManager): Promise<PubSubMana
65
38
  this.appChecking = true;
66
39
 
67
40
  const listeners = this.getActiveListeners();
41
+ const updateCurrentlyUsedTriggersQuery = !listeners.length? "" : `
42
+ UPDATE prostgles.app_triggers
43
+ SET last_used = CASE WHEN (table_name, condition) IN (
44
+ ${listeners.map(l => ` ( ${asValue(l.table_name)}, ${asValue(l.condition)} ) `).join(", ")}
45
+ ) THEN NOW() ELSE last_used END
46
+ WHERE app_id = ${asValue(this.appID)};
47
+ `;
48
+
68
49
  const checkedListenerTableCond = listeners.map(l => `${l.table_name}.${l.condition}`);
69
50
  let dataTriggerCheckQuery = "";
70
51
  if(this.checkedListenerTableCond?.sort().join() !== checkedListenerTableCond.sort().join()){
@@ -79,13 +60,7 @@ export async function initPubSubManager(this: PubSubManager): Promise<PubSubMana
79
60
  --LOCK TABLE prostgles.app_triggers IN ACCESS EXCLUSIVE MODE;
80
61
 
81
62
  /* UPDATE currently used triggers */
82
- ${!listeners.length? "" : `
83
- UPDATE prostgles.app_triggers
84
- SET last_used = CASE WHEN (table_name, condition) IN (
85
- ${listeners.map(l => ` ( ${asValue(l.table_name)}, ${asValue(l.condition)} ) `).join(", ")}
86
- ) THEN NOW() ELSE last_used END
87
- WHERE app_id = ${asValue(this.appID)};
88
- `}
63
+ ${updateCurrentlyUsedTriggersQuery}
89
64
 
90
65
  /* DELETE stale triggers for current app. Other triggers will be deleted on app startup */
91
66
  DELETE FROM prostgles.app_triggers
@@ -175,9 +150,9 @@ export async function initPubSubManager(this: PubSubManager): Promise<PubSubMana
175
150
  }
176
151
  }
177
152
 
178
- this.postgresNotifListenManager = new PostgresNotifListenManager(this.db, this.notifListener, this.NOTIF_CHANNEL.getFull());
153
+ this.postgresNotifListenManager = new PostgresNotifListenManager(this.db, this.notifListener, NOTIF_CHANNEL.getFull(this.appID));
179
154
 
180
- await this.initialiseEventTriggers()
155
+ await this.initialiseEventTriggers();
181
156
 
182
157
  return this;
183
158
 
@@ -1,4 +1,4 @@
1
- import { log, pickKeys, PubSubManager } from "./PubSubManager";
1
+ import { log, NOTIF_TYPE, pickKeys, PubSubManager } from "./PubSubManager";
2
2
 
3
3
  /* Relay relevant data to relevant subscriptions */
4
4
  export async function notifListener(this: PubSubManager, data: { payload: string }) {
@@ -14,25 +14,25 @@ export async function notifListener(this: PubSubManager, data: { payload: string
14
14
 
15
15
  log(str);
16
16
 
17
- if (notifType === this.NOTIF_TYPE.schema) {
17
+ if (notifType === NOTIF_TYPE.schema) {
18
18
 
19
- if (this.onSchemaChange) {
19
+ if (this.dboBuilder.prostgles.schemaWatch?.onSchemaChange) {
20
20
  const [_, command, _event_type, query] = dataArr;
21
21
  await this.dboBuilder.prostgles.opts.onLog?.({ type: "debug", command: "schemaChangeNotif", duration: 0, data: { command, query } });
22
22
 
23
23
  if (query && command) {
24
- this.onSchemaChange({ command, query })
24
+ this.dboBuilder.prostgles.schemaWatch.onSchemaChange({ command, query })
25
25
  }
26
26
  }
27
27
 
28
28
  return;
29
- } else if(notifType === this.NOTIF_TYPE.data_trigger_change) {
29
+ } else if(notifType === NOTIF_TYPE.data_trigger_change) {
30
30
 
31
31
  await this.refreshTriggers();
32
32
  return;
33
33
  }
34
34
 
35
- if (notifType !== this.NOTIF_TYPE.data) {
35
+ if (notifType !== NOTIF_TYPE.data) {
36
36
  console.error("Unexpected notif type: ", notifType);
37
37
  return;
38
38
  }
@@ -6,7 +6,7 @@ import { getFileTableRules } from "./getFileTableRules";
6
6
  import { getSchemaFromPublish } from "./getSchemaFromPublish";
7
7
  import { getTableRulesWithoutFileTable } from "./getTableRulesWithoutFileTable";
8
8
  import { DboTable, DboTableCommand, ParsedPublishTable, PublishMethods, PublishObject, PublishParams, RULE_TO_METHODS, TableRule } from "./publishTypesAndUtils";
9
- import { VoidFunction } from "../SchemaWatch";
9
+ import { VoidFunction } from "../SchemaWatch/SchemaWatch";
10
10
 
11
11
  export class PublishParser {
12
12
  publish: any;
@@ -0,0 +1,79 @@
1
+ import type { DboBuilder } from "../DboBuilder/DboBuilder";
2
+ import { EVENT_TRIGGER_TAGS } from "../Event_Trigger_Tags";
3
+ import { OnSchemaChangeCallback } from "../Prostgles";
4
+ import { PubSubManager, log } from "../PubSubManager/PubSubManager";
5
+ import { ValidatedWatchSchemaType, getValidatedWatchSchemaType } from "./getValidatedWatchSchemaType";
6
+
7
+ export type VoidFunction = () => void;
8
+
9
+ export class SchemaWatch {
10
+
11
+ dboBuilder: DboBuilder;
12
+ type: ValidatedWatchSchemaType;
13
+ private constructor(dboBuilder: DboBuilder){
14
+ this.dboBuilder = dboBuilder;
15
+ this.type = getValidatedWatchSchemaType(dboBuilder);
16
+ if(this.type.watchType === "NONE") {
17
+ this.onSchemaChange = undefined;
18
+ }
19
+ }
20
+
21
+ static create = async (dboBuilder: DboBuilder) => {
22
+ const instance = new SchemaWatch(dboBuilder);
23
+ if(instance.type.watchType === "DDL_trigger") {
24
+ await dboBuilder.getPubSubManager()
25
+ }
26
+ return instance;
27
+ }
28
+
29
+ onSchemaChange: OnSchemaChangeCallback | undefined = async (event) => {
30
+ if(this.type.watchType === "NONE") return;
31
+
32
+ const { watchSchema, onReady, tsGeneratedTypesDir } = this.dboBuilder.prostgles.opts;
33
+ if (watchSchema && this.dboBuilder.prostgles.loaded) {
34
+ log("Schema changed");
35
+ const { query, command } = event;
36
+ if (typeof query === "string" && query.includes(PubSubManager.EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID)) {
37
+ log("Schema change event excluded from triggers due to EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID");
38
+ return;
39
+ }
40
+
41
+ if (typeof watchSchema === "function") {
42
+ /* Only call the provided func */
43
+ watchSchema(event);
44
+
45
+ } else if (watchSchema === "hotReloadMode") {
46
+ if (tsGeneratedTypesDir) {
47
+ /* Hot reload integration. Will only touch tsGeneratedTypesDir */
48
+ console.log("watchSchema: Re-writing TS schema");
49
+
50
+ await this.dboBuilder.prostgles.refreshDBO();
51
+ this.dboBuilder.prostgles.writeDBSchema(true);
52
+ }
53
+
54
+ } else if (watchSchema) {
55
+ /* Full re-init. Sockets must reconnect */
56
+ console.log("watchSchema: Full re-initialisation", { query })
57
+ this.dboBuilder.prostgles.init(onReady as any, { type: "schema change", query, command });
58
+ }
59
+ }
60
+ };
61
+ }
62
+
63
+
64
+ /**
65
+ * Fallback for watchSchema in case of not a superuser (cannot add db event listener)
66
+ */
67
+ export const watchSchemaFallback = async function(this: DboBuilder, { queryWithoutRLS, command }: { queryWithoutRLS: string; command: string; }){
68
+ const SCHEMA_ALTERING_COMMANDS = EVENT_TRIGGER_TAGS;// ["CREATE", "ALTER", "DROP", "REVOKE", "GRANT"];
69
+ const isNotPickedUpByDDLTrigger = ["REVOKE", "GRANT"].includes(command);
70
+ const { watchSchema, watchSchemaType } = this.prostgles?.opts || {};
71
+ if (
72
+ watchSchema &&
73
+ (!this.prostgles.isSuperUser || watchSchemaType === "prostgles_queries" || isNotPickedUpByDDLTrigger)
74
+ ) {
75
+ if (SCHEMA_ALTERING_COMMANDS.includes(command as any)) {
76
+ this.prostgles.schemaWatch?.onSchemaChange?.({ command, query: queryWithoutRLS })
77
+ }
78
+ }
79
+ }
@@ -0,0 +1,45 @@
1
+ import type { DboBuilder } from "../DboBuilder/DboBuilder";
2
+ import { OnSchemaChangeCallback } from "../Prostgles";
3
+
4
+ export type ValidatedWatchSchemaType =
5
+ | { watchType: "NONE" }
6
+ | { watchType: "DDL_trigger"; onChange?: OnSchemaChangeCallback; }
7
+ | { watchType: "prostgles_queries"; onChange?: OnSchemaChangeCallback; isFallbackFromDDL: boolean; }
8
+
9
+ export const getValidatedWatchSchemaType = (dboBuilder: DboBuilder): ValidatedWatchSchemaType => {
10
+ const {watchSchema, watchSchemaType, tsGeneratedTypesDir} = dboBuilder.prostgles.opts;
11
+ if(!watchSchema) return { watchType: "NONE" };
12
+
13
+ if (watchSchema === "hotReloadMode" && !tsGeneratedTypesDir) {
14
+ throw "tsGeneratedTypesDir option is needed for watchSchema: hotReloadMode to work ";
15
+ }
16
+
17
+ const onChange = typeof watchSchema === "function"? watchSchema : undefined;
18
+
19
+ if(watchSchemaType === "DDL_trigger" || !watchSchemaType){
20
+ if(!dboBuilder.prostgles.isSuperUser){
21
+
22
+ if(watchSchemaType === "DDL_trigger"){
23
+ console.error(`watchSchemaType "DDL_trigger" cannot be used because db user is not a superuser. Will fallback to watchSchemaType "prostgles_queries" `)
24
+ } else {
25
+ console.warn(`watchSchema fallback to watchSchemaType "prostgles_queries" due to non-superuser`)
26
+ }
27
+ return {
28
+ watchType: "prostgles_queries",
29
+ onChange,
30
+ isFallbackFromDDL: true
31
+ }
32
+ }
33
+
34
+ return {
35
+ watchType: "DDL_trigger",
36
+ onChange
37
+ };
38
+ }
39
+
40
+ return {
41
+ watchType: watchSchemaType,
42
+ isFallbackFromDDL: false,
43
+ onChange
44
+ }
45
+ }
@@ -0,0 +1,27 @@
1
+ import { getKeys, isObject } from "prostgles-types";
2
+ import { EVENT_TRIGGER_TAGS } from "../Event_Trigger_Tags";
3
+ import { ProstglesInitOptions } from "../Prostgles";
4
+
5
+ export const getWatchSchemaTagList = (watchSchema: ProstglesInitOptions["watchSchema"]) => {
6
+ if(!watchSchema) return undefined;
7
+
8
+ if(watchSchema === "*"){
9
+ return EVENT_TRIGGER_TAGS.slice(0);
10
+ }
11
+ if (isObject(watchSchema) && typeof watchSchema !== "function"){
12
+ const watchSchemaKeys = getKeys(watchSchema);
13
+ const isInclusive = Object.values(watchSchema).every(v => v);
14
+ return EVENT_TRIGGER_TAGS
15
+ .slice(0)
16
+ .filter(v => {
17
+ const matches = watchSchemaKeys.includes(v);
18
+ return isInclusive? matches : !matches;
19
+ });
20
+ }
21
+
22
+ const coreTags: typeof EVENT_TRIGGER_TAGS[number][] = [
23
+ 'COMMENT', 'CREATE TABLE', 'ALTER TABLE', 'DROP TABLE', 'CREATE VIEW',
24
+ 'DROP VIEW', 'ALTER VIEW', 'CREATE TABLE AS', 'SELECT INTO', 'CREATE POLICY'
25
+ ];
26
+ return coreTags;
27
+ }