prostgles-server 2.0.153 → 2.0.156

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 (40) hide show
  1. package/dist/DBEventsManager.d.ts.map +1 -1
  2. package/dist/DBEventsManager.js +1 -2
  3. package/dist/DBEventsManager.js.map +1 -1
  4. package/dist/DboBuilder.d.ts +0 -1
  5. package/dist/DboBuilder.d.ts.map +1 -1
  6. package/dist/DboBuilder.js +4 -8
  7. package/dist/DboBuilder.js.map +1 -1
  8. package/dist/FileManager.js +1 -2
  9. package/dist/FileManager.js.map +1 -1
  10. package/dist/Filtering.d.ts.map +1 -1
  11. package/dist/Filtering.js +1 -1
  12. package/dist/Filtering.js.map +1 -1
  13. package/dist/Prostgles.d.ts.map +1 -1
  14. package/dist/Prostgles.js +6 -6
  15. package/dist/Prostgles.js.map +1 -1
  16. package/dist/PubSubManager.d.ts +1 -1
  17. package/dist/PubSubManager.d.ts.map +1 -1
  18. package/dist/PubSubManager.js +4 -3
  19. package/dist/PubSubManager.js.map +1 -1
  20. package/dist/QueryBuilder.d.ts.map +1 -1
  21. package/dist/QueryBuilder.js +4 -2
  22. package/dist/QueryBuilder.js.map +1 -1
  23. package/dist/TableConfig.d.ts.map +1 -1
  24. package/dist/TableConfig.js +2 -3
  25. package/dist/TableConfig.js.map +1 -1
  26. package/lib/DBEventsManager.ts +2 -2
  27. package/lib/DboBuilder.ts +3 -5
  28. package/lib/FileManager.ts +2 -2
  29. package/lib/Filtering.ts +2 -2
  30. package/lib/Prostgles.ts +2 -2
  31. package/lib/PubSubManager.ts +766 -765
  32. package/lib/QueryBuilder.ts +4 -3
  33. package/lib/TableConfig.ts +3 -3
  34. package/package.json +2 -2
  35. package/tests/client/PID.txt +1 -1
  36. package/tests/client/package-lock.json +15 -15
  37. package/tests/client/package.json +1 -1
  38. package/tests/client_only_queries.js +16 -8
  39. package/tests/client_only_queries.ts +18 -8
  40. package/tests/server/package-lock.json +3 -3
@@ -18,187 +18,187 @@ import { ClientExpressData, syncData } from "./SyncReplication";
18
18
 
19
19
  type PGP = pgPromise.IMain<{}, pg.IClient>;
20
20
  let pgp: PGP = pgPromise({
21
- promiseLib: Bluebird
21
+ promiseLib: Bluebird
22
22
  });
23
23
  export const asValue = (v: any) => pgp.as.format("$1", [v]);
24
24
  export const DEFAULT_SYNC_BATCH_SIZE = 50;
25
25
 
26
26
  export const log = (...args: any[]) => {
27
- if(process.env.TEST_TYPE){
28
- console.log(...args)
29
- }
27
+ if (process.env.TEST_TYPE) {
28
+ console.log(...args)
29
+ }
30
30
  }
31
31
 
32
32
  export type BasicCallback = (err?: any, res?: any) => void
33
33
 
34
34
  export type SyncParams = {
35
- socket_id: string;
36
- channel_name: string;
37
- table_name: string;
38
- table_rules?: TableRule;
39
- synced_field: string;
40
- allow_delete: boolean;
41
- id_fields: string[];
42
- batch_size: number;
43
- filter: object;
44
- params: {
45
- select: FieldFilter
46
- };
47
- condition: string;
48
- wal?: WAL,
49
- throttle?: number;
50
- lr?: AnyObject;
51
- last_synced: number;
52
- is_syncing: boolean;
35
+ socket_id: string;
36
+ channel_name: string;
37
+ table_name: string;
38
+ table_rules?: TableRule;
39
+ synced_field: string;
40
+ allow_delete: boolean;
41
+ id_fields: string[];
42
+ batch_size: number;
43
+ filter: object;
44
+ params: {
45
+ select: FieldFilter
46
+ };
47
+ condition: string;
48
+ wal?: WAL,
49
+ throttle?: number;
50
+ lr?: AnyObject;
51
+ last_synced: number;
52
+ is_syncing: boolean;
53
53
  }
54
54
 
55
55
  type AddSyncParams = {
56
- socket: any;
57
- table_info: TableInfo;
58
- table_rules: TableRule;
59
- synced_field: string;
60
- allow_delete?: boolean;
61
- id_fields: string[];
62
- filter: object;
63
- params: {
64
- select: FieldFilter
65
- };
66
- condition: string;
67
- throttle?: number;
56
+ socket: any;
57
+ table_info: TableInfo;
58
+ table_rules: TableRule;
59
+ synced_field: string;
60
+ allow_delete?: boolean;
61
+ id_fields: string[];
62
+ filter: object;
63
+ params: {
64
+ select: FieldFilter
65
+ };
66
+ condition: string;
67
+ throttle?: number;
68
68
  }
69
69
 
70
70
  type SubscriptionParams = {
71
- socket_id?: string;
72
- channel_name: string;
73
- table_name: string;
74
- socket: PRGLIOSocket | undefined;
75
- table_info: TableOrViewInfo;
76
- table_rules?: TableRule;
77
- filter: object;
78
- params: SelectParams;
79
- func?: (data: any) => any;
80
- throttle?: number;
81
- last_throttled: number;
82
- is_throttling?: any;
83
- is_ready?: boolean;
84
- // subOne?: boolean;
71
+ socket_id?: string;
72
+ channel_name: string;
73
+ table_name: string;
74
+ socket: PRGLIOSocket | undefined;
75
+ table_info: TableOrViewInfo;
76
+ table_rules?: TableRule;
77
+ filter: object;
78
+ params: SelectParams;
79
+ func?: (data: any) => any;
80
+ throttle?: number;
81
+ last_throttled: number;
82
+ is_throttling?: any;
83
+ is_ready?: boolean;
84
+ // subOne?: boolean;
85
85
  }
86
86
  type AddSubscriptionParams = SubscriptionParams & {
87
- condition: string;
87
+ condition: string;
88
88
  }
89
89
 
90
90
  export type PubSubManagerOptions = {
91
- dboBuilder: DboBuilder;
92
- db: DB;
93
- dbo: DbHandler;
94
- wsChannelNamePrefix?: string;
95
- pgChannelName?: string;
96
- onSchemaChange?: (event: { command: string; query: string }) => void;
91
+ dboBuilder: DboBuilder;
92
+ db: DB;
93
+ dbo: DbHandler;
94
+ wsChannelNamePrefix?: string;
95
+ pgChannelName?: string;
96
+ onSchemaChange?: (event: { command: string; query: string }) => void;
97
97
  }
98
98
 
