prostgles-server 4.2.439 → 4.2.441

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 (73) hide show
  1. package/dist/DboBuilder/QueryStreamer.js +1 -1
  2. package/dist/DboBuilder/QueryStreamer.js.map +1 -1
  3. package/dist/DboBuilder/ViewHandler/ViewHandler.d.ts +6 -6
  4. package/dist/DboBuilder/ViewHandler/ViewHandler.d.ts.map +1 -1
  5. package/dist/DboBuilder/ViewHandler/ViewHandler.js +6 -8
  6. package/dist/DboBuilder/ViewHandler/ViewHandler.js.map +1 -1
  7. package/dist/DboBuilder/ViewHandler/subscribe.d.ts +4 -12
  8. package/dist/DboBuilder/ViewHandler/subscribe.d.ts.map +1 -1
  9. package/dist/DboBuilder/ViewHandler/subscribe.js +11 -25
  10. package/dist/DboBuilder/ViewHandler/subscribe.js.map +1 -1
  11. package/dist/Logging.d.ts +2 -1
  12. package/dist/Logging.d.ts.map +1 -1
  13. package/dist/PubSubManager/PubSubManager.d.ts +19 -11
  14. package/dist/PubSubManager/PubSubManager.d.ts.map +1 -1
  15. package/dist/PubSubManager/PubSubManager.js +35 -25
  16. package/dist/PubSubManager/PubSubManager.js.map +1 -1
  17. package/dist/PubSubManager/addSub.d.ts.map +1 -1
  18. package/dist/PubSubManager/addSub.js +6 -6
  19. package/dist/PubSubManager/addSub.js.map +1 -1
  20. package/dist/PubSubManager/addSync.js +1 -1
  21. package/dist/PubSubManager/addSync.js.map +1 -1
  22. package/dist/PubSubManager/init/getDataWatchFunctionQuery.d.ts.map +1 -1
  23. package/dist/PubSubManager/init/getDataWatchFunctionQuery.js +22 -1
  24. package/dist/PubSubManager/init/getDataWatchFunctionQuery.js.map +1 -1
  25. package/dist/PubSubManager/notifListener.js +3 -3
  26. package/dist/PubSubManager/notifListener.js.map +1 -1
  27. package/dist/PubSubManager/pushSubData.d.ts +1 -1
  28. package/dist/PubSubManager/pushSubData.d.ts.map +1 -1
  29. package/dist/PubSubManager/pushSubData.js +21 -42
  30. package/dist/PubSubManager/pushSubData.js.map +1 -1
  31. package/dist/PubSubManager/refreshTriggers.d.ts.map +1 -1
  32. package/dist/PubSubManager/refreshTriggers.js +6 -5
  33. package/dist/PubSubManager/refreshTriggers.js.map +1 -1
  34. package/dist/PublishParser/PublishParser.d.ts +3 -6
  35. package/dist/PublishParser/PublishParser.d.ts.map +1 -1
  36. package/dist/PublishParser/PublishParser.js +32 -42
  37. package/dist/PublishParser/PublishParser.js.map +1 -1
  38. package/dist/PublishParser/getSchemaFromPublish.js +11 -11
  39. package/dist/PublishParser/getSchemaFromPublish.js.map +1 -1
  40. package/dist/PublishParser/getTableRulesWithoutFileTable.d.ts.map +1 -1
  41. package/dist/PublishParser/getTableRulesWithoutFileTable.js +2 -2
  42. package/dist/PublishParser/getTableRulesWithoutFileTable.js.map +1 -1
  43. package/dist/PublishParser/publishTypesAndUtils.d.ts +1 -0
  44. package/dist/PublishParser/publishTypesAndUtils.d.ts.map +1 -1
  45. package/dist/SyncReplication.d.ts.map +1 -1
  46. package/dist/SyncReplication.js +1 -1
  47. package/dist/SyncReplication.js.map +1 -1
  48. package/dist/initProstgles.d.ts +2 -2
  49. package/dist/initProstgles.d.ts.map +1 -1
  50. package/dist/initProstgles.js +14 -1
  51. package/dist/initProstgles.js.map +1 -1
  52. package/dist/runClientRequest.d.ts.map +1 -1
  53. package/dist/runClientRequest.js +4 -3
  54. package/dist/runClientRequest.js.map +1 -1
  55. package/lib/DboBuilder/QueryStreamer.ts +1 -1
  56. package/lib/DboBuilder/ViewHandler/ViewHandler.ts +32 -30
  57. package/lib/DboBuilder/ViewHandler/subscribe.ts +22 -48
  58. package/lib/Logging.ts +3 -1
  59. package/lib/PubSubManager/PubSubManager.ts +50 -42
  60. package/lib/PubSubManager/addSub.ts +13 -8
  61. package/lib/PubSubManager/addSync.ts +1 -1
  62. package/lib/PubSubManager/init/getDataWatchFunctionQuery.ts +28 -1
  63. package/lib/PubSubManager/notifListener.ts +8 -8
  64. package/lib/PubSubManager/pushSubData.ts +20 -35
  65. package/lib/PubSubManager/refreshTriggers.ts +9 -8
  66. package/lib/PublishParser/PublishParser.ts +40 -52
  67. package/lib/PublishParser/getSchemaFromPublish.ts +21 -21
  68. package/lib/PublishParser/getTableRulesWithoutFileTable.ts +5 -6
  69. package/lib/PublishParser/publishTypesAndUtils.ts +1 -0
  70. package/lib/SyncReplication.ts +1 -0
  71. package/lib/initProstgles.ts +21 -3
  72. package/lib/runClientRequest.ts +6 -5
  73. package/package.json +3 -3
