prostgles-client 4.0.164 → 4.0.166

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.
package/lib/prostgles.ts CHANGED
@@ -41,6 +41,7 @@ import { FunctionQueuer } from "./FunctionQueuer";
41
41
  import { isEqual, useFetch, useSubscribe, useSync } from "./react-hooks";
42
42
  import { SQL } from "./SQL";
43
43
  import type { DbTableSync, Sync, SyncDataItem, SyncOne, SyncOneOptions, SyncOptions, SyncedTable } from "./SyncedTable/SyncedTable";
44
+ import { getSubscriptionHandler } from "./subscriptionHandler";
44
45
 
45
46
  const DEBUG_KEY = "DEBUG_SYNCEDTABLE";
46
47
  export const hasWnd = typeof window !== "undefined";
@@ -52,8 +53,8 @@ export const debug: any = function (...args: any[]) {
52
53
 
53
54
  export { MethodHandler, SQLResult, asName };
54
55
 
55
- export * from "./react-hooks";
56
- export * from "./useProstglesClient";
56
+ export * from "./react-hooks";
57
+ export * from "./useProstglesClient";
57
58
 
58
59
 
59
60
  export type ViewHandlerClient<T extends AnyObject = AnyObject, S extends DBSchema | void = void> = ViewHandler<T, S> & {
@@ -133,7 +134,17 @@ type SyncDebugEvent = {
133
134
  command: keyof ClientSyncHandles;
134
135
  data: AnyObject;
135
136
  };
136
- type DebugEvent = {
137
+ type DebugEvent =
138
+ | {
139
+ type: "table";
140
+ command: "unsubscribe";
141
+ tableName: string;
142
+ /**
143
+ * If defined then the server will be asked to unsubscribe
144
+ */
145
+ unsubChannel?: string;
146
+ }
147
+ | {
137
148
  type: "table";
138
149
  command: keyof TableHandlerClient;
139
150
  tableName: string;
@@ -168,28 +179,15 @@ export type InitOptions<DBSchema = void> = {
168
179
  onDebug?: (event: DebugEvent) => any;
169
180
  }
170
181
 
171
- type AnyFunction = (...args: any[]) => any;
182
+ export type AnyFunction = (...args: any[]) => any;
172
183
 
173
- type CoreParams = {
184
+ export type CoreParams = {
174
185
  tableName: string;
175
186
  command: string;
176
187
  param1?: AnyObject;
177
188
  param2?: AnyObject;
178
189
  };
179
190
 
180
- type Subscription = CoreParams & {
181
- lastData: any;
182
- onCall: AnyFunction,
183
- handlers: AnyFunction[];
184
- errorHandlers: (AnyFunction | undefined)[];
185
- unsubChannel: string;
186
- destroy: () => any;
187
- };
188
-
189
- type Subscriptions = {
190
- [key: string]: Subscription
191
- };
192
-
193
191
  export type onUpdatesParams = { data: object[]; isSynced: boolean }
194
192
 
195
193
  export type SyncInfo = {
@@ -224,17 +222,19 @@ export function prostgles<DBSchema>(initOpts: InitOptions<DBSchema>, syncedTable
224
222
  }
225
223
 
226
224
  const preffix = CHANNELS._preffix;
227
- const subscriptions: Subscriptions = {};
228
225
 
229
226
  let syncedTables: Record<string, any> = {};
230
227
 
228
+ //@ts-ignore
229
+ const subscriptionHandler = getSubscriptionHandler(initOpts);
230
+
231
231
  let syncs: Syncs = {};
232
232
  let state: undefined | "connected" | "disconnected" | "reconnected";
233
- const sql = new SQL()
233
+ const sql = new SQL();
234
234
 
235
235
  const destroySyncs = async () => {
236
- debug("destroySyncs", { subscriptions, syncedTables });
237
- await Promise.all(Object.values(subscriptions).map(s => s.destroy()));
236
+ debug("destroySyncs", { syncedTables });
237
+ // await subscriptionHandler.destroy();
238
238
  syncs = {};
239
239
  Object.values(syncedTables).map((s: any) => {
240
240
  if (s && s.destroy) s.destroy();
@@ -242,32 +242,6 @@ export function prostgles<DBSchema>(initOpts: InitOptions<DBSchema>, syncedTable
242
242
  syncedTables = {};
243
243
  }
244
244
 
245
- function _unsubscribe(channelName: string, unsubChannel: string, handler: AnyFunction) {
246
- debug("_unsubscribe", { channelName, handler });
247
-
248
- return new Promise((resolve, reject) => {
249
- const sub = subscriptions[channelName];
250
- if (sub) {
251
- sub.handlers = sub.handlers.filter(h => h !== handler);
252
- if (!sub.handlers.length) {
253
- socket.emit(unsubChannel, {}, (err: any, _res: any) => {
254
- if (err) console.error(err);
255
- else reject(err);
256
- });
257
- socket.removeListener(channelName, sub.onCall);
258
- delete subscriptions[channelName];
259
-
260
- /* Not waiting for server confirmation to speed things up */
261
- resolve(true)
262
- } else {
263
- resolve(true)
264
- }
265
- } else {
266
- resolve(true)
267
- }
268
- });
269
- }
270
-
271
245
  function _unsync(channelName: string, triggers: ClientSyncHandles) {
272
246
  debug("_unsync", { channelName, triggers })
273
247
  return new Promise((resolve, reject) => {
@@ -306,21 +280,21 @@ export function prostgles<DBSchema>(initOpts: InitOptions<DBSchema>, syncedTable
306
280
  });
307
281
  });
308
282
  }
309
- /**
310
- * Obtaines subscribe channel from server
311
- */
312
- function addServerSub({ tableName, command, param1, param2 }: CoreParams): Promise<SubscriptionChannels> {
313
- return new Promise((resolve, reject) => {
314
- socket.emit(preffix, { tableName, command, param1, param2 }, (err?: any, res?: SubscriptionChannels) => {
315
- if (err) {
316
- console.error(err);
317
- reject(err);
318
- } else if (res) {
319
- resolve(res);
320
- }
321
- });
322
- });
323
- }
283
+ // /**
284
+ // * Obtaines subscribe channel from server
285
+ // */
286
+ // function addServerSub({ tableName, command, param1, param2 }: CoreParams): Promise<SubscriptionChannels> {
287
+ // return new Promise((resolve, reject) => {
288
+ // socket.emit(preffix, { tableName, command, param1, param2 }, (err?: any, res?: SubscriptionChannels) => {
289
+ // if (err) {
290
+ // console.error(err);
291
+ // reject(err);
292
+ // } else if (res) {
293
+ // resolve(res);
294
+ // }
295
+ // });
296
+ // });
297
+ // }
324
298
 
325
299
  const addSyncQueuer = new FunctionQueuer(_addSync, ([{ tableName }]) => tableName);
326
300
  async function addSync(params: CoreParams, triggers: ClientSyncHandles): Promise<any> {
@@ -428,112 +402,6 @@ export function prostgles<DBSchema>(initOpts: InitOptions<DBSchema>, syncedTable
428
402
 
429
403
  }
430
404
 
431
- /**
432
- * Can be used concurrently
433
- */
434
- const addSubQueuer = new FunctionQueuer(_addSub, ([_, { tableName }]) => tableName);
435
- async function addSub(dbo: any, params: CoreParams, onChange: AnyFunction, _onError?: AnyFunction): Promise<SubscriptionHandler> {
436
- return addSubQueuer.run([dbo, params, onChange, _onError]);
437
- }
438
-
439
- /**
440
- * Do NOT use concurrently
441
- */
442
- async function _addSub(dbo: any, { tableName, command, param1, param2 }: CoreParams, onChange: AnyFunction, _onError?: AnyFunction): Promise<SubscriptionHandler> {
443
-
444
- function makeHandler(channelName: string, unsubChannel: string) {
445
-
446
- const unsubscribe = function () {
447
- return _unsubscribe(channelName, unsubChannel, onChange);
448
- }
449
- let res: any = { unsubscribe, filter: { ...param1 } }
450
- /* Some dbo sorting was done to make sure this will work */
451
- if (dbo[tableName].update) {
452
- res = {
453
- ...res,
454
- update: function (newData: AnyObject, updateParams: UpdateParams) {
455
- return dbo[tableName].update(param1, newData, updateParams);
456
- }
457
- }
458
- }
459
- if (dbo[tableName].delete) {
460
- res = {
461
- ...res,
462
- delete: function (deleteParams: DeleteParams) {
463
- return dbo[tableName].delete(param1, deleteParams);
464
- }
465
- }
466
- }
467
- return Object.freeze(res);
468
- }
469
-
470
- const existing = Object.entries(subscriptions).find(([ch, s]) => {
471
- return (
472
- s.tableName === tableName &&
473
- s.command === command &&
474
- JSON.stringify(s.param1 || {}) === JSON.stringify(param1 || {}) &&
475
- JSON.stringify(s.param2 || {}) === JSON.stringify(param2 || {})
476
- );
477
- });
478
-
479
- if (existing) {
480
- const existingCh = existing[0];
481
- existing[1].handlers.push(onChange);
482
- existing[1].errorHandlers.push(_onError);
483
- setTimeout(() => {
484
- if (subscriptions[existingCh]?.lastData) {
485
- onChange(subscriptions[existingCh]?.lastData)
486
- }
487
- }, 10)
488
- return makeHandler(existingCh, existing[1].unsubChannel);
489
- }
490
-
491
- const { channelName, channelNameReady, channelNameUnsubscribe } = await addServerSub({ tableName, command, param1, param2 })
492
-
493
- const onCall = function (data: any) {
494
- /* TO DO: confirm receiving data or server will unsubscribe */
495
- // if(cb) cb(true);
496
- const sub = subscriptions[channelName];
497
- if (sub) {
498
- if (data.data) {
499
- sub.lastData = data.data;
500
- sub.handlers.forEach(h => {
501
- h(data.data);
502
- });
503
- } else if (data.err) {
504
- sub.errorHandlers.forEach(h => {
505
- h?.(data.err);
506
- });
507
- } else {
508
- console.error("INTERNAL ERROR: Unexpected data format from subscription: ", data)
509
- }
510
- } else {
511
- console.warn("Orphaned subscription: ", channelName)
512
- }
513
- }
514
- const onError = _onError || function (err: any) { console.error(`Uncaught error within running subscription \n ${channelName}`, err) }
515
-
516
- socket.on(channelName, onCall);
517
- subscriptions[channelName] = {
518
- lastData: undefined,
519
- tableName,
520
- command,
521
- param1,
522
- param2,
523
- onCall,
524
- unsubChannel: channelNameUnsubscribe,
525
- handlers: [onChange],
526
- errorHandlers: [onError],
527
- destroy: async () => {
528
- for await(const h of subscriptions[channelName]?.handlers ?? []){
529
- await _unsubscribe(channelName, channelNameUnsubscribe, h);
530
- }
531
- }
532
- }
533
- socket.emit(channelNameReady, { now: Date.now() });
534
- return makeHandler(channelName, channelNameUnsubscribe);
535
- }
536
-
537
405
  return new Promise((resolve, reject) => {
538
406
 
539
407
  socket.removeAllListeners(CHANNELS.CONNECTION)
@@ -560,11 +428,13 @@ export function prostgles<DBSchema>(initOpts: InitOptions<DBSchema>, syncedTable
560
428
  /* Schema = published schema */
561
429
  socket.on(CHANNELS.SCHEMA, async ({ joinTables = [], ...clientSchema }: ClientSchema) => {
562
430
  const { schema, methods, tableSchema, auth: authConfig, rawSQL, err } = clientSchema;
431
+
563
432
  /** Only destroy existing syncs if schema changed */
564
433
  const schemaDidNotChange = schemaAge?.clientSchema && isEqual(schemaAge.clientSchema, clientSchema)
565
434
  if(!schemaDidNotChange){
566
435
  await destroySyncs();
567
436
  }
437
+
568
438
  if ((state === "connected" || state === "reconnected") && onReconnect) {
569
439
  onReconnect(socket, err);
570
440
  if (err) {
@@ -684,7 +554,7 @@ export function prostgles<DBSchema>(initOpts: InitOptions<DBSchema>, syncedTable
684
554
  const subFunc = async function (param1 = {}, param2 = {}, onChange, onError) {
685
555
  await onDebug?.({ type: "table", command: command as typeof sub_commands[number], tableName, data: { param1, param2, onChange, onError } });
686
556
  checkSubscriptionArgs(param1, param2, onChange, onError);
687
- return addSub(dbo, { tableName, command, param1, param2 }, onChange, onError);
557
+ return subscriptionHandler.addSub(dbo, { tableName, command, param1, param2 }, onChange, onError);
688
558
  };
689
559
  dboTable[command] = subFunc;
690
560
  const SUBONE = "subscribeOne";
@@ -704,7 +574,7 @@ export function prostgles<DBSchema>(initOpts: InitOptions<DBSchema>, syncedTable
704
574
  checkSubscriptionArgs(param1, param2, onChange, onError);
705
575
 
706
576
  const onChangeOne = (rows) => { onChange(rows[0]) };
707
- return addSub(dbo, { tableName, command, param1, param2 }, onChangeOne, onError);
577
+ return subscriptionHandler.addSub(dbo, { tableName, command, param1, param2 }, onChangeOne, onError);
708
578
  };
709
579
  }
710
580
  } else {
@@ -741,16 +611,7 @@ export function prostgles<DBSchema>(initOpts: InitOptions<DBSchema>, syncedTable
741
611
  })
742
612
  });
743
613
 
744
-
745
- // Re-attach listeners
746
- Object.entries(subscriptions).forEach(async ([ch, s]) => {
747
- try {
748
- await addServerSub(s);
749
- socket.on(ch, s.onCall);
750
- } catch (err) {
751
- console.error("There was an issue reconnecting old subscriptions", err)
752
- }
753
- });
614
+ await subscriptionHandler.reAttachAll();
754
615
  Object.entries(syncs).forEach(async ([ch, s]) => {
755
616
  const firstTrigger = s.triggers[0];
756
617
  if(firstTrigger){
@@ -0,0 +1,207 @@
1
+ import { CHANNELS, type AnyObject, type DeleteParams, type SubscriptionChannels, type SubscriptionHandler, type UpdateParams } from "prostgles-types";
2
+ import { debug, isEqual, type AnyFunction, type CoreParams, type InitOptions } from "./prostgles";
3
+ import { FunctionQueuer } from "./FunctionQueuer";
4
+
5
+ type Subscription = CoreParams & {
6
+ lastData: any;
7
+ onCall: AnyFunction,
8
+ handlers: AnyFunction[];
9
+ errorHandlers: (AnyFunction | undefined)[];
10
+ unsubChannel: string;
11
+ reAttach: () => Promise<void>;
12
+ };
13
+
14
+ type Subscriptions = {
15
+ [key: string]: Subscription
16
+ };
17
+
18
+ const preffix = CHANNELS._preffix;
19
+
20
+ export const getSubscriptionHandler = <DBSchema>(initOpts: InitOptions<DBSchema>) => {
21
+ const { socket, onDebug, } = initOpts;
22
+
23
+ const subscriptions: Subscriptions = {};
24
+
25
+ const removeServerSub = (unsubChannel: string) => {
26
+ return new Promise((resolve, reject) => {
27
+ socket.emit(unsubChannel, {}, (err: any, _res: any) => {
28
+ if (err) {
29
+ console.error(err);
30
+ reject(err);
31
+ } else {
32
+ resolve(_res);
33
+ }
34
+ });
35
+ });
36
+ }
37
+
38
+ function _unsubscribe(channelName: string, unsubChannel: string, handler: AnyFunction, onDebug: InitOptions["onDebug"]): Promise<true> {
39
+ debug("_unsubscribe", { channelName, handler });
40
+
41
+ return new Promise((resolve, reject) => {
42
+ const sub = subscriptions[channelName];
43
+ if (sub) {
44
+ sub.handlers = sub.handlers.filter(h => h !== handler);
45
+ if (!sub.handlers.length) {
46
+ onDebug?.({ type: "table", command: "unsubscribe", tableName: sub.tableName });
47
+ removeServerSub(unsubChannel);
48
+ socket.removeListener(channelName, sub.onCall);
49
+ delete subscriptions[channelName];
50
+
51
+ /* Not waiting for server confirmation to speed things up */
52
+ resolve(true)
53
+ } else {
54
+ onDebug?.({ type: "table", command: "unsubscribe", tableName: sub.tableName, unsubChannel });
55
+ resolve(true)
56
+ }
57
+ } else {
58
+ resolve(true)
59
+ }
60
+ });
61
+ }
62
+
63
+
64
+ /**
65
+ * Obtaines subscribe channel from server
66
+ */
67
+ function addServerSub({ tableName, command, param1, param2 }: CoreParams): Promise<SubscriptionChannels> {
68
+ return new Promise((resolve, reject) => {
69
+ socket.emit(preffix, { tableName, command, param1, param2 }, (err?: any, res?: SubscriptionChannels) => {
70
+ if (err) {
71
+ console.error(err);
72
+ reject(err);
73
+ } else if (res) {
74
+ resolve(res);
75
+ }
76
+ });
77
+ });
78
+ }
79
+
80
+
81
+ /**
82
+ * Can be used concurrently
83
+ */
84
+ const addSubQueuer = new FunctionQueuer(_addSub, ([_, { tableName }]) => tableName);
85
+ const addSub = async (dbo: any, params: CoreParams, onChange: AnyFunction, _onError?: AnyFunction): Promise<SubscriptionHandler> => {
86
+ return addSubQueuer.run([dbo, params, onChange, _onError]);
87
+ }
88
+
89
+ /**
90
+ * Do NOT use concurrently
91
+ */
92
+ async function _addSub(dbo: any, { tableName, command, param1, param2 }: CoreParams, onChange: AnyFunction, _onError?: AnyFunction): Promise<SubscriptionHandler> {
93
+
94
+ const makeHandler = (channelName: string, unsubChannel: string) => {
95
+
96
+ const unsubscribe = function () {
97
+ return _unsubscribe(channelName, unsubChannel, onChange, onDebug);
98
+ }
99
+
100
+ let subHandlers: any = { unsubscribe, filter: { ...param1 } }
101
+
102
+ /* Some dbo sorting was done to make sure this will work */
103
+ if (dbo[tableName].update) {
104
+ subHandlers = {
105
+ ...subHandlers,
106
+ update: function (newData: AnyObject, updateParams: UpdateParams) {
107
+ return dbo[tableName].update(param1, newData, updateParams);
108
+ }
109
+ }
110
+ }
111
+ if (dbo[tableName].delete) {
112
+ subHandlers = {
113
+ ...subHandlers,
114
+ delete: function (deleteParams: DeleteParams) {
115
+ return dbo[tableName].delete(param1, deleteParams);
116
+ }
117
+ }
118
+ }
119
+ return Object.freeze(subHandlers);
120
+ }
121
+
122
+ const existing = Object.entries(subscriptions).find(([ch, s]) => {
123
+ return (
124
+ s.tableName === tableName &&
125
+ s.command === command &&
126
+ isEqual(s.param1 || {}, param1 || {}) &&
127
+ isEqual(s.param2 || {}, param2 || {})
128
+ );
129
+ });
130
+
131
+ if (existing) {
132
+ const existingCh = existing[0];
133
+ existing[1].handlers.push(onChange);
134
+ existing[1].errorHandlers.push(_onError);
135
+ setTimeout(() => {
136
+ if (subscriptions[existingCh]?.lastData) {
137
+ onChange(subscriptions[existingCh]?.lastData)
138
+ }
139
+ }, 10)
140
+ return makeHandler(existingCh, existing[1].unsubChannel);
141
+ }
142
+
143
+ const { channelName, channelNameReady, channelNameUnsubscribe } = await addServerSub({ tableName, command, param1, param2 })
144
+
145
+ const onCall = function (data: any) {
146
+ /* TO DO: confirm receiving data or server will unsubscribe */
147
+ // if(cb) cb(true);
148
+ const sub = subscriptions[channelName];
149
+ if (sub) {
150
+ if (data.data) {
151
+ sub.lastData = data.data;
152
+ sub.handlers.forEach(h => {
153
+ h(data.data);
154
+ });
155
+ } else if (data.err) {
156
+ sub.errorHandlers.forEach(h => {
157
+ h?.(data.err);
158
+ });
159
+ } else {
160
+ console.error("INTERNAL ERROR: Unexpected data format from subscription: ", data)
161
+ }
162
+ } else {
163
+ console.warn("Orphaned subscription: ", channelName)
164
+ }
165
+ }
166
+ const onError = _onError || function (err: any) { console.error(`Uncaught error within running subscription \n ${channelName}`, err) }
167
+
168
+ socket.on(channelName, onCall);
169
+ subscriptions[channelName] = {
170
+ lastData: undefined,
171
+ tableName,
172
+ command,
173
+ param1,
174
+ param2,
175
+ onCall,
176
+ unsubChannel: channelNameUnsubscribe,
177
+ handlers: [onChange],
178
+ errorHandlers: [onError],
179
+ reAttach: async () => {
180
+ await removeServerSub(channelNameUnsubscribe).catch(console.error);
181
+ await addServerSub({ tableName, command, param1, param2 });
182
+ socket.emit(channelNameReady, { now: Date.now() });
183
+ }
184
+ }
185
+ socket.emit(channelNameReady, { now: Date.now() });
186
+ return makeHandler(channelName, channelNameUnsubscribe);
187
+ }
188
+
189
+ const reAttachAll = async () => {
190
+ for await (const s of Object.values(subscriptions)) {
191
+ try {
192
+ await s.reAttach();
193
+ } catch (err) {
194
+ console.error("There was an issue reconnecting old subscriptions", err, s)
195
+ Object.values(s.errorHandlers).forEach(h => h?.(err));
196
+ }
197
+ }
198
+ }
199
+
200
+ return {
201
+ addSub,
202
+ subscriptions,
203
+ addServerSub,
204
+ _unsubscribe,
205
+ reAttachAll,
206
+ }
207
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prostgles-client",
3
- "version": "4.0.164",
3
+ "version": "4.0.166",
4
4
  "description": "Reactive client for Postgres",
5
5
  "main": "dist/prostgles-full.js",
6
6
  "types": "dist/prostgles-full.d.ts",
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "..": {
19
19
  "name": "prostgles-client",
20
- "version": "4.0.163",
20
+ "version": "4.0.165",
21
21
  "license": "MIT",
22
22
  "dependencies": {
23
23
  "prostgles-types": "^4.0.112"