99
99
  export class PubSubManager {
100
- static DELIMITER = '|$prstgls$|';
101
-
102
- dboBuilder: DboBuilder;
103
- db: DB;
104
- dbo: DbHandler;
105
- _triggers?: Record<string, string[]>;
106
- sockets: any;
107
- subs: { [ke: string]: { [ke: string]: { subs: SubscriptionParams[] } } };
108
- syncs: SyncParams[];
109
- socketChannelPreffix: string;
110
- onSchemaChange?: ((event: { command: string; query: string }) => void) = undefined;
111
-
112
- postgresNotifListenManager?: PostgresNotifListenManager;
113
-
114
- private constructor (options: PubSubManagerOptions) {
115
- const { db, dbo, wsChannelNamePrefix, pgChannelName, onSchemaChange, dboBuilder } = options;
116
- if(!db || !dbo){
117
- throw 'MISSING: db_pg, db';
118
- }
119
- this.db = db;
120
- this.dbo = dbo;
121
- this.onSchemaChange = onSchemaChange;
122
- this.dboBuilder = dboBuilder;
123
-
124
- this.sockets = {};
125
- this.subs = {};
126
- this.syncs = [];
127
- this.socketChannelPreffix = wsChannelNamePrefix || "_psqlWS_";
128
-
129
- log("Created PubSubManager");
100
+ static DELIMITER = '|$prstgls$|';
101
+
102
+ dboBuilder: DboBuilder;
103
+ db: DB;
104
+ dbo: DbHandler;
105
+ _triggers?: Record<string, string[]>;
106
+ sockets: any;
107
+ subs: { [ke: string]: { [ke: string]: { subs: SubscriptionParams[] } } };
108
+ syncs: SyncParams[];
109
+ socketChannelPreffix: string;
110
+ onSchemaChange?: ((event: { command: string; query: string }) => void) = undefined;
111
+
112
+ postgresNotifListenManager?: PostgresNotifListenManager;
113
+
114
+ private constructor(options: PubSubManagerOptions) {
115
+ const { db, dbo, wsChannelNamePrefix, pgChannelName, onSchemaChange, dboBuilder } = options;
116
+ if (!db || !dbo) {
117
+ throw 'MISSING: db_pg, db';
130
118
  }
131
-
132
- NOTIF_TYPE = {
133
- data: "data_has_changed",
134
- schema: "schema_has_changed"
135
- }
136
- NOTIF_CHANNEL = {
137
- preffix: 'prostgles_',
138
- getFull: (appID?: string) => {
139
- if(!this.appID && !appID) throw "No appID";
140
- return this.NOTIF_CHANNEL.preffix + (appID || this.appID);
141
- }
119
+ this.db = db;
120
+ this.dbo = dbo;
121
+ this.onSchemaChange = onSchemaChange;
122
+ this.dboBuilder = dboBuilder;
123
+
124
+ this.sockets = {};
125
+ this.subs = {};
126
+ this.syncs = [];
127
+ this.socketChannelPreffix = wsChannelNamePrefix || "_psqlWS_";
128
+
129
+ log("Created PubSubManager");
130
+ }
131
+
132
+ NOTIF_TYPE = {
133
+ data: "data_has_changed",
134
+ schema: "schema_has_changed"
135
+ }
136
+ NOTIF_CHANNEL = {
137
+ preffix: 'prostgles_',
138
+ getFull: (appID?: string) => {
139
+ if (!this.appID && !appID) throw "No appID";
140
+ return this.NOTIF_CHANNEL.preffix + (appID || this.appID);
142
141
  }
142
+ }
143
143
 
144
- private appID?: string;
145
-
146
- appCheckFrequencyMS = 10 * 1000;
147
- appCheck?: ReturnType<typeof setInterval>;
148
-
149
-
144
+ private appID?: string;
150
145
 
151
- // ,datname
152
- // ,usename
153
- // ,client_hostname
154
- // ,client_port
155
- // ,backend_start
156
- // ,query_start
157
- // ,query
158
- // ,state
159
-
160
- // console.log(await _db.any(`
161
- // SELECT pid, application_name, state
162
- // FROM pg_stat_activity
163
- // WHERE application_name IS NOT NULL AND application_name != '' -- state = 'active';
164
- // `))
165
-
166
-
167
- public static create = async (options: PubSubManagerOptions) => {
168
- const res = new PubSubManager(options);
169
- return await res.init();
170
- }
146
+ appCheckFrequencyMS = 10 * 1000;
147
+ appCheck?: ReturnType<typeof setInterval>;
171
148
 
172
- destroyed = false;
173
- destroy = () => {
174
- this.destroyed = true;
175
- if(this.appCheck){
176
- clearInterval(this.appCheck);
177
- }
178
- this.onSocketDisconnected();
179
- // if(this.postgresNotifListenManager){
180
- // this.postgresNotifListenManager.stopListening();
181
- // }
182
- if(!this.postgresNotifListenManager) throw "this.postgresNotifListenManager missing"
183
- this.postgresNotifListenManager.destroy();
184
- }
185
149
 
186
- canContinue = () => {
187
- if(this.destroyed){
188
- console.trace("Could not start destroyed instance");
189
- return false
190
- }
191
- return true
150
+
151
+ // ,datname
152
+ // ,usename
153
+ // ,client_hostname
154
+ // ,client_port
155
+ // ,backend_start
156
+ // ,query_start
157
+ // ,query
158
+ // ,state
159
+
160
+ // console.log(await _db.any(`
161
+ // SELECT pid, application_name, state
162
+ // FROM pg_stat_activity
163
+ // WHERE application_name IS NOT NULL AND application_name != '' -- state = 'active';
164
+ // `))
165
+
166
+
167
+ public static create = async (options: PubSubManagerOptions) => {
168
+ const res = new PubSubManager(options);
169
+ return await res.init();
170
+ }
171
+
172
+ destroyed = false;
173
+ destroy = () => {
174
+ this.destroyed = true;
175
+ if (this.appCheck) {
176
+ clearInterval(this.appCheck);
177
+ }
178
+ this.onSocketDisconnected();
179
+ // if(this.postgresNotifListenManager){
180
+ // this.postgresNotifListenManager.stopListening();
181
+ // }
182
+ if (!this.postgresNotifListenManager) throw "this.postgresNotifListenManager missing"
183
+ this.postgresNotifListenManager.destroy();
184
+ }
185
+
186
+ canContinue = () => {
187
+ if (this.destroyed) {
188
+ console.trace("Could not start destroyed instance");
189
+ return false
192
190
  }
191
+ return true
192
+ }
193
193
 
194
- appChecking = false;
195
- init = async (): Promise<PubSubManager | undefined> => {
196
- if(!this.canContinue()) return undefined;
194
+ appChecking = false;
195
+ init = async (): Promise<PubSubManager | undefined> => {
196
+ if (!this.canContinue()) return undefined;
197
197
 
198
- try {
199
- const schema_version = 4;
200
-
201
- const q = `
198
+ try {
199
+ const schema_version = 4;
200
+
201
+ const q = `
202
202
  BEGIN; -- ISOLATION LEVEL SERIALIZABLE;-- TRANSACTION ISOLATION LEVEL SERIALIZABLE;
203
203
 
204
204
  --SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
@@ -698,48 +698,48 @@ export class PubSubManager {
698
698
  COMMIT;
699
699
  `;
700
700
 
701
- // const prgl_exists = await this.db.oneOrNone(`
702
- // DROP SCHEMA IF EXISTS prostgles CASCADE;
703
- // SELECT 1 FROM information_schema.schemata WHERE schema_name = 'prostgles'
704
- // `);
705
-
706
- // if(!prgl_exists){
707
- // await this.db.any(q);
708
- // }
709
- await this.db.any(q);
710
- if(!this.canContinue()) return;
711
-
712
-
713
- /* Prepare App id */
714
- if(!this.appID){
715
- const raw = await this.db.one(
716
- "INSERT INTO prostgles.apps (check_frequency_ms, watching_schema, application_name) VALUES($1, $2, current_setting('application_name')) RETURNING *; "
717
- , [this.appCheckFrequencyMS, Boolean(this.onSchemaChange)]
718
- );
719
- this.appID = raw.id;
720
-
721
- if(!this.appCheck) {
722
-
723
- this.appCheck = setInterval(async () => {
724
- let appQ = "";
725
- try { // drop owned by api
701
+ // const prgl_exists = await this.db.oneOrNone(`
702
+ // DROP SCHEMA IF EXISTS prostgles CASCADE;
703
+ // SELECT 1 FROM information_schema.schemata WHERE schema_name = 'prostgles'
704
+ // `);
705
+
706
+ // if(!prgl_exists){
707
+ // await this.db.any(q);
708
+ // }
709
+ await this.db.any(q);
710
+ if (!this.canContinue()) return;
726
711
 
727
- this.appChecking = true;
728
712
 
729
- let trgUpdateLastUsed = "",
730
- listeners = this.getActiveListeners();
713
+ /* Prepare App id */
714
+ if (!this.appID) {
715
+ const raw = await this.db.one(
716
+ "INSERT INTO prostgles.apps (check_frequency_ms, watching_schema, application_name) VALUES($1, $2, current_setting('application_name')) RETURNING *; "
717
+ , [this.appCheckFrequencyMS, Boolean(this.onSchemaChange)]
718
+ );
719
+ this.appID = raw.id;
731
720
 
732
- if(listeners.length){
733
- trgUpdateLastUsed = `
721
+ if (!this.appCheck) {
722
+
723
+ this.appCheck = setInterval(async () => {
724
+ let appQ = "";
725
+ try { // drop owned by api
726
+
727
+ this.appChecking = true;
728
+
729
+ let trgUpdateLastUsed = "",
730
+ listeners = this.getActiveListeners();
731
+
732
+ if (listeners.length) {
733
+ trgUpdateLastUsed = `
734
734
  UPDATE prostgles.app_triggers
735
735
  SET last_used = CASE WHEN (table_name, condition) IN (
736
- ${listeners.map(l => ` ( ${asValue(l.table_name)}, ${asValue(l.condition)} ) ` ).join(", ") }
736
+ ${listeners.map(l => ` ( ${asValue(l.table_name)}, ${asValue(l.condition)} ) `).join(", ")}
737
737
  ) THEN NOW() ELSE last_used END
738
738
  WHERE app_id = ${asValue(this.appID)};
739
739
  `
740
- }
740
+ }
741
741
 
742
- appQ = `
742
+ appQ = `
743
743
 
744
744
  DO $$
745
745
  BEGIN
@@ -812,46 +812,46 @@ export class PubSubManager {
812
812
  --COMMIT;
813
813
  END $$;
814
814
  `
815
- await this.db.any(appQ);
816
- log("updated last_check");
817
- } catch (e) {
818
- console.error("appCheck FAILED: \n", e, appQ);
819
- }
820
-
821
- this.appChecking = false;
822
- }, 0.8 * this.appCheckFrequencyMS);
823
- }
815
+ await this.db.any(appQ);
816
+ log("updated last_check");
817
+ } catch (e) {
818
+ console.error("appCheck FAILED: \n", e, appQ);
824
819
  }
825
820
 
826
- this.postgresNotifListenManager = new PostgresNotifListenManager(this.db, this.notifListener, this.NOTIF_CHANNEL.getFull());
821
+ this.appChecking = false;
822
+ }, 0.8 * this.appCheckFrequencyMS);
823
+ }
824
+ }
827
825
 
828
- await this.prepareTriggers()
826
+ this.postgresNotifListenManager = new PostgresNotifListenManager(this.db, this.notifListener, this.NOTIF_CHANNEL.getFull());
829
827
 
830
- return this;
831
-
832
- } catch (e) {
833
- console.error("PubSubManager init failed: ", e);
834
- }
835
- }
828
+ await this.prepareTriggers()
829
+
830
+ return this;
836
831
 
837
- DB_OBJ_NAMES = {
838
- trigger_add_remove_func: "prostgles.trigger_add_remove_func",
839
- data_watch_func: "prostgles.prostgles_trigger_function",
840
- schema_watch_func: "prostgles.schema_watch_func",
841
- schema_watch_trigger: "prostgles_schema_watch_trigger_new"
832
+ } catch (e) {
833
+ console.error("PubSubManager init failed: ", e);
834
+ }
835
+ }
836
+
837
+ DB_OBJ_NAMES = {
838
+ trigger_add_remove_func: "prostgles.trigger_add_remove_func",
839
+ data_watch_func: "prostgles.prostgles_trigger_function",
840
+ schema_watch_func: "prostgles.schema_watch_func",
841
+ schema_watch_trigger: "prostgles_schema_watch_trigger_new"
842
+ }
843
+
844
+ static EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID = "prostgles internal query that should be excluded from "
845
+ prepareTriggers = async () => {
846
+ // SELECT * FROM pg_catalog.pg_event_trigger WHERE evtname
847
+ if (!this.appID) throw "prepareTriggers failed: this.appID missing";
848
+ if (this.dboBuilder.prostgles.opts.watchSchema && !(await isSuperUser(this.db))) {
849
+ console.warn("prostgles watchSchema requires superuser db user. Will not watch")
842
850
  }
843
851
 
844
- static EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID = "prostgles internal query that should be excluded from "
845
- prepareTriggers = async () => {
846
- // SELECT * FROM pg_catalog.pg_event_trigger WHERE evtname
847
- if(!this.appID) throw "prepareTriggers failed: this.appID missing";
848
- if(this.dboBuilder.prostgles.opts.watchSchema && !(await isSuperUser(this.db))){
849
- console.warn("prostgles watchSchema requires superuser db user. Will not watch")
850
- }
852
+ try {
851
853
 
852
- try {
853
-
854
- await this.db.any(`
854
+ await this.db.any(`
855
855
  BEGIN;-- ISOLATION LEVEL SERIALIZABLE;
856
856
 
857
857
  /** ${PubSubManager.EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID}
@@ -952,606 +952,607 @@ export class PubSubManager {
952
952
 
953
953
  COMMIT;
954
954
  `).catch(e => {
955
- console.error("prepareTriggers failed: ", e);
956
- throw e;
957
- });
955
+ console.error("prepareTriggers failed: ", e);
956
+ throw e;
957
+ });
958
958
 
959
- return true;
960
-
961
- } catch (e){
962
- console.error("prepareTriggers failed: ", e);
963
- throw e;
964
- }
965
- }
959
+ return true;
966
960
 
967
- isReady(){
968
- if(!this.postgresNotifListenManager) throw "this.postgresNotifListenManager missing";
969
- return this.postgresNotifListenManager.isListening();
961
+ } catch (e) {
962
+ console.error("prepareTriggers failed: ", e);
963
+ throw e;
970
964
  }
965
+ }
971
966
 
972
- getSubs(table_name: string, condition: string): SubscriptionParams[]{
973
- return get(this.subs, [table_name, condition, "subs"])
974
- }
967
+ isReady() {
968
+ if (!this.postgresNotifListenManager) throw "this.postgresNotifListenManager missing";
969
+ return this.postgresNotifListenManager.isListening();
970
+ }
975
971
 
976
- getSyncs(table_name: string, condition: string){
977
- return (this.syncs || [])
978
- .filter((s: SyncParams) => s.table_name === table_name && s.condition === condition);
979
- }
972
+ getSubs(table_name: string, condition: string): SubscriptionParams[] {
973
+ return get(this.subs, [table_name, condition, "subs"])
974
+ }
980
975
 
981
- /* Relay relevant data to relevant subscriptions */
982
- notifListener = async (data: { payload: string }) => {
983
- const str = data.payload;
976
+ getSyncs(table_name: string, condition: string) {
977
+ return (this.syncs || [])
978
+ .filter((s: SyncParams) => s.table_name === table_name && s.condition === condition);
979
+ }
984
980
 
985
- if(!str) {
986
- console.error("Empty notif?")
987
- return;
988
- }
989
- const dataArr = str.split(PubSubManager.DELIMITER),
990
- notifType = dataArr[0];
981
+ /* Relay relevant data to relevant subscriptions */
982
+ notifListener = async (data: { payload: string }) => {
983
+ const str = data.payload;
991
984
 
992
- log(str);
985
+ if (!str) {
986
+ console.error("Empty notif?")
987
+ return;
988
+ }
989
+ const dataArr = str.split(PubSubManager.DELIMITER),
990
+ notifType = dataArr[0];
993
991
 
994
- if(notifType === this.NOTIF_TYPE.schema){
995
- if(this.onSchemaChange){
996
- const command = dataArr[1],
997
- event_type = dataArr[2],
998
- query = dataArr[3];
992
+ log(str);
999
993
 
1000
- if(query){
1001
- this.onSchemaChange({ command, query })
1002
- }
1003
- }
994
+ if (notifType === this.NOTIF_TYPE.schema) {
995
+ if (this.onSchemaChange) {
996
+ const command = dataArr[1],
997
+ event_type = dataArr[2],
998
+ query = dataArr[3];
1004
999
 
1005
- return;
1000
+ if (query) {
1001
+ this.onSchemaChange({ command, query })
1006
1002
  }
1007
-
1008
- if(notifType !== this.NOTIF_TYPE.data){
1009
- console.error("Unexpected notif type: ", notifType);
1010
- return;
1011
- }
1012
-
1013
- const table_name = dataArr[1],
1014
- op_name = dataArr[2],
1015
- condition_ids_str = dataArr[3];
1016
-
1017
- // const triggers = await this.db.any("SELECT * FROM prostgles.triggers WHERE table_name = $1 AND id IN ($2:csv)", [table_name, condition_ids_str.split(",").map(v => +v)]);
1018
- // const conditions: string[] = triggers.map(t => t.condition);
1019
-
1020
- log("PG Trigger ->", dataArr.join("__"))
1021
- if(
1022
- condition_ids_str && condition_ids_str.startsWith("error") &&
1023
- this._triggers && this._triggers[table_name] && this._triggers[table_name].length
1024
- ){
1025
- const pref = "INTERNAL ERROR. Schema might have changed";
1026
- console.error(`${pref}: ${condition_ids_str}`)
1027
- this._triggers[table_name].map(c => {
1028
- const subs = this.getSubs(table_name, c);
1029
- subs.map(s => {
1030
- this.pushSubData(s, pref + ". Check server logs");
1031
- })
1032
- });
1033
- } else if(
1034
- condition_ids_str &&
1035
- condition_ids_str.split(",").length &&
1036
- !condition_ids_str.split(",").find((c: string )=> !Number.isInteger(+c)) &&
1037
- this._triggers && this._triggers[table_name] && this._triggers[table_name].length
1038
- ){
1039
- const idxs = condition_ids_str.split(",").map(v => +v);
1040
- const conditions = this._triggers[table_name].filter((c, i) => idxs.includes(i))
1041
-
1042
- log("PG Trigger -> ",{ table_name, op_name, condition_ids_str, conditions }, this._triggers[table_name]);
1043
-
1044
- conditions.map(condition => {
1045
-
1046
- const subs = this.getSubs(table_name, condition);
1047
- const syncs = this.getSyncs(table_name, condition);
1048
-
1003
+ }
1049
1004
 
1050
- syncs.map((s) => {
1051
- log("PG Trigger -> syncData. LR: ", s.lr);
1052
- this.syncData(s);
1053
- });
1005
+ return;
1006
+ }
1054
1007
 
1055
- if(!subs){
1008
+ if (notifType !== this.NOTIF_TYPE.data) {
1009
+ console.error("Unexpected notif type: ", notifType);
1010
+ return;
1011
+ }
1056
1012
 
1057
- // console.error(`sub missing for ${table_name} ${condition}`, this.triggers);
1058
- // console.log(this.subs)
1059
- return;
1060
- }
1013
+ const table_name = dataArr[1],
1014
+ op_name = dataArr[2],
1015
+ condition_ids_str = dataArr[3];
1016
+
1017
+ // const triggers = await this.db.any("SELECT * FROM prostgles.triggers WHERE table_name = $1 AND id IN ($2:csv)", [table_name, condition_ids_str.split(",").map(v => +v)]);
1018
+ // const conditions: string[] = triggers.map(t => t.condition);
1019
+
1020
+ log("PG Trigger ->", dataArr.join("__"))
1021
+ if (
1022
+ condition_ids_str && condition_ids_str.startsWith("error") &&
1023
+ this._triggers && this._triggers[table_name] && this._triggers[table_name].length
1024
+ ) {
1025
+ const pref = "INTERNAL ERROR. Schema might have changed";
1026
+ console.error(`${pref}: ${condition_ids_str}`)
1027
+ this._triggers[table_name].map(c => {
1028
+ const subs = this.getSubs(table_name, c);
1029
+ subs.map(s => {
1030
+ this.pushSubData(s, pref + ". Check server logs");
1031
+ })
1032
+ });
1033
+ } else if (
1034
+ condition_ids_str &&
1035
+ condition_ids_str.split(",").length &&
1036
+ !condition_ids_str.split(",").find((c: string) => !Number.isInteger(+c)) &&
1037
+ this._triggers && this._triggers[table_name] && this._triggers[table_name].length
1038
+ ) {
1039
+ const idxs = condition_ids_str.split(",").map(v => +v);
1040
+ const conditions = this._triggers[table_name].filter((c, i) => idxs.includes(i))
1041
+
1042
+ log("PG Trigger -> ", { table_name, op_name, condition_ids_str, conditions }, this._triggers[table_name]);
1043
+
1044
+ conditions.map(condition => {
1045
+
1046
+ const subs = this.getSubs(table_name, condition);
1047
+ const syncs = this.getSyncs(table_name, condition);
1048
+
1049
+
1050
+ syncs.map((s) => {
1051
+ log("PG Trigger -> syncData. LR: ", s.lr);
1052
+ this.syncData(s);
1053
+ });
1061
1054
 
1062
- /* Throttle the subscriptions */
1063
- for(var i = 0; i < subs.length; i++){
1064
- var sub = subs[i];
1065
- if(
1066
- this.dbo[sub.table_name] &&
1067
- sub.is_ready &&
1068
- (sub.socket_id && this.sockets[sub.socket_id]) || sub.func
1069
- ){
1070
- const throttle = sub.throttle || 0;
1071
- if(sub.last_throttled <= Date.now() - throttle){
1072
-
1073
- /* It is assumed the policy was checked before this point */
1074
- this.pushSubData(sub);
1075
- // sub.last_throttled = Date.now();
1076
- } else if(!sub.is_throttling) {
1077
-
1078
-
1079
- log("throttling sub")
1080
- sub.is_throttling = setTimeout(() => {
1081
- log("throttling finished. pushSubData...")
1082
- sub.is_throttling = null;
1083
- this.pushSubData(sub);
1084
- }, throttle);// sub.throttle);
1085
- }
1086
- }
1087
- }
1088
- });
1055
+ if (!subs) {
1089
1056
 
1090
- } else {
1057
+ // console.error(`sub missing for ${table_name} ${condition}`, this.triggers);
1058
+ // console.log(this.subs)
1059
+ return;
1060
+ }
1091
1061
 
1092
- // if(!this._triggers || !this._triggers[table_name] || !this._triggers[table_name].length){
1093
- // console.warn(190, "Trigger sub not found. DROPPING TRIGGER", table_name, condition_ids_str, this._triggers);
1094
- // this.dropTrigger(table_name);
1095
- // } else {
1096
- // }
1097
- console.warn(190, "Trigger sub issue: ", table_name, condition_ids_str, this._triggers);
1062
+ /* Throttle the subscriptions */
1063
+ for (var i = 0; i < subs.length; i++) {
1064
+ var sub = subs[i];
1065
+ if (
1066
+ this.dbo[sub.table_name] &&
1067
+ sub.is_ready &&
1068
+ (sub.socket_id && this.sockets[sub.socket_id]) || sub.func
1069
+ ) {
1070
+ const throttle = sub.throttle || 0;
1071
+ if (sub.last_throttled <= Date.now() - throttle) {
1072
+
1073
+ /* It is assumed the policy was checked before this point */
1074
+ this.pushSubData(sub);
1075
+ // sub.last_throttled = Date.now();
1076
+ } else if (!sub.is_throttling) {
1077
+
1078
+
1079
+ log("throttling sub")
1080
+ sub.is_throttling = setTimeout(() => {
1081
+ log("throttling finished. pushSubData...")
1082
+ sub.is_throttling = null;
1083
+ this.pushSubData(sub);
1084
+ }, throttle);// sub.throttle);
1085
+ }
1086
+ }
1098
1087
  }
1088
+ });
1089
+
1090
+ } else {
1091
+
1092
+ // if(!this._triggers || !this._triggers[table_name] || !this._triggers[table_name].length){
1093
+ // console.warn(190, "Trigger sub not found. DROPPING TRIGGER", table_name, condition_ids_str, this._triggers);
1094
+ // this.dropTrigger(table_name);
1095
+ // } else {
1096
+ // }
1097
+ console.warn(190, "Trigger sub issue: ", table_name, condition_ids_str, this._triggers);
1099
1098
  }
1099
+ }
1100
1100
 
1101
1101
 
1102
- pushSubData(sub: SubscriptionParams, err?: any){
1103
- if(!sub) throw "pushSubData: invalid sub";
1104
- const { table_name, filter, params, table_rules, socket_id, channel_name, func } = sub; //, subOne = false
1102
+ pushSubData(sub: SubscriptionParams, err?: any) {
1103
+ if (!sub) throw "pushSubData: invalid sub";
1104
+ const { table_name, filter, params, table_rules, socket_id, channel_name, func } = sub; //, subOne = false
1105
1105
 
1106
- sub.last_throttled = Date.now();
1106
+ sub.last_throttled = Date.now();
1107
1107
 
1108
- if(err){
1109
- if(socket_id){
1110
- this.sockets[socket_id].emit(channel_name, { err });
1111
- }
1112
- return true;
1113
- }
1108
+ if (err) {
1109
+ if (socket_id) {
1110
+ this.sockets[socket_id].emit(channel_name, { err });
1111
+ }
1112
+ return true;
1113
+ }
1114
1114
 
1115
- return new Promise(async (resolve, reject) => {
1116
- /* TODO: Retire subOne -> it's redundant */
1117
- // this.dbo[table_name][subOne? "findOne" : "find"](filter, params, null, table_rules)
1118
- if(!this.dbo?.[table_name]?.find) throw "1107 this.dbo[table_name].find";
1115
+ return new Promise(async (resolve, reject) => {
1116
+ /* TODO: Retire subOne -> it's redundant */
1117
+ // this.dbo[table_name][subOne? "findOne" : "find"](filter, params, null, table_rules)
1118
+ if (!this.dbo?.[table_name]?.find) throw "1107 this.dbo[table_name].find";
1119
1119
 
1120
- this.dbo?.[table_name]?.find?.(filter, params, undefined, table_rules)
1121
- .then(data => {
1122
-
1123
- if(socket_id && this.sockets[socket_id]){
1124
- log("Pushed " + data.length + " records to sub")
1125
- this.sockets[socket_id].emit(channel_name, { data }, () => {
1126
- resolve(data);
1127
- });
1128
- /* TO DO: confirm receiving data or server will unsubscribe
1129
- { data }, (cb)=> { console.log(cb) });
1130
- */
1131
- } else if(func) {
1132
- func(data);
1133
- resolve(data);
1134
- }
1135
- sub.last_throttled = Date.now();
1136
- }).catch(err => {
1137
- const errObj = { _err_msg: err.toString(), err };
1138
- if(socket_id && this.sockets[socket_id]){
1139
- this.sockets[socket_id].emit(channel_name, { err: errObj });
1140
- } else if(func) {
1141
- func({ err: errObj });
1142
- }
1143
- reject(errObj)
1144
- });
1120
+ this.dbo?.[table_name]?.find?.(filter, params, undefined, table_rules)
1121
+ .then(data => {
1122
+
1123
+ if (socket_id && this.sockets[socket_id]) {
1124
+ log("Pushed " + data.length + " records to sub")
1125
+ this.sockets[socket_id].emit(channel_name, { data }, () => {
1126
+ resolve(data);
1127
+ });
1128
+ /* TO DO: confirm receiving data or server will unsubscribe
1129
+ { data }, (cb)=> { console.log(cb) });
1130
+ */
1131
+ } else if (func) {
1132
+ func(data);
1133
+ resolve(data);
1134
+ }
1135
+ sub.last_throttled = Date.now();
1136
+ }).catch(err => {
1137
+ const errObj = { _err_msg: err.toString(), err };
1138
+ if (socket_id && this.sockets[socket_id]) {
1139
+ this.sockets[socket_id].emit(channel_name, { err: errObj });
1140
+ } else if (func) {
1141
+ func({ err: errObj });
1142
+ }
1143
+ reject(errObj)
1145
1144
  });
1146
- }
1145
+ });
1146
+ }
1147
1147
 
1148
- upsertSocket(socket: any, channel_name: string){
1149
- if(socket && !this.sockets[socket.id]){
1150
- this.sockets[socket.id] = socket;
1151
- socket.on("disconnect", () => this.onSocketDisconnected(socket));
1152
- }
1148
+ upsertSocket(socket: any, channel_name: string) {
1149
+ if (socket && !this.sockets[socket.id]) {
1150
+ this.sockets[socket.id] = socket;
1151
+ socket.on("disconnect", () => this.onSocketDisconnected(socket));
1153
1152
  }
1153
+ }
1154
+
1155
+ syncTimeout?: ReturnType<typeof setTimeout>;
1156
+ async syncData(sync: SyncParams, clientData?: ClientExpressData) {
1157
+ return await syncData(this, sync, clientData);
1158
+ }
1159
+
1160
+ /**
1161
+ * Returns a sync channel
1162
+ * A sync channel is unique per socket for each filter
1163
+ */
1164
+ async addSync(syncParams: AddSyncParams) {
1165
+ const {
1166
+ socket = null, table_info = null, table_rules, synced_field = null,
1167
+ allow_delete = false, id_fields = [], filter = {},
1168
+ params, condition = "", throttle = 0
1169
+ } = syncParams || {};
1170
+
1171
+ let conditionParsed = this.parseCondition(condition);
1172
+ if (!socket || !table_info) throw "socket or table_info missing";
1173
+
1174
+
1175
+ const { name: table_name } = table_info,
1176
+ channel_name = `${this.socketChannelPreffix}.${table_name}.${JSON.stringify(filter)}.sync`;
1177
+
1178
+ if (!synced_field) throw "synced_field missing from table_rules";
1179
+
1180
+ this.upsertSocket(socket, channel_name);
1181
+
1182
+ const upsertSync = () => {
1183
+ let newSync = {
1184
+ channel_name,
1185
+ table_name,
1186
+ filter,
1187
+ condition: conditionParsed,
1188
+ synced_field,
1189
+ id_fields,
1190
+ allow_delete,
1191
+ table_rules,
1192
+ throttle: Math.max(throttle || 0, table_rules?.sync?.throttle || 0),
1193
+ batch_size: get(table_rules, "sync.batch_size") || DEFAULT_SYNC_BATCH_SIZE,
1194
+ last_throttled: 0,
1195
+ socket_id: socket.id,
1196
+ is_sync: true,
1197
+ last_synced: 0,
1198
+ lr: undefined,
1199
+ table_info,
1200
+ is_syncing: false,
1201
+ wal: undefined,
1202
+ socket,
1203
+ params
1204
+ };
1205
+
1206
+ /* Only a sync per socket per table per condition allowed */
1207
+ this.syncs = this.syncs || [];
1208
+ let existing = this.syncs.find(s => s.socket_id === socket.id && s.channel_name === channel_name);
1209
+ if (!existing) {
1210
+ this.syncs.push(newSync);
1211
+ // console.log("Added SYNC");
1212
+
1213
+ socket.removeAllListeners(channel_name + "unsync");
1214
+ socket.once(channel_name + "unsync", (_data: any, cb: BasicCallback) => {
1215
+ this.onSocketDisconnected(socket, channel_name);
1216
+ cb(null, { res: "ok" })
1217
+ });
1154
1218
 
1155
- syncTimeout?: ReturnType<typeof setTimeout>;
1156
- async syncData(sync: SyncParams, clientData?: ClientExpressData){
1157
- return await syncData(this, sync, clientData);
1158
- }
1219
+ socket.removeAllListeners(channel_name);
1220
+ socket.on(channel_name, (data: any, cb: BasicCallback) => {
1159
1221
 
1160
- /**
1161
- * Returns a sync channel
1162
- * A sync channel is unique per socket for each filter
1163
- */
1164
- async addSync(syncParams: AddSyncParams){
1165
- const {
1166
- socket = null, table_info = null, table_rules, synced_field = null,
1167
- allow_delete = false, id_fields = [], filter = {},
1168
- params, condition = "", throttle = 0
1169
- } = syncParams || {};
1170
-
1171
- let conditionParsed = this.parseCondition(condition);
1172
- if(!socket || !table_info) throw "socket or table_info missing";
1173
-
1222
+ if (!data) {
1223
+ cb({ err: "Unexpected request. Need data or onSyncRequest" });
1224
+ return;
1225
+ }
1226
+
1227
+ /*
1228
+ */
1229
+
1230
+ /* Server will:
1231
+ 1. Ask for last_synced emit(onSyncRequest)
1232
+ 2. Ask for data >= server_synced emit(onPullRequest)
1233
+ -> Upsert that data
1234
+ 2. Push data >= last_synced emit(data.data)
1235
+
1236
+ Client will:
1237
+ 1. Send last_synced on(onSyncRequest)
1238
+ 2. Send data >= server_synced on(onPullRequest)
1239
+ 3. Send data on CRUD emit(data.data | data.deleted)
1240
+ 4. Upsert data.data | deleted on(data.data | data.deleted)
1241
+ */
1242
+
1243
+ // if(data.data){
1244
+ // console.error("THIS SHOUKD NEVER FIRE !! NEW DATA FROM SYNC");
1245
+ // this.upsertClientData(newSync, data.data);
1246
+ // } else
1247
+ if (data.onSyncRequest) {
1248
+ // console.log("syncData from socket")
1249
+ this.syncData(newSync, data.onSyncRequest);
1250
+
1251
+ // console.log("onSyncRequest ", socket._user)
1252
+ } else {
1253
+ console.error("Unexpected sync request data from client: ", data)
1254
+ }
1255
+ });
1174
1256
 
1175
- const { name: table_name } = table_info,
1176
- channel_name = `${this.socketChannelPreffix}.${table_name}.${JSON.stringify(filter)}.sync`;
1177
-
1178
- if(!synced_field) throw "synced_field missing from table_rules";
1179
-
1180
- this.upsertSocket(socket, channel_name);
1181
-
1182
- const upsertSync = () => {
1183
- let newSync = {
1184
- channel_name,
1185
- table_name,
1186
- filter,
1187
- condition: conditionParsed,
1188
- synced_field,
1189
- id_fields,
1190
- allow_delete,
1191
- table_rules,
1192
- throttle: Math.max(throttle || 0, table_rules?.sync?.throttle || 0),
1193
- batch_size: get(table_rules, "sync.batch_size") || DEFAULT_SYNC_BATCH_SIZE,
1194
- last_throttled: 0,
1195
- socket_id: socket.id,
1196
- is_sync: true,
1197
- last_synced: 0,
1198
- lr: undefined,
1199
- table_info,
1200
- is_syncing: false,
1201
- wal: undefined,
1202
- socket,
1203
- params
1204
- };
1205
-
1206
- /* Only a sync per socket per table per condition allowed */
1207
- this.syncs = this.syncs || [];
1208
- let existing = this.syncs.find(s => s.socket_id === socket.id && s.channel_name === channel_name );
1209
- if(!existing){
1210
- this.syncs.push(newSync);
1211
- // console.log("Added SYNC");
1212
-
1213
- socket.removeAllListeners(channel_name + "unsync");
1214
- socket.once(channel_name + "unsync", (_data: any, cb: BasicCallback) => {
1215
- this.onSocketDisconnected(socket, channel_name);
1216
- cb(null, { res: "ok" })
1217
- });
1218
-
1219
- socket.removeAllListeners(channel_name);
1220
- socket.on(channel_name, (data: any, cb: BasicCallback) => {
1221
-
1222
- if(!data){
1223
- cb({ err: "Unexpected request. Need data or onSyncRequest"});
1224
- return;
1225
- }
1257
+ // socket.emit(channel_name, { onSyncRequest: true }, (response) => {
1258
+ // console.log(response)
1259
+ // });
1260
+ } else {
1261
+ console.error("UNCLOSED DUPLICATE SYNC FOUND");
1262
+ }
1226
1263
 
1227
- /*
1228
- */
1264
+ return newSync;
1265
+ };
1229
1266
 
1230
- /* Server will:
1231
- 1. Ask for last_synced emit(onSyncRequest)
1232
- 2. Ask for data >= server_synced emit(onPullRequest)
1233
- -> Upsert that data
1234
- 2. Push data >= last_synced emit(data.data)
1235
-
1236
- Client will:
1237
- 1. Send last_synced on(onSyncRequest)
1238
- 2. Send data >= server_synced on(onPullRequest)
1239
- 3. Send data on CRUD emit(data.data | data.deleted)
1240
- 4. Upsert data.data | deleted on(data.data | data.deleted)
1241
- */
1242
1267
 
1243
- // if(data.data){
1244
- // console.error("THIS SHOUKD NEVER FIRE !! NEW DATA FROM SYNC");
1245
- // this.upsertClientData(newSync, data.data);
1246
- // } else
1247
- if(data.onSyncRequest){
1248
- // console.log("syncData from socket")
1249
- this.syncData(newSync, data.onSyncRequest);
1250
-
1251
- // console.log("onSyncRequest ", socket._user)
1252
- } else {
1253
- console.error("Unexpected sync request data from client: ", data)
1254
- }
1255
- });
1256
-
1257
- // socket.emit(channel_name, { onSyncRequest: true }, (response) => {
1258
- // console.log(response)
1259
- // });
1260
- } else {
1261
- console.error("UNCLOSED DUPLICATE SYNC FOUND");
1262
- }
1268
+ // const { min_id, max_id, count, max_synced } = params;
1263
1269
 
1264
- return newSync;
1265
- };
1266
-
1270
+ let sync = upsertSync();
1267
1271
 
1268
- // const { min_id, max_id, count, max_synced } = params;
1272
+ await this.addTrigger({ table_name, condition: conditionParsed });
1269
1273
 
1270
- let sync = upsertSync();
1271
-
1272
- await this.addTrigger({ table_name, condition: conditionParsed });
1274
+ return channel_name;
1275
+ }
1273
1276
 
1274
- return channel_name;
1275
- }
1277
+ parseCondition = (condition: string): string => Boolean(condition && condition.trim().length) ? condition : "TRUE"
1276
1278
 
1277
- parseCondition = (condition: string): string => Boolean(condition && condition.trim().length)? condition : "TRUE"
1279
+ /* Must return a channel for socket */
1280
+ /* The distinct list of channel names must have a corresponding trigger in the database */
1281
+ async addSub(subscriptionParams: Omit<AddSubscriptionParams, "channel_name">) {
1282
+ const {
1283
+ socket, func = null, table_info = null, table_rules, filter = {},
1284
+ params = {}, condition = "", throttle = 0 //subOne = false,
1285
+ } = subscriptionParams || {};
1278
1286
 
1279
- /* Must return a channel for socket */
1280
- /* The distinct list of channel names must have a corresponding trigger in the database */
1281
- async addSub(subscriptionParams: Omit<AddSubscriptionParams, "channel_name">){
1282
- const {
1283
- socket, func = null, table_info = null, table_rules, filter = {},
1284
- params = {}, condition = "", throttle = 0 //subOne = false,
1285
- } = subscriptionParams || {};
1287
+ let validated_throttle = subscriptionParams.throttle || 10;
1288
+ if ((!socket && !func) || !table_info) throw "socket/func or table_info missing";
1286
1289
 
1287
- let validated_throttle = subscriptionParams.throttle || 10;
1288
- if((!socket && !func) || !table_info) throw "socket/func or table_info missing";
1290
+ const pubThrottle = get(table_rules, ["subscribe", "throttle"]) || 0;
1291
+ if (pubThrottle && Number.isInteger(pubThrottle) && pubThrottle > 0) {
1292
+ validated_throttle = pubThrottle;
1293
+ }
1294
+ if (throttle && Number.isInteger(throttle) && throttle >= pubThrottle) {
1295
+ validated_throttle = throttle;
1296
+ }
1289
1297
 
1290
- const pubThrottle = get(table_rules, ["subscribe","throttle"]) || 0;
1291
- if(pubThrottle && Number.isInteger(pubThrottle) && pubThrottle > 0){
1292
- validated_throttle = pubThrottle;
1293
- }
1294
- if(throttle && Number.isInteger(throttle) && throttle >= pubThrottle){
1295
- validated_throttle = throttle;
1298
+ let channel_name = `${this.socketChannelPreffix}.${table_info.name}.${JSON.stringify(filter)}.${JSON.stringify(params)}.${"m"}.sub`; //.${subOne? "o" : "m"}.sub`;
1299
+
1300
+ this.upsertSocket(socket, channel_name);
1301
+
1302
+ const upsertSub = (newSubData: { table_name: string, condition: string, is_ready: boolean }) => {
1303
+ const { table_name, condition: _cond, is_ready = false } = newSubData,
1304
+ condition = this.parseCondition(_cond),
1305
+ newSub: SubscriptionParams = {
1306
+ socket,
1307
+ table_name: table_info.name,
1308
+ table_info,
1309
+ filter,
1310
+ params,
1311
+ table_rules,
1312
+ channel_name,
1313
+ func: func ? func : undefined,
1314
+ socket_id: socket?.id,
1315
+ throttle: validated_throttle,
1316
+ is_throttling: null,
1317
+ last_throttled: 0,
1318
+ is_ready,
1319
+ // subOne
1320
+ };
1321
+
1322
+ this.subs[table_name] = this.subs[table_name] || {};
1323
+ this.subs[table_name][condition] = this.subs[table_name][condition] || { subs: [] };
1324
+ this.subs[table_name][condition].subs = this.subs[table_name][condition].subs || [];
1325
+
1326
+ // console.log("1034 upsertSub", this.subs)
1327
+ const sub_idx = this.subs[table_name][condition].subs.findIndex(s =>
1328
+ s.channel_name === channel_name &&
1329
+ (
1330
+ socket && s.socket_id === socket.id ||
1331
+ func && s.func === func
1332
+ )
1333
+ );
1334
+ if (sub_idx < 0) {
1335
+ this.subs[table_name][condition].subs.push(newSub);
1336
+ if (socket) {
1337
+ const chnUnsub = channel_name + "unsubscribe";
1338
+ socket.removeAllListeners(chnUnsub);
1339
+ socket.once(chnUnsub, (_data: any, cb: BasicCallback) => {
1340
+ const res = this.onSocketDisconnected(socket, channel_name);
1341
+ cb(null, { res });
1342
+ });
1296
1343
  }
1297
-
1298
- let channel_name = `${this.socketChannelPreffix}.${table_info.name}.${JSON.stringify(filter)}.${JSON.stringify(params)}.${"m"}.sub`; //.${subOne? "o" : "m"}.sub`;
1344
+ } else {
1345
+ this.subs[table_name][condition].subs[sub_idx] = newSub;
1346
+ }
1299
1347
 
1300
- this.upsertSocket(socket, channel_name);
1301
-
1302
- const upsertSub = (newSubData: { table_name: string, condition: string, is_ready: boolean }) => {
1303
- const { table_name, condition: _cond, is_ready = false } = newSubData,
1304
- condition = this.parseCondition(_cond),
1305
- newSub: SubscriptionParams = {
1306
- socket,
1307
- table_name: table_info.name,
1308
- table_info,
1309
- filter,
1310
- params,
1311
- table_rules,
1312
- channel_name,
1313
- func: func? func : undefined,
1314
- socket_id: socket?.id,
1315
- throttle: validated_throttle,
1316
- is_throttling: null,
1317
- last_throttled: 0,
1318
- is_ready,
1319
- // subOne
1320
- };
1321
-
1322
- this.subs[table_name] = this.subs[table_name] || {};
1323
- this.subs[table_name][condition] = this.subs[table_name][condition] || { subs: [] };
1324
- this.subs[table_name][condition].subs = this.subs[table_name][condition].subs || [];
1325
-
1326
- // console.log("1034 upsertSub", this.subs)
1327
- const sub_idx = this.subs[table_name][condition].subs.findIndex(s =>
1328
- s.channel_name === channel_name &&
1329
- (
1330
- socket && s.socket_id === socket.id ||
1331
- func && s.func === func
1332
- )
1333
- );
1334
- if(sub_idx < 0){
1335
- this.subs[table_name][condition].subs.push(newSub);
1336
- if(socket){
1337
- const chnUnsub = channel_name + "unsubscribe";
1338
- socket.removeAllListeners(chnUnsub);
1339
- socket.once(chnUnsub, (_data: any, cb: BasicCallback) =>{
1340
- const res = this.onSocketDisconnected(socket, channel_name);
1341
- cb(null, { res });
1342
- });
1343
- }
1344
- } else {
1345
- this.subs[table_name][condition].subs[sub_idx] = newSub;
1346
- }
1348
+ if (is_ready) {
1349
+ this.pushSubData(newSub);
1350
+ }
1351
+ };
1347
1352
 
1348
- if(is_ready){
1349
- this.pushSubData(newSub);
1350
- }
1351
- };
1352
-
1353
1353
 
1354
- if(table_info.is_view && table_info.parent_tables){
1355
- if(table_info.parent_tables.length){
1356
-
1357
- let _condition = "TRUE";
1358
- table_info.parent_tables.map(async table_name => {
1359
-
1360
- upsertSub({
1361
- table_name,
1362
- condition: _condition,
1363
- is_ready: true
1364
- });
1365
-
1366
- await this.addTrigger({
1367
- table_name,
1368
- condition: _condition
1369
- });
1370
-
1371
- upsertSub({
1372
- table_name,
1373
- condition: _condition,
1374
- is_ready: true
1375
- });
1376
- });
1377
-
1378
- return channel_name
1379
- } else {
1380
- throw "PubSubManager: view parent_tables missing";
1381
- }
1382
- /* */
1383
- } else {
1384
- /* Just a table, add table + condition trigger */
1385
- // console.log(table_info, 202);
1386
-
1387
- upsertSub({
1388
- table_name: table_info.name,
1389
- condition: this.parseCondition(condition),
1390
- is_ready: false
1391
- });
1392
- await this.addTrigger({
1393
- table_name: table_info.name,
1394
- condition: this.parseCondition(condition),
1395
- });
1396
- upsertSub({
1397
- table_name: table_info.name,
1398
- condition: this.parseCondition(condition),
1399
- is_ready: true
1400
- });
1354
+ if (table_info.is_view && table_info.parent_tables) {
1355
+ if (table_info.parent_tables.length) {
1401
1356
 
1402
- return channel_name
1403
- }
1404
- }
1357
+ let _condition = "TRUE";
1358
+ table_info.parent_tables.map(async table_name => {
1405
1359
 
1406
- removeLocalSub(table_name: string, condition: string, func: (items: object[])=>any){
1407
- let cond = this.parseCondition(condition);
1408
- if(get(this.subs, [table_name, cond, "subs"])){
1409
- this.subs[table_name][cond].subs.map((sub, i) => {
1410
- if(
1411
- sub.func && sub.func === func
1412
- ){
1413
- this.subs[table_name][cond].subs.splice(i, 1);
1414
- }
1415
- });
1416
- } else {
1417
- console.error("Could not unsubscribe. Subscription might not have initialised yet")
1418
- }
1419
- }
1360
+ upsertSub({
1361
+ table_name,
1362
+ condition: _condition,
1363
+ is_ready: true
1364
+ });
1420
1365
 
1421
- getActiveListeners = (): { table_name: string; condition: string }[] => {
1422
- let result: { table_name: string; condition: string }[] = [];
1423
- const upsert = (t: string, c: string) => {
1424
- if(!result.find(r => r.table_name === t && r.condition === c)){
1425
- result.push({ table_name: t, condition: c });
1426
- }
1427
- }
1428
- (this.syncs || []).map(s => {
1429
- upsert(s.table_name, s.condition)
1430
- });
1431
- Object.keys(this.subs || {}).map(table_name => {
1432
- Object.keys(this.subs[table_name] || {}).map(condition => {
1433
- if(this.subs[table_name][condition].subs.length) {
1434
- upsert(table_name, condition);
1435
- }
1436
- });
1366
+ await this.addTrigger({
1367
+ table_name,
1368
+ condition: _condition
1369
+ });
1370
+
1371
+ upsertSub({
1372
+ table_name,
1373
+ condition: _condition,
1374
+ is_ready: true
1375
+ });
1437
1376
  });
1438
1377
 
1439
- return result;
1378
+ return channel_name
1379
+ } else {
1380
+ throw "PubSubManager: view parent_tables missing";
1381
+ }
1382
+ /* */
1383
+ } else {
1384
+ /* Just a table, add table + condition trigger */
1385
+ // console.log(table_info, 202);
1386
+
1387
+ upsertSub({
1388
+ table_name: table_info.name,
1389
+ condition: this.parseCondition(condition),
1390
+ is_ready: false
1391
+ });
1392
+ await this.addTrigger({
1393
+ table_name: table_info.name,
1394
+ condition: this.parseCondition(condition),
1395
+ });
1396
+ upsertSub({
1397
+ table_name: table_info.name,
1398
+ condition: this.parseCondition(condition),
1399
+ is_ready: true
1400
+ });
1401
+
1402
+ return channel_name
1440
1403
  }
1441
-
1442
- onSocketDisconnected(socket?: any, channel_name?: string){
1443
- // process.on('warning', e => {
1444
- // console.warn(e.stack)
1445
- // });
1446
- // console.log("onSocketDisconnected", channel_name, this.syncs)
1447
- if(this.subs){
1448
- Object.keys(this.subs).map(table_name => {
1449
- Object.keys(this.subs[table_name]).map(condition => {
1450
- this.subs[table_name][condition].subs.map((sub, i) => {
1451
-
1452
- /**
1453
- * If a channel name is specified then delete triggers
1454
- */
1455
- if(
1456
- (socket && sub.socket_id === socket.id) &&
1457
- (!channel_name || sub.channel_name === channel_name)
1458
- ){
1459
- this.subs[table_name][condition].subs.splice(i, 1);
1460
- if(!this.subs[table_name][condition].subs.length) {
1461
- delete this.subs[table_name][condition];
1462
-
1463
- if(isEmpty(this.subs[table_name])){
1464
- delete this.subs[table_name];
1465
- }
1466
- }
1467
- }
1468
- });
1469
- })
1470
- });
1404
+ }
1405
+
1406
+ removeLocalSub(table_name: string, condition: string, func: (items: object[]) => any) {
1407
+ let cond = this.parseCondition(condition);
1408
+ if (get(this.subs, [table_name, cond, "subs"])) {
1409
+ this.subs[table_name][cond].subs.map((sub, i) => {
1410
+ if (
1411
+ sub.func && sub.func === func
1412
+ ) {
1413
+ this.subs[table_name][cond].subs.splice(i, 1);
1471
1414
  }
1472
-
1473
- if(this.syncs){
1474
- this.syncs = this.syncs.filter(s => {
1475
- if(channel_name){
1476
- return (socket && s.socket_id !== socket.id) || s.channel_name !== channel_name
1415
+ });
1416
+ } else {
1417
+ console.error("Could not unsubscribe. Subscription might not have initialised yet")
1418
+ }
1419
+ }
1420
+
1421
+ getActiveListeners = (): { table_name: string; condition: string }[] => {
1422
+ let result: { table_name: string; condition: string }[] = [];
1423
+ const upsert = (t: string, c: string) => {
1424
+ if (!result.find(r => r.table_name === t && r.condition === c)) {
1425
+ result.push({ table_name: t, condition: c });
1426
+ }
1427
+ }
1428
+ (this.syncs || []).map(s => {
1429
+ upsert(s.table_name, s.condition)
1430
+ });
1431
+ Object.keys(this.subs || {}).map(table_name => {
1432
+ Object.keys(this.subs[table_name] || {}).map(condition => {
1433
+ if (this.subs[table_name][condition].subs.length) {
1434
+ upsert(table_name, condition);
1435
+ }
1436
+ });
1437
+ });
1438
+
1439
+ return result;
1440
+ }
1441
+
1442
+ onSocketDisconnected(socket?: PRGLIOSocket, channel_name?: string) {
1443
+ // process.on('warning', e => {
1444
+ // console.warn(e.stack)
1445
+ // });
1446
+ // console.log("onSocketDisconnected", channel_name, this.syncs)
1447
+ if (this.subs) {
1448
+ Object.keys(this.subs).map(table_name => {
1449
+ Object.keys(this.subs[table_name]).map(condition => {
1450
+ this.subs[table_name][condition].subs.map((sub, i) => {
1451
+
1452
+ /**
1453
+ * If a channel name is specified then delete triggers
1454
+ */
1455
+ if (
1456
+ (socket && sub.socket_id === socket.id) &&
1457
+ (!channel_name || sub.channel_name === channel_name)
1458
+ ) {
1459
+ this.subs[table_name][condition].subs.splice(i, 1);
1460
+ if (!this.subs[table_name][condition].subs.length) {
1461
+ delete this.subs[table_name][condition];
1462
+
1463
+ if (isEmpty(this.subs[table_name])) {
1464
+ delete this.subs[table_name];
1477
1465
  }
1466
+ }
1467
+ }
1468
+ });
1469
+ })
1470
+ });
1471
+ }
1478
1472
 
1479
- return (socket && s.socket_id !== socket.id)
1480
- });
1473
+ if (this.syncs) {
1474
+ this.syncs = this.syncs.filter(s => {
1475
+ const matchesSocket = Boolean(socket && s.socket_id !== socket.id)
1476
+ if (channel_name) {
1477
+ return matchesSocket || s.channel_name !== channel_name
1481
1478
  }
1482
1479
 
1483
- if(!socket){
1480
+ return matchesSocket;
1481
+ });
1482
+ }
1484
1483
 
1485
- } else if(!channel_name){
1486
- delete this.sockets[socket.id];
1487
- } else {
1488
- socket.removeAllListeners(channel_name);
1489
- socket.removeAllListeners(channel_name + "unsync");
1490
- socket.removeAllListeners(channel_name + "unsubscribe");
1491
- }
1484
+ if (!socket) {
1492
1485
 
1493
- return "ok";
1486
+ } else if (!channel_name) {
1487
+ delete this.sockets[socket.id];
1488
+ } else {
1489
+ socket.removeAllListeners(channel_name);
1490
+ socket.removeAllListeners(channel_name + "unsync");
1491
+ socket.removeAllListeners(channel_name + "unsubscribe");
1494
1492
  }
1495
1493
 
1494
+ return "ok";
1495
+ }
1496
1496
 
1497
- checkIfTimescaleBug = async (table_name: string) => {
1498
- const schema = "_timescaledb_catalog",
1499
- res = await this.db.oneOrNone("SELECT EXISTS( \
1497
+
1498
+ checkIfTimescaleBug = async (table_name: string) => {
1499
+ const schema = "_timescaledb_catalog",
1500
+ res = await this.db.oneOrNone("SELECT EXISTS( \
1500
1501
  SELECT * \
1501
1502
  FROM information_schema.tables \
1502
1503
  WHERE 1 = 1 \
1503
1504
  AND table_schema = ${schema} \
1504
1505
  AND table_name = 'hypertable' \
1505
1506
  );", { schema });
1506
- if(res.exists){
1507
- let isHyperTable = await this.db.any("SELECT * FROM " + asName(schema) + ".hypertable WHERE table_name = ${table_name};", { table_name, schema });
1508
- if(isHyperTable && isHyperTable.length){
1509
- throw "Triggers do not work on timescaledb hypertables due to bug:\nhttps://github.com/timescale/timescaledb/issues/1084"
1510
- }
1511
- }
1512
- return true;
1507
+ if (res.exists) {
1508
+ let isHyperTable = await this.db.any("SELECT * FROM " + asName(schema) + ".hypertable WHERE table_name = ${table_name};", { table_name, schema });
1509
+ if (isHyperTable && isHyperTable.length) {
1510
+ throw "Triggers do not work on timescaledb hypertables due to bug:\nhttps://github.com/timescale/timescaledb/issues/1084"
1511
+ }
1513
1512
  }
1513
+ return true;
1514
+ }
1514
1515
 
1515
- /*
1516
- A table will only have a trigger with all conditions (for different subs)
1517
- conditions = ["user_id = 1"]
1518
- fields = ["user_id"]
1519
- */
1516
+ /*
1517
+ A table will only have a trigger with all conditions (for different subs)
1518
+ conditions = ["user_id = 1"]
1519
+ fields = ["user_id"]
1520
+ */
1520
1521
 
1521
- getMyTriggerQuery = async () => {
1522
- return pgp.as.format(`
1522
+ getMyTriggerQuery = async () => {
1523
+ return pgp.as.format(`
1523
1524
  SELECT * --, ROW_NUMBER() OVER(PARTITION BY table_name ORDER BY table_name, condition ) - 1 as id
1524
1525
  FROM prostgles.v_triggers
1525
1526
  WHERE app_id = $1
1526
1527
  ORDER BY table_name, condition
1527
1528
  `, [this.appID]
1528
- )
1529
- }
1529
+ )
1530
+ }
1530
1531
 
1531
- // waitingTriggers: { [key: string]: string[] } = undefined;
1532
- addingTrigger: any;
1533
- addTriggerPool?: Record<string, string[]> = undefined;
1534
- async addTrigger(params: { table_name: string; condition: string; }){
1535
- try{
1532
+ // waitingTriggers: { [key: string]: string[] } = undefined;
1533
+ addingTrigger: any;
1534
+ addTriggerPool?: Record<string, string[]> = undefined;
1535
+ async addTrigger(params: { table_name: string; condition: string; }) {
1536
+ try {
1536
1537
 
1537
- let { table_name, condition } = { ...params }
1538
- if(!table_name) throw "MISSING table_name";
1539
- if(!this.appID) throw "MISSING appID";
1538
+ let { table_name, condition } = { ...params }
1539
+ if (!table_name) throw "MISSING table_name";
1540
+ if (!this.appID) throw "MISSING appID";
1540
1541
 
1541
- if(!condition || !condition.trim().length) condition = "TRUE";
1542
+ if (!condition || !condition.trim().length) condition = "TRUE";
1542
1543
 
1543
- const app_id = this.appID;
1544
+ const app_id = this.appID;
1544
1545
 
1545
- // console.log(1623, { app_id, addTrigger: { table_name, condition } });
1546
+ // console.log(1623, { app_id, addTrigger: { table_name, condition } });
1546
1547
 
1547
- await this.checkIfTimescaleBug(table_name);
1548
+ await this.checkIfTimescaleBug(table_name);
1548
1549
 
1549
- const trgVals = {
1550
- tbl: asValue(table_name),
1551
- cond: asValue(condition),
1552
- }
1550
+ const trgVals = {
1551
+ tbl: asValue(table_name),
1552
+ cond: asValue(condition),
1553
+ }
1553
1554
 
1554
- await this.db.any(`
1555
+ await this.db.any(`
1555
1556
  BEGIN WORK;
1556
1557
  LOCK TABLE prostgles.app_triggers IN ACCESS EXCLUSIVE MODE;
1557
1558
 
@@ -1562,52 +1563,52 @@ export class PubSubManager {
1562
1563
  COMMIT WORK;
1563
1564
  `);
1564
1565
 
1565
- log("addTrigger.. ", {table_name, condition});
1566
+ log("addTrigger.. ", { table_name, condition });
1566
1567
 
1567
- const triggers: {
1568
- table_name: string;
1569
- condition: string;
1570
- }[] = await this.db.any(await this.getMyTriggerQuery());
1568
+ const triggers: {
1569
+ table_name: string;
1570
+ condition: string;
1571
+ }[] = await this.db.any(await this.getMyTriggerQuery());
1571
1572
 
1572
1573
 
1573
- this._triggers = {};
1574
- triggers.map(t => {
1575
- this._triggers = this._triggers || {};
1576
- this._triggers[t.table_name] = this._triggers[t.table_name] || [];
1577
- if(!this._triggers[t.table_name].includes(t.condition)){
1578
- this._triggers[t.table_name].push(t.condition)
1579
- }
1580
- });
1581
- log("trigger added.. ", {table_name, condition});
1574
+ this._triggers = {};
1575
+ triggers.map(t => {
1576
+ this._triggers = this._triggers || {};
1577
+ this._triggers[t.table_name] = this._triggers[t.table_name] || [];
1578
+ if (!this._triggers[t.table_name].includes(t.condition)) {
1579
+ this._triggers[t.table_name].push(t.condition)
1580
+ }
1581
+ });
1582
+ log("trigger added.. ", { table_name, condition });
1582
1583
 
1583
- return true;
1584
- // console.log("1612", JSON.stringify(triggers, null, 2))
1585
- // console.log("1613",JSON.stringify(this._triggers, null, 2))
1586
-
1584
+ return true;
1585
+ // console.log("1612", JSON.stringify(triggers, null, 2))
1586
+ // console.log("1613",JSON.stringify(this._triggers, null, 2))
1587
1587
 
1588
- } catch(e){
1589
- console.trace("Failed adding trigger", e);
1590
- // throw e
1591
- }
1592
1588
 
1589
+ } catch (e) {
1590
+ console.trace("Failed adding trigger", e);
1591
+ // throw e
1593
1592
  }
1593
+
1594
+ }
1594
1595
  }
1595
1596
 
1596
1597
 
1597
1598
  /* Get only the specified properties of an object */
1598
- export function filterObj(obj: AnyObject, keys: string[] = [], exclude?: string[]): AnyObject{
1599
- if(exclude && exclude.length) keys = Object.keys(obj).filter(k => !exclude.includes(k))
1600
- if(!keys.length) {
1601
- // console.warn("filterObj: returning empty object");
1602
- return {};
1603
- }
1604
- if(obj && keys.length){
1605
- let res: AnyObject = {};
1606
- keys.map(k => {
1607
- res[k] = obj[k];
1608
- });
1609
- return res;
1610
- }
1611
-
1612
- return obj;
1599
+ export function filterObj(obj: AnyObject, keys: string[] = [], exclude?: string[]): AnyObject {
1600
+ if (exclude && exclude.length) keys = Object.keys(obj).filter(k => !exclude.includes(k))
1601
+ if (!keys.length) {
1602
+ // console.warn("filterObj: returning empty object");
1603
+ return {};
1604
+ }
1605
+ if (obj && keys.length) {
1606
+ let res: AnyObject = {};
1607
+ keys.map(k => {
1608
+ res[k] = obj[k];
1609
+ });
1610
+ return res;
1611
+ }
1612
+
1613
+ return obj;
1613
1614
  }