@@ -5,6 +5,7 @@ import type {
5
5
  FieldFilter,
6
6
  SelectParams,
7
7
  SubscribeParams,
8
+ SubscriptionChannels,
8
9
  } from "prostgles-types";
9
10
  import { asName, isObject, postgresToTsType } from "prostgles-types";
10
11
  import type { TableEvent } from "../../Logging";
@@ -23,7 +24,7 @@ import { getInfo } from "./getInfo";
23
24
  import { parseFieldFilter } from "./parseFieldFilter";
24
25
  import { prepareWhere } from "./prepareWhere";
25
26
  import { size } from "./size";
26
- import type { LocalFuncs } from "./subscribe";
27
+ import type { OnData } from "./subscribe";
27
28
  import { subscribe } from "./subscribe";
28
29
  import { validateViewRules } from "./validateViewRules";
29
30
  import { escapeTSNames } from "../../utils/utils";
@@ -64,7 +65,7 @@ export class ViewHandler {
64
65
  tableOrViewInfo: TableSchema,
65
66
  dboBuilder: DboBuilder,
66
67
  tx?: { t: pgPromise.ITask<{}>; dbTX: TableHandlers },
67
- joinPaths?: JoinPaths
68
+ joinPaths?: JoinPaths,
68
69
  ) {
69
70
  this.db = db;
70
71
  this.tx = tx;
@@ -84,7 +85,7 @@ export class ViewHandler {
84
85
  this.joins = this.dboBuilder.joins;
85
86
  this.columnsForTypes.map(({ name, udt_name, is_nullable }) => {
86
87
  this.tsColumnDefs.push(
87
- `${escapeTSNames(name)}?: ${postgresToTsType(udt_name) as string} ${is_nullable ? " | null " : ""};`
88
+ `${escapeTSNames(name)}?: ${postgresToTsType(udt_name) as string} ${is_nullable ? " | null " : ""};`,
88
89
  );
89
90
  });
90
91
  }
@@ -192,7 +193,7 @@ export class ViewHandler {
192
193
  selectParams?: SelectParams,
193
194
  _param3_unused?: undefined,
194
195
  table_rules?: ParsedTableRule,
195
- localParams?: LocalParams
196
+ localParams?: LocalParams,
196
197
  ): Promise<any> {
197
198
  try {
198
199
  const { limit, ...params } = selectParams ?? {};
@@ -205,7 +206,7 @@ export class ViewHandler {
205
206
  { ...params, limit: 1, returnType: "row" },
206
207
  undefined,
207
208
  table_rules,
208
- localParams
209
+ localParams,
209
210
  );
210
211
  await this._log({
211
212
  command: "find",
@@ -226,58 +227,59 @@ export class ViewHandler {
226
227
  async subscribe(
227
228
  filter: Filter,
228
229
  params: SubscribeParams,
229
- localFuncs: LocalFuncs
230
+ onData: OnData,
230
231
  ): Promise<{ unsubscribe: () => any }>;
231
232
 
232
233
  async subscribe(
233
234
  filter: Filter,
234
235
  params: SubscribeParams,
235
- localFuncs: undefined,
236
- table_rules: ParsedTableRule | undefined,
237
- localParams: LocalParams
238
- ): Promise<string>;
236
+ onData?: OnData,
237
+ table_rules?: ParsedTableRule,
238
+ localParams?: LocalParams,
239
+ ): Promise<SubscriptionChannels>;
239
240
 
240
241
  async subscribe(
241
242
  filter: Filter,
242
243
  params: SubscribeParams,
243
- localFuncs?: LocalFuncs,
244
+ onData?: OnData,
244
245
  table_rules?: ParsedTableRule,
245
- localParams?: LocalParams
246
- ): Promise<{ unsubscribe: () => any } | string> {
247
- //@ts-ignore
248
- return subscribe.bind(this)(
246
+ localParams?: LocalParams,
247
+ ): Promise<{ unsubscribe: () => any } | SubscriptionChannels> {
248
+ const result = await subscribe.bind(this)(
249
249
  filter,
250
250
  params,
251
251
  //@ts-ignore
252
- localFuncs,
252
+ onData,
253
253
  table_rules,
254
- localParams
254
+ localParams,
255
255
  );
256
+ return result;
256
257
  }
257
258
 
258
259
  /* This should only be called from server */
259
260
  subscribeOne(
260
261
  filter: Filter,
261
262
  params: SubscribeParams,
262
- localFunc: (item: AnyObject) => any
263
+ onData: (item: AnyObject | undefined, error?: unknown) => any,
263
264
  ): Promise<{ unsubscribe: () => any }>;
264
265
  subscribeOne(
265
266
  filter: Filter,
266
267
  params: SubscribeParams,
267
- localFunc: undefined,
268
+ onData: undefined,
268
269
  table_rules: ParsedTableRule,
269
- localParams: LocalParams
270
- ): Promise<string>;
270
+ localParams: LocalParams,
271
+ ): Promise<SubscriptionChannels>;
271
272
  subscribeOne(
272
273
  filter: Filter,
273
274
  params: SubscribeParams = {},
274
- localFunc?: (item: AnyObject) => any,
275
+ onData?: (item: AnyObject | undefined, error?: unknown) => void,
275
276
  table_rules?: ParsedTableRule,
276
- localParams?: LocalParams
277
- ): Promise<string | { unsubscribe: () => any }> {
278
- //@ts-ignore
279
- const func = localParams ? undefined : (rows: AnyObject[]) => localFunc(rows[0]);
280
- //@ts-ignore
277
+ localParams?: LocalParams,
278
+ ): Promise<SubscriptionChannels | { unsubscribe: () => any }> {
279
+ const func =
280
+ localParams || !onData ? undefined : (
281
+ (rows: AnyObject[], error?: unknown) => onData(rows[0], error)
282
+ );
281
283
  return this.subscribe(filter, { ...params, limit: 2 }, func, table_rules, localParams);
282
284
  }
283
285
 
@@ -287,7 +289,7 @@ export class ViewHandler {
287
289
  getAllowedSelectFields(
288
290
  selectParams: FieldFilter = "*",
289
291
  allowed_cols: FieldFilter,
290
- allow_empty = true
292
+ allow_empty = true,
291
293
  ): string[] {
292
294
  const all_columns = this.column_names.slice(0);
293
295
  let allowedFields = all_columns.slice(0),
@@ -317,7 +319,7 @@ export class ViewHandler {
317
319
  intersectColumns(
318
320
  allowedFields: FieldFilter,
319
321
  dissallowedFields: FieldFilter,
320
- removeDisallowedFields = false
322
+ removeDisallowedFields = false,
321
323
  ): string[] {
322
324
  let result: string[] = [];
323
325
  if (allowedFields) {
@@ -338,7 +340,7 @@ export class ViewHandler {
338
340
  parseFieldFilter(
339
341
  fieldParams: FieldFilter = "*",
340
342
  allow_empty = true,
341
- allowed_cols?: string[]
343
+ allowed_cols?: string[],
342
344
  ): string[] {
343
345
  return parseFieldFilter(fieldParams, allow_empty, allowed_cols ?? this.column_names.slice(0));
344
346
  }
@@ -1,68 +1,42 @@
1
1
  import type { AnyObject, SubscribeParams, SubscriptionChannels } from "prostgles-types";
2
2
  import type { ParsedTableRule } from "../../PublishParser/PublishParser";
3
- import type {
4
- Filter,
5
- LocalParams} from "../DboBuilder";
6
- import {
7
- getErrorAsObject,
8
- getSerializedClientErrorFromPGError
9
- } from "../DboBuilder";
3
+ import type { Filter, LocalParams } from "../DboBuilder";
4
+ import { getErrorAsObject, getSerializedClientErrorFromPGError } from "../DboBuilder";
10
5
  import { getSubscribeRelatedTables } from "../getSubscribeRelatedTables";
11
6
  import type { NewQuery } from "../QueryBuilder/QueryBuilder";
12
7
  import type { ViewHandler } from "./ViewHandler";
13
8
  import { getValidatedSubscribeOptions } from "./getValidatedSubscribeOptions";
14
9
 
15
- type OnData = (items: AnyObject[]) => any;
16
- export type LocalFuncs =
17
- | {
18
- onData: OnData;
19
- onError?: (error: any) => void;
20
- }
21
- | OnData;
10
+ export type OnData = (items: AnyObject[], error?: unknown) => any;
22
11
 
23
- export const getOnDataFunc = (localFuncs: LocalFuncs | undefined): OnData | undefined => {
24
- return typeof localFuncs === "function" ? localFuncs : localFuncs?.onData;
25
- };
26
12
  export const matchesLocalFuncs = (
27
- localFuncs1: LocalFuncs | undefined,
28
- localFuncs2: LocalFuncs | undefined
13
+ localFuncs1: OnData | undefined,
14
+ localFuncs2: OnData | undefined,
29
15
  ) => {
30
- return localFuncs1 && localFuncs2 && getOnDataFunc(localFuncs1) === getOnDataFunc(localFuncs2);
31
- };
32
- export const parseLocalFuncs = (
33
- localFuncs1: LocalFuncs | undefined
34
- ): Extract<LocalFuncs, { onData: OnData }> | undefined => {
35
- return (
36
- !localFuncs1 ? undefined
37
- : typeof localFuncs1 === "function" ?
38
- {
39
- onData: localFuncs1,
40
- }
41
- : localFuncs1
42
- );
16
+ return localFuncs1 && localFuncs2 && localFuncs1 === localFuncs2;
43
17
  };
44
18
 
45
19
  async function subscribe(
46
20
  this: ViewHandler,
47
21
  filter: Filter,
48
22
  params: SubscribeParams,
49
- localFuncs: LocalFuncs
23
+ onData: OnData,
50
24
  ): Promise<{ unsubscribe: () => any }>;
51
25
  async function subscribe(
52
26
  this: ViewHandler,
53
27
  filter: Filter,
54
28
  params: SubscribeParams,
55
- localFuncs: undefined,
29
+ onData: undefined,
56
30
  table_rules: ParsedTableRule | undefined,
57
- localParams: LocalParams
31
+ localParams: LocalParams,
58
32
  ): Promise<SubscriptionChannels>;
59
33
  async function subscribe(
60
34
  this: ViewHandler,
61
35
  filter: Filter,
62
36
  params: SubscribeParams,
63
- localFuncs?: LocalFuncs,
37
+ onData?: OnData,
64
38
  table_rules?: ParsedTableRule,
65
- localParams?: LocalParams
39
+ localParams?: LocalParams,
66
40
  ): Promise<{ unsubscribe: () => any } | SubscriptionChannels> {
67
41
  const start = Date.now();
68
42
  try {
@@ -74,12 +48,11 @@ async function subscribe(
74
48
  throw "subscribe not allowed within transactions";
75
49
  }
76
50
  const clientReq = localParams?.clientReq;
77
- if (!clientReq && !localFuncs) {
78
- throw " missing data. provide -> localFunc | localParams { socket } ";
51
+ if (!clientReq && !onData) {
52
+ throw " missing data. expecting onData | localParams { socket } ";
79
53
  }
80
- if (clientReq?.socket && localFuncs) {
81
- console.error({ localParams, localFuncs });
82
- throw " Cannot have localFunc AND socket ";
54
+ if (clientReq?.socket && onData) {
55
+ throw " Cannot have onData and socket ";
83
56
  }
84
57
 
85
58
  const { throttle, throttleOpts, skipFirst, actions, skipChangedColumnsCheck, ...selectParams } =
@@ -93,7 +66,7 @@ async function subscribe(
93
66
  { ...selectParams, limit: 0 },
94
67
  undefined,
95
68
  table_rules,
96
- { ...localParams, returnNewQuery: true }
69
+ { ...localParams, returnNewQuery: true },
97
70
  )) as unknown as NewQuery;
98
71
  const viewOptions = await getSubscribeRelatedTables.bind(this)({
99
72
  filter,
@@ -114,19 +87,20 @@ async function subscribe(
114
87
  selectParams: { ...selectParams },
115
88
  subscribeOptions: getValidatedSubscribeOptions(
116
89
  { actions, skipFirst, throttle, throttleOpts, skipChangedColumnsCheck },
117
- table_rules?.subscribe
90
+ table_rules?.subscribe,
118
91
  ),
119
92
  lastPushed: 0,
120
93
  tracked_columns,
121
94
  } as const;
122
95
 
123
96
  const pubSubManager = await this.dboBuilder.getPubSubManager();
124
- if (!localFuncs) {
97
+
98
+ if (!onData) {
125
99
  const { socket } = clientReq ?? {};
126
100
  const result = await pubSubManager.addSub({
127
101
  ...commonSubOpts,
128
102
  socket,
129
- localFuncs: undefined,
103
+ onData: undefined,
130
104
  socket_id: socket?.id,
131
105
  });
132
106
 
@@ -141,13 +115,13 @@ async function subscribe(
141
115
  const { channelName, sendFirstData } = await pubSubManager.addSub({
142
116
  ...commonSubOpts,
143
117
  socket: undefined,
144
- localFuncs,
118
+ onData,
145
119
  socket_id: undefined,
146
120
  });
147
121
 
148
122
  const unsubscribe = async () => {
149
123
  const pubSubManager = await this.dboBuilder.getPubSubManager();
150
- pubSubManager.removeSubscription(channelName, { type: "local", localFuncs });
124
+ pubSubManager.removeSubscription(channelName, { type: "local", onData });
151
125
  };
152
126
  await this._log({
153
127
  command: "subscribe",
package/lib/Logging.ts CHANGED
@@ -58,6 +58,8 @@ export namespace EventTypes {
58
58
  tableName: string;
59
59
  localParams?: LocalParams;
60
60
  };
61
+ type MapValue<M> = M extends Map<any, infer V> ? V : never;
62
+
61
63
  export type SyncOrSub = DebugInfo & {
62
64
  type: "syncOrSub";
63
65
  connectedSocketIds: string[];
@@ -83,7 +85,7 @@ export namespace EventTypes {
83
85
  command: "notifListener.Finished";
84
86
  op_name: string | undefined;
85
87
  condition_ids_str: string | undefined;
86
- tableTriggers: PubSubManagerTriggers[string] | undefined;
88
+ tableTriggers: MapValue<PubSubManagerTriggers> | undefined;
87
89
  tableSyncs: string;
88
90
  state: "ok" | "error" | "no-triggers" | "invalid_condition_ids";
89
91
  })
@@ -20,11 +20,11 @@ import { initialiseEventTriggers } from "./initialiseEventTriggers";
20
20
  import { refreshTriggers } from "./refreshTriggers";
21
21
 
22
22
  import type { AnyObject, FieldFilter, SelectParams, WAL } from "prostgles-types";
23
- import { CHANNELS, type SubscribeOptions } from "prostgles-types";
23
+ import { CHANNELS, getSerialisableError, type SubscribeOptions } from "prostgles-types";
24
24
 
25
25
  import { find, pickKeys } from "prostgles-types";
26
- import type { LocalFuncs } from "../DboBuilder/ViewHandler/subscribe";
27
- import { getOnDataFunc, matchesLocalFuncs } from "../DboBuilder/ViewHandler/subscribe";
26
+ import type { OnData } from "../DboBuilder/ViewHandler/subscribe";
27
+ import { matchesLocalFuncs } from "../DboBuilder/ViewHandler/subscribe";
28
28
  import type { EventTypes } from "../Logging";
29
29
  import type { ParsedTableRule } from "../PublishParser/PublishParser";
30
30
  import { syncData } from "../SyncReplication";
@@ -109,7 +109,7 @@ export type SubscriptionParams = {
109
109
  subscribeOptions: SubscribeOptions;
110
110
  tracked_columns: string[] | undefined;
111
111
 
112
- localFuncs?: LocalFuncs;
112
+ onData?: OnData;
113
113
  socket: PRGLIOSocket | undefined;
114
114
 
115
115
  lastPushed: number;
@@ -125,7 +125,7 @@ export type Subscription = Pick<
125
125
  | "lastPushed"
126
126
  | "channel_name"
127
127
  | "is_ready"
128
- | "localFuncs"
128
+ | "onData"
129
129
  | "socket"
130
130
  | "socket_id"
131
131
  | "table_info"
@@ -136,7 +136,7 @@ export type Subscription = Pick<
136
136
  triggers: AddTriggerParams[];
137
137
  };
138
138
 
139
- export type PubSubManagerTriggers = Record<string, { condition: string; hash: string }[]>;
139
+ export type PubSubManagerTriggers = Map<string, { condition: string; hash: string }[]>;
140
140
 
141
141
  /**
142
142
  * Used to facilitate table subscribe and sync
@@ -165,7 +165,7 @@ export class PubSubManager {
165
165
  * Triggers used for sync/sub that reflect prostgles.app_triggers.
166
166
  * Updated through refreshTriggers()
167
167
  */
168
- _triggers: PubSubManagerTriggers = {};
168
+ _triggers: PubSubManagerTriggers = new Map();
169
169
  sockets: Record<string, PRGLIOSocket> = {};
170
170
 
171
171
  subs: Subscription[] = [];
@@ -218,13 +218,13 @@ export class PubSubManager {
218
218
 
219
219
  getClientSubs({
220
220
  channel_name,
221
- localFuncs,
221
+ onData,
222
222
  socket_id,
223
- }: Pick<Subscription, "localFuncs" | "socket_id" | "channel_name">): Subscription[] {
223
+ }: Pick<Subscription, "onData" | "socket_id" | "channel_name">): Subscription[] {
224
224
  return this.subs.filter((s) => {
225
225
  return (
226
226
  s.channel_name === channel_name &&
227
- (matchesLocalFuncs(localFuncs, s.localFuncs) || (socket_id && s.socket_id === socket_id))
227
+ (matchesLocalFuncs(onData, s.onData) || (socket_id && s.socket_id === socket_id))
228
228
  );
229
229
  });
230
230
  }
@@ -236,30 +236,31 @@ export class PubSubManager {
236
236
 
237
237
  removeSubscription = (
238
238
  channelName: string,
239
- subInfo: { type: "local"; localFuncs: LocalFuncs } | { type: "ws"; socket: PRGLIOSocket },
239
+ subInfo: { type: "local"; onData: OnData } | { type: "ws"; socket: PRGLIOSocket },
240
240
  ) => {
241
241
  const matchingSubIdx = this.subs.findIndex(
242
242
  (s) =>
243
243
  s.channel_name === channelName &&
244
244
  (subInfo.type === "local" ?
245
- getOnDataFunc(subInfo.localFuncs) === getOnDataFunc(s.localFuncs)
245
+ subInfo.onData === s.onData
246
246
  : subInfo.socket.id === s.socket?.id),
247
247
  );
248
- if (matchingSubIdx > -1) {
249
- const tableName = this.subs[matchingSubIdx]!.table_info.name;
250
- const oldActiveTriggers = this.getActiveTriggers(tableName);
248
+ const matchingSub = this.subs[matchingSubIdx];
249
+ if (matchingSub) {
250
+ /** Ensure we check and refresh related table/view triggers as well */
251
+ const oldActiveTriggers = this.getAllActiveTriggers();
251
252
  this.subs.splice(matchingSubIdx, 1);
252
- const newActiveTriggers = this.getActiveTriggers(tableName);
253
+ const newActiveTriggers = this.getAllActiveTriggers();
254
+ const tableNames = new Set(
255
+ [...oldActiveTriggers, ...newActiveTriggers].map((t) => t.tableName),
256
+ );
253
257
  if (newActiveTriggers.length < oldActiveTriggers.length) {
254
- this.deleteOrphanedTriggers(tableName);
258
+ this.deleteOrphanedTriggers(tableNames);
255
259
  }
256
260
  } else {
257
- console.error(
258
- "Could not unsubscribe localFunc. Subscription might not have initialised yet",
259
- {
260
- channelName,
261
- },
262
- );
261
+ console.error("Could not unsubscribe. Subscription might not have initialised yet", {
262
+ channelName,
263
+ });
263
264
  }
264
265
  };
265
266
 
@@ -272,11 +273,11 @@ export class PubSubManager {
272
273
  notifListener = notifListener.bind(this);
273
274
 
274
275
  getTriggerInfo = (tableName: string) => {
275
- const tableTriggerConditions = this._triggers[tableName]?.map((cond, idx) => ({
276
+ const tableTriggerConditions = this._triggers.get(tableName)?.map((triggerInfo, idx) => ({
276
277
  idx,
277
- ...cond,
278
- subs: this.getTriggerSubs(tableName, cond.condition),
279
- syncs: this.getSyncs(tableName, cond.condition),
278
+ ...triggerInfo,
279
+ subs: this.getTriggerSubs(tableName, triggerInfo.condition),
280
+ syncs: this.getSyncs(tableName, triggerInfo.condition),
280
281
  }));
281
282
  return tableTriggerConditions;
282
283
  };
@@ -287,13 +288,20 @@ export class PubSubManager {
287
288
  return activeTriggers;
288
289
  };
289
290
 
290
- getSubData = async (
291
- sub: Subscription,
292
- ): Promise<{ data: any[]; err?: undefined } | { data?: undefined; err: unknown }> => {
293
- const { table_info, filter, selectParams: params, table_rules, socket, localFuncs } = sub; //, subOne = false
291
+ getAllActiveTriggers = () => {
292
+ return Array.from(this._triggers.keys()).flatMap((tableName) => {
293
+ return this.getActiveTriggers(tableName).map((triggerInfo) => ({
294
+ ...triggerInfo,
295
+ tableName,
296
+ }));
297
+ });
298
+ };
299
+
300
+ getSubData = async (sub: Subscription) => {
301
+ const { table_info, filter, selectParams: params, table_rules, socket, onData } = sub; //, subOne = false
294
302
  const { name: table_name } = table_info;
295
303
  const tableHandler = this.dbo[table_name];
296
- if (!localFuncs && !socket) {
304
+ if (!onData && !socket) {
297
305
  throw new Error("Subscription must have either localFuncs or socket");
298
306
  }
299
307
  if (!tableHandler?.find) {
@@ -301,7 +309,7 @@ export class PubSubManager {
301
309
  }
302
310
 
303
311
  try {
304
- const data = await tableHandler.find(
312
+ const data = (await tableHandler.find(
305
313
  filter,
306
314
  params,
307
315
  undefined,
@@ -309,10 +317,10 @@ export class PubSubManager {
309
317
  socket && {
310
318
  clientReq: { socket },
311
319
  },
312
- );
320
+ )) as AnyObject[];
313
321
  return { data };
314
322
  } catch (err) {
315
- return { err };
323
+ return { err: getSerialisableError(err) || "Unknown error fetching subscription data" };
316
324
  }
317
325
  };
318
326
 
@@ -401,23 +409,23 @@ export class PubSubManager {
401
409
  /** Throttle trigger deletes */
402
410
  deletingOrphanedTriggers:
403
411
  | {
404
- tableNames: string[];
412
+ tableNames: Set<string>;
405
413
  timeout: NodeJS.Timeout;
406
414
  }
407
415
  | undefined;
408
- deleteOrphanedTriggers = (latestTableName: string) => {
416
+ deleteOrphanedTriggers = (latestTableNames: Set<string>) => {
409
417
  this.deletingOrphanedTriggers ??= {
410
- tableNames: [latestTableName],
418
+ tableNames: latestTableNames,
411
419
  timeout: setTimeout(() => {
412
420
  const tableNames = this.deletingOrphanedTriggers!.tableNames;
413
421
  this.deletingOrphanedTriggers = undefined;
414
- void deleteOrphanedTriggers.bind(this)(tableNames);
422
+ void deleteOrphanedTriggers.bind(this)(Array.from(tableNames));
415
423
  }, 1000),
416
424
  };
417
425
 
418
- if (!this.deletingOrphanedTriggers.tableNames.includes(latestTableName)) {
419
- this.deletingOrphanedTriggers.tableNames.push(latestTableName);
420
- }
426
+ latestTableNames.forEach((latestTableName) => {
427
+ this.deletingOrphanedTriggers?.tableNames.add(latestTableName);
428
+ });
421
429
  };
422
430
 
423
431
  addingTrigger: any;
@@ -1,7 +1,12 @@
1
1
  import type { SubscriptionChannels } from "prostgles-types";
2
2
  import type { VoidFunction } from "../SchemaWatch/SchemaWatch";
3
3
  import { tout } from "./init/initPubSubManager";
4
- import type { BasicCallback, PubSubManager, Subscription, SubscriptionParams } from "./PubSubManager";
4
+ import type {
5
+ BasicCallback,
6
+ PubSubManager,
7
+ Subscription,
8
+ SubscriptionParams,
9
+ } from "./PubSubManager";
5
10
  import { parseCondition } from "./PubSubManagerUtils";
6
11
  import type { AddTriggerParams } from "./addTrigger";
7
12
 
@@ -17,11 +22,11 @@ type AddSubResult = SubscriptionChannels & {
17
22
  /* The distinct list of {table_name, condition} must have a corresponding trigger in the database */
18
23
  export async function addSub(
19
24
  this: PubSubManager,
20
- subscriptionParams: Omit<AddSubscriptionParams, "channel_name" | "parentSubParams">
25
+ subscriptionParams: Omit<AddSubscriptionParams, "channel_name" | "parentSubParams">,
21
26
  ): Promise<AddSubResult> {
22
27
  const {
23
28
  socket,
24
- localFuncs,
29
+ onData,
25
30
  table_rules,
26
31
  filter = {},
27
32
  selectParams = {},
@@ -33,10 +38,10 @@ export async function addSub(
33
38
  } = subscriptionParams;
34
39
  const table_name = table_info.name;
35
40
 
36
- if (!socket && !localFuncs) {
41
+ if (!socket && !onData) {
37
42
  throw "socket AND func missing";
38
43
  }
39
- if (socket && localFuncs) {
44
+ if (socket && onData) {
40
45
  throw "addSub: cannot have socket AND func";
41
46
  }
42
47
 
@@ -50,7 +55,7 @@ export async function addSub(
50
55
  const newSub: Subscription = {
51
56
  channel_name,
52
57
  filter,
53
- localFuncs,
58
+ onData,
54
59
  selectParams: selectParams,
55
60
  lastPushed: 0,
56
61
  socket,
@@ -74,7 +79,7 @@ export async function addSub(
74
79
  const [matchingSub] = this.getClientSubs(newSub);
75
80
  if (matchingSub) {
76
81
  console.error(
77
- `Trying to add a duplicate ${localFuncs ? "local" : "socket"} sub for: ${channel_name}`
82
+ `Trying to add a duplicate ${onData ? "local" : "socket"} sub for: ${channel_name}`,
78
83
  );
79
84
  return result;
80
85
  }
@@ -104,7 +109,7 @@ export async function addSub(
104
109
  void this.pushSubData(newSub);
105
110
  };
106
111
 
107
- if (localFuncs) {
112
+ if (onData) {
108
113
  /**
109
114
  * Must ensure sub will start sending data after all triggers are set up.
110
115
  * Socket clients are not affected as they need to confirm they are ready to receive data
@@ -111,7 +111,7 @@ export async function addSync(
111
111
  }
112
112
  });
113
113
  } else {
114
- console.warn("UNCLOSED DUPLICATE SYNC FOUND", existing.channel_name);
114
+ console.warn("addSync: Client tried to create a duplicate sync", existing.channel_name);
115
115
  }
116
116
 
117
117
  return newSync;
@@ -8,7 +8,7 @@ import { asValue, DELIMITER, NOTIF_CHANNEL, NOTIF_TYPE } from "../PubSubManagerU
8
8
  */
9
9
  export const udtNamesWithoutEqualityComparison = ["json", "xml"];
10
10
  export const getDataWatchFunctionQuery = (debugMode: boolean | undefined) => {
11
- return `
11
+ const dataWatchFunctionQuery = `
12
12
 
13
13
  CREATE OR REPLACE FUNCTION ${DB_OBJ_NAMES.data_watch_func}() RETURNS TRIGGER
14
14
  AS $$
@@ -161,6 +161,25 @@ export const getDataWatchFunctionQuery = (debugMode: boolean | undefined) => {
161
161
  COMMENT ON FUNCTION ${DB_OBJ_NAMES.data_watch_func} IS 'Prostgles internal function used to notify when data in the table changed';
162
162
 
163
163
  `;
164
+
165
+ /** Ensure every execute is followed by EXCEPTION catch to ensure we remove stale schema/faulty triggers */
166
+ const queryLines = dataWatchFunctionQuery
167
+ .split("\n")
168
+ .map((l) => l.trim())
169
+ .filter((l) => l);
170
+ queryLines.forEach((line, lineIndex) => {
171
+ const nextLine = queryLines[lineIndex + 1] ?? "";
172
+ if (
173
+ line.toUpperCase().startsWith("EXECUTE") &&
174
+ !nextLine.toUpperCase().startsWith("EXCEPTION")
175
+ ) {
176
+ throw new Error(
177
+ `Every EXECUTE statement in the data watch function must be followed by an EXCEPTION block to catch errors and avoid stale triggers. Problematic line: ${line}`,
178
+ );
179
+ }
180
+ });
181
+
182
+ return dataWatchFunctionQuery;
164
183
  };
165
184
 
166
185
  /**
@@ -208,6 +227,14 @@ IF TG_OP = 'UPDATE' THEN
208
227
 
209
228
  BEGIN
210
229
  EXECUTE query INTO changed_columns;
230
+ EXCEPTION WHEN OTHERS THEN
231
+
232
+ has_errors := TRUE;
233
+
234
+ GET STACKED DIAGNOSTICS
235
+ err_text = MESSAGE_TEXT,
236
+ err_detail = PG_EXCEPTION_DETAIL,
237
+ err_hint = PG_EXCEPTION_HINT;
211
238
  END;
212
239
 
213
240
  /* It is possible to get no changes */