prostgles-server 4.2.234 → 4.2.236

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 (46) hide show
  1. package/dist/Auth/AuthTypes.d.ts +2 -2
  2. package/dist/Auth/AuthTypes.d.ts.map +1 -1
  3. package/dist/Auth/endpoints/setMagicLinkOrOTPRequestHandler.d.ts.map +1 -1
  4. package/dist/Auth/endpoints/setMagicLinkOrOTPRequestHandler.js +49 -47
  5. package/dist/Auth/endpoints/setMagicLinkOrOTPRequestHandler.js.map +1 -1
  6. package/dist/Auth/endpoints/setRegisterRequestHandler.js +1 -1
  7. package/dist/Auth/endpoints/setRegisterRequestHandler.js.map +1 -1
  8. package/dist/DboBuilder/ViewHandler/subscribe.d.ts.map +1 -1
  9. package/dist/DboBuilder/ViewHandler/subscribe.js.map +1 -1
  10. package/dist/Logging.d.ts +6 -3
  11. package/dist/Logging.d.ts.map +1 -1
  12. package/dist/PubSubManager/PubSubManager.d.ts +12 -3
  13. package/dist/PubSubManager/PubSubManager.d.ts.map +1 -1
  14. package/dist/PubSubManager/PubSubManager.js +20 -237
  15. package/dist/PubSubManager/PubSubManager.js.map +1 -1
  16. package/dist/PubSubManager/addTrigger.d.ts +21 -0
  17. package/dist/PubSubManager/addTrigger.d.ts.map +1 -0
  18. package/dist/PubSubManager/addTrigger.js +83 -0
  19. package/dist/PubSubManager/addTrigger.js.map +1 -0
  20. package/dist/PubSubManager/deleteOrphanedTriggers.d.ts +3 -0
  21. package/dist/PubSubManager/deleteOrphanedTriggers.d.ts.map +1 -0
  22. package/dist/PubSubManager/deleteOrphanedTriggers.js +32 -0
  23. package/dist/PubSubManager/deleteOrphanedTriggers.js.map +1 -0
  24. package/dist/PubSubManager/initialiseEventTriggers.d.ts +3 -0
  25. package/dist/PubSubManager/initialiseEventTriggers.d.ts.map +1 -0
  26. package/dist/PubSubManager/initialiseEventTriggers.js +150 -0
  27. package/dist/PubSubManager/initialiseEventTriggers.js.map +1 -0
  28. package/dist/PubSubManager/notifListener.d.ts.map +1 -1
  29. package/dist/PubSubManager/notifListener.js +5 -26
  30. package/dist/PubSubManager/notifListener.js.map +1 -1
  31. package/dist/PubSubManager/refreshTriggers.d.ts +3 -0
  32. package/dist/PubSubManager/refreshTriggers.d.ts.map +1 -0
  33. package/dist/PubSubManager/refreshTriggers.js +31 -0
  34. package/dist/PubSubManager/refreshTriggers.js.map +1 -0
  35. package/lib/Auth/AuthTypes.ts +2 -2
  36. package/lib/Auth/endpoints/setMagicLinkOrOTPRequestHandler.ts +48 -48
  37. package/lib/Auth/endpoints/setRegisterRequestHandler.ts +1 -1
  38. package/lib/DboBuilder/ViewHandler/subscribe.ts +0 -1
  39. package/lib/Logging.ts +7 -3
  40. package/lib/PubSubManager/PubSubManager.ts +27 -266
  41. package/lib/PubSubManager/addTrigger.ts +94 -0
  42. package/lib/PubSubManager/deleteOrphanedTriggers.ts +31 -0
  43. package/lib/PubSubManager/initialiseEventTriggers.ts +156 -0
  44. package/lib/PubSubManager/notifListener.ts +5 -29
  45. package/lib/PubSubManager/refreshTriggers.ts +38 -0
  46. package/package.json +1 -1
@@ -0,0 +1,94 @@
1
+ import { tryCatchV2 } from "prostgles-types";
2
+ import { asValue, PubSubManager, ViewSubscriptionOptions } from "./PubSubManager";
3
+ import * as crypto from "crypto";
4
+ import { PRGLIOSocket } from "../DboBuilder/DboBuilderTypes";
5
+
6
+ export async function addTrigger(
7
+ this: PubSubManager,
8
+ params: { table_name: string; condition: string },
9
+ viewOptions: ViewSubscriptionOptions | undefined,
10
+ socket: PRGLIOSocket | undefined
11
+ ) {
12
+ const addedTrigger = await tryCatchV2(async () => {
13
+ const { table_name } = { ...params };
14
+ let { condition } = { ...params };
15
+ if (!table_name) throw "MISSING table_name";
16
+
17
+ if (!condition || !condition.trim().length) {
18
+ condition = "TRUE";
19
+ }
20
+
21
+ if (this.dbo[table_name]?.tableOrViewInfo?.isHyperTable) {
22
+ throw "Triggers do not work on timescaledb hypertables due to bug:\nhttps://github.com/timescale/timescaledb/issues/1084";
23
+ }
24
+
25
+ const trgVals = {
26
+ tbl: asValue(table_name),
27
+ cond: asValue(condition),
28
+ condHash: asValue(crypto.createHash("md5").update(condition).digest("hex")),
29
+ };
30
+
31
+ await this.db.tx((t) =>
32
+ t.any(`
33
+ BEGIN WORK;
34
+ /* ${PubSubManager.EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID} */
35
+ /* why is this lock level needed? */
36
+ --LOCK TABLE prostgles.app_triggers IN ACCESS EXCLUSIVE MODE;
37
+
38
+ /** app_triggers is not refreshed when tables are dropped */
39
+ DELETE FROM prostgles.app_triggers at
40
+ WHERE app_id = ${asValue(this.appId)}
41
+ AND NOT EXISTS (
42
+ SELECT 1
43
+ FROM pg_catalog.pg_trigger t
44
+ WHERE tgname like format('prostgles_triggers_%s_', at.table_name) || '%'
45
+ AND tgenabled = 'O'
46
+ );
47
+
48
+ INSERT INTO prostgles.app_triggers (
49
+ table_name,
50
+ condition,
51
+ condition_hash,
52
+ app_id,
53
+ related_view_name,
54
+ related_view_def
55
+ )
56
+ VALUES (
57
+ ${trgVals.tbl},
58
+ ${trgVals.cond},
59
+ ${trgVals.condHash},
60
+ ${asValue(this.appId)},
61
+ ${asValue(viewOptions?.viewName ?? null)},
62
+ ${asValue(viewOptions?.definition ?? null)}
63
+ )
64
+ ON CONFLICT DO NOTHING;
65
+
66
+ COMMIT WORK;
67
+ `)
68
+ );
69
+
70
+ /** This might be redundant due to trigger on app_triggers */
71
+ await this.refreshTriggers();
72
+
73
+ return trgVals;
74
+ });
75
+
76
+ await this._log({
77
+ type: "syncOrSub",
78
+ command: "addTrigger",
79
+ condition: addedTrigger.data?.cond ?? params.condition,
80
+ duration: addedTrigger.duration,
81
+ socketId: socket?.id,
82
+ state: !addedTrigger.data?.tbl ? "fail" : "ok",
83
+ error: addedTrigger.error,
84
+ sid: socket && this.dboBuilder.prostgles.authHandler?.getSIDNoError({ socket }),
85
+ tableName: addedTrigger.data?.tbl ?? params.table_name,
86
+ connectedSocketIds: this.dboBuilder.prostgles.connectedSockets.map((s) => s.id),
87
+ localParams: socket && { clientReq: { socket } },
88
+ triggers: this._triggers,
89
+ });
90
+
91
+ if (addedTrigger.error) throw addedTrigger.error;
92
+
93
+ return addedTrigger;
94
+ }
@@ -0,0 +1,31 @@
1
+ import { PubSubManager } from "./PubSubManager";
2
+
3
+ export async function deleteOrphanedTriggers(this: PubSubManager, tableName: string) {
4
+ const activeConditions = (this._triggers?.[tableName] ?? []).map((t) => t.hash);
5
+ this.db
6
+ .any(
7
+ `
8
+ /* Delete removed subscriptions */
9
+ /* ${PubSubManager.EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID} */
10
+ DELETE FROM prostgles.app_triggers at
11
+ WHERE EXISTS (
12
+ SELECT 1
13
+ FROM prostgles.v_triggers t
14
+ WHERE t.table_name = $1
15
+ --AND t.c_id IN ($2:csv)
16
+ AND t.app_id = $3
17
+ AND at.app_id = t.app_id
18
+ AND at.table_name = t.table_name
19
+ --AND at.condition = t.condition
20
+ AND at.condition_hash NOT IN ($2:csv)
21
+ )
22
+ `,
23
+ [tableName, activeConditions, this.appId]
24
+ )
25
+ .then(() => {
26
+ return this.refreshTriggers();
27
+ })
28
+ .catch((e) => {
29
+ console.error("Error deleting orphaned triggers", e);
30
+ });
31
+ }
@@ -0,0 +1,156 @@
1
+ import * as pgp from "pg-promise";
2
+ import { PubSubManager } from "./PubSubManager";
3
+ import { getIsSuperUser } from "../Prostgles";
4
+ import { EVENT_TRIGGER_TAGS } from "../Event_Trigger_Tags";
5
+ import { DELETE_DISCONNECTED_APPS_QUERY } from "./orphanTriggerCheck";
6
+ import { DB_OBJ_NAMES } from "./getPubSubManagerInitQuery";
7
+
8
+ const asValue = (v: any) => pgp.as.format("$1", [v]);
9
+
10
+ export async function initialiseEventTriggers(this: PubSubManager) {
11
+ const { watchSchema } = this.dboBuilder.prostgles.opts;
12
+ if (watchSchema && !(await getIsSuperUser(this.db))) {
13
+ console.warn(
14
+ "prostgles watchSchema requires superuser db user. Will not watch using event triggers"
15
+ );
16
+ }
17
+
18
+ try {
19
+ /** We use these names because they include schema where necessary */
20
+ const allTableNames = Object.keys(this.dbo).filter((k) => this.dbo[k]?.tableOrViewInfo);
21
+ const tableFilterQuery =
22
+ allTableNames.length ?
23
+ `OR table_name NOT IN (${allTableNames.map((tblName) => asValue(tblName)).join(", ")})`
24
+ : "";
25
+ const query = pgp.as.format(
26
+ `
27
+ BEGIN;-- ISOLATION LEVEL SERIALIZABLE;
28
+
29
+ /**
30
+ * ${PubSubManager.EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID}
31
+ * Drop stale triggers
32
+ * */
33
+ DO
34
+ $do$
35
+ DECLARE trg RECORD;
36
+ q TEXT;
37
+ ev_trg_needed BOOLEAN := FALSE;
38
+ ev_trg_exists BOOLEAN := FALSE;
39
+ is_super_user BOOLEAN := FALSE;
40
+ BEGIN
41
+
42
+ /**
43
+ * Delete disconnected app records, this will delete related triggers
44
+ * */
45
+ ${DELETE_DISCONNECTED_APPS_QUERY};
46
+
47
+ DELETE FROM prostgles.app_triggers
48
+ WHERE app_id NOT IN (SELECT id FROM prostgles.apps)
49
+ ${tableFilterQuery}
50
+ ;
51
+
52
+ /** IS THIS STILL NEEDED? Delete existing triggers without locking
53
+ */
54
+ LOCK TABLE prostgles.app_triggers IN ACCESS EXCLUSIVE MODE;
55
+ EXECUTE format(
56
+ $q$
57
+
58
+ CREATE TEMP TABLE %1$I AS --ON COMMIT DROP AS
59
+ SELECT * FROM prostgles.app_triggers;
60
+
61
+ DELETE FROM prostgles.app_triggers;
62
+
63
+ INSERT INTO prostgles.app_triggers
64
+ SELECT * FROM %1$I;
65
+
66
+ DROP TABLE IF EXISTS %1$I;
67
+ $q$,
68
+ ${asValue("triggers_" + this.appId)}
69
+ );
70
+
71
+ ${SCHEMA_WATCH_EVENT_TRIGGER_QUERY}
72
+
73
+ END
74
+ $do$;
75
+
76
+
77
+ COMMIT;
78
+ `,
79
+ { EVENT_TRIGGER_TAGS }
80
+ );
81
+
82
+ await this.db
83
+ .tx((t) => t.any(query))
84
+ .catch((e: any) => {
85
+ console.error("prepareTriggers failed: ", e);
86
+ throw e;
87
+ });
88
+
89
+ return true;
90
+ } catch (e) {
91
+ console.error("prepareTriggers failed: ", e);
92
+ throw e;
93
+ }
94
+ }
95
+
96
+ const SCHEMA_WATCH_EVENT_TRIGGER_QUERY = `
97
+
98
+ is_super_user := EXISTS (select 1 from pg_user where usename = CURRENT_USER AND usesuper IS TRUE);
99
+
100
+ /* DROP the old buggy schema watch trigger */
101
+ IF EXISTS (
102
+ SELECT 1 FROM pg_catalog.pg_event_trigger
103
+ WHERE evtname = 'prostgles_schema_watch_trigger'
104
+ ) AND is_super_user IS TRUE
105
+ THEN
106
+ DROP EVENT TRIGGER IF EXISTS prostgles_schema_watch_trigger;
107
+ END IF;
108
+
109
+ ev_trg_needed := EXISTS (
110
+ SELECT 1 FROM prostgles.apps
111
+ WHERE watching_schema_tag_names IS NOT NULL
112
+ );
113
+ ev_trg_exists := EXISTS (
114
+ SELECT 1 FROM pg_catalog.pg_event_trigger
115
+ WHERE evtname = ${asValue(DB_OBJ_NAMES.schema_watch_trigger)}
116
+ );
117
+
118
+ /* DROP stale event trigger */
119
+ IF
120
+ is_super_user IS TRUE
121
+ AND ev_trg_needed IS FALSE
122
+ AND ev_trg_exists IS TRUE
123
+ THEN
124
+
125
+ SELECT format(
126
+ $$
127
+ DROP EVENT TRIGGER IF EXISTS %I ;
128
+ DROP EVENT TRIGGER IF EXISTS %I ;
129
+ $$
130
+ , ${asValue(DB_OBJ_NAMES.schema_watch_trigger)}
131
+ , ${asValue(DB_OBJ_NAMES.schema_watch_trigger_drop)}
132
+ )
133
+ INTO q;
134
+ EXECUTE q;
135
+
136
+ /* CREATE event trigger */
137
+ ELSIF
138
+ is_super_user IS TRUE
139
+ AND ev_trg_needed IS TRUE
140
+ AND ev_trg_exists IS FALSE
141
+ THEN
142
+
143
+ DROP EVENT TRIGGER IF EXISTS ${DB_OBJ_NAMES.schema_watch_trigger};
144
+ CREATE EVENT TRIGGER ${DB_OBJ_NAMES.schema_watch_trigger}
145
+ ON ddl_command_end
146
+ WHEN TAG IN (\${EVENT_TRIGGER_TAGS:csv})
147
+ EXECUTE PROCEDURE ${DB_OBJ_NAMES.schema_watch_func}();
148
+
149
+ DROP EVENT TRIGGER IF EXISTS ${DB_OBJ_NAMES.schema_watch_trigger_drop};
150
+ CREATE EVENT TRIGGER ${DB_OBJ_NAMES.schema_watch_trigger_drop}
151
+ ON sql_drop
152
+ --WHEN TAG IN (\${EVENT_TRIGGER_TAGS:csv})
153
+ EXECUTE PROCEDURE ${DB_OBJ_NAMES.schema_watch_func}();
154
+
155
+ END IF;
156
+ `;
@@ -72,11 +72,11 @@ export async function notifListener(this: PubSubManager, data: { payload: string
72
72
  throw "table_name undef";
73
73
  }
74
74
 
75
- const tableTriggerConditions = this._triggers?.[table_name]?.map((condition, idx) => ({
75
+ const tableTriggerConditions = this._triggers?.[table_name]?.map((cond, idx) => ({
76
76
  idx,
77
- condition,
78
- subs: this.getTriggerSubs(table_name, condition),
79
- syncs: this.getSyncs(table_name, condition),
77
+ ...cond,
78
+ subs: this.getTriggerSubs(table_name, cond.condition),
79
+ syncs: this.getSyncs(table_name, cond.condition),
80
80
  }));
81
81
  let state: "error" | "no-triggers" | "ok" | "invalid_condition_ids" = "ok";
82
82
 
@@ -109,31 +109,7 @@ export async function notifListener(this: PubSubManager, data: { payload: string
109
109
  return !tc || (tc.subs.length === 0 && tc.syncs.length === 0);
110
110
  });
111
111
  if (orphanedTableConditions.length) {
112
- this.db
113
- .any(
114
- `
115
- /* Delete removed subscriptions */
116
- /* ${PubSubManager.EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID} */
117
- DELETE FROM prostgles.app_triggers at
118
- WHERE EXISTS (
119
- SELECT 1
120
- FROM prostgles.v_triggers t
121
- WHERE t.table_name = $1
122
- AND t.c_id IN ($2:csv)
123
- AND t.app_id = $3
124
- AND at.app_id = t.app_id
125
- AND at.table_name = t.table_name
126
- AND at.condition = t.condition
127
- )
128
- `,
129
- [table_name, orphanedTableConditions, this.appId]
130
- )
131
- .then(() => {
132
- return this.refreshTriggers();
133
- })
134
- .catch((e) => {
135
- console.error("Error deleting orphaned triggers", e);
136
- });
112
+ void this.deleteOrphanedTriggers.bind(this)(table_name);
137
113
  }
138
114
 
139
115
  firedTableConditions.map(({ subs, syncs }) => {
@@ -0,0 +1,38 @@
1
+ import type { PubSubManager } from "./PubSubManager";
2
+
3
+ export async function refreshTriggers(this: PubSubManager) {
4
+ const start = Date.now();
5
+ const triggers: {
6
+ table_name: string;
7
+ condition: string;
8
+ condition_hash: string;
9
+ }[] = await this.db.any(
10
+ `
11
+ SELECT *
12
+ FROM prostgles.v_triggers
13
+ WHERE app_id = $1
14
+ ORDER BY table_name, condition
15
+ `,
16
+ [this.dboBuilder.prostgles.appId]
17
+ );
18
+
19
+ const oldTriggers = { ...this._triggers };
20
+
21
+ this._triggers = {};
22
+ triggers.map((t) => {
23
+ this._triggers ??= {};
24
+ this._triggers[t.table_name] ??= [];
25
+ if (!this._triggers[t.table_name]?.map((t) => t.condition).includes(t.condition)) {
26
+ this._triggers[t.table_name]?.push({ condition: t.condition, hash: t.condition_hash });
27
+ }
28
+ });
29
+
30
+ await this._log({
31
+ type: "syncOrSub",
32
+ command: "refreshTriggers",
33
+ duration: Date.now() - start,
34
+ connectedSocketIds: this.connectedSocketIds,
35
+ triggers: this._triggers,
36
+ oldTriggers,
37
+ });
38
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prostgles-server",
3
- "version": "4.2.234",
3
+ "version": "4.2.236",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",