prostgles-server 3.0.86 → 3.0.88

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 (216) hide show
  1. package/.eslintignore +5 -0
  2. package/.eslintrc.json +30 -0
  3. package/dist/DBEventsManager.js +1 -1
  4. package/dist/DBEventsManager.js.map +1 -1
  5. package/dist/DBSchemaBuilder.d.ts.map +1 -1
  6. package/dist/DBSchemaBuilder.js +6 -6
  7. package/dist/DBSchemaBuilder.js.map +1 -1
  8. package/dist/DboBuilder/QueryBuilder/Functions.js +9 -9
  9. package/dist/DboBuilder/QueryBuilder/Functions.js.map +1 -1
  10. package/dist/DboBuilder/QueryBuilder/QueryBuilder.d.ts.map +1 -1
  11. package/dist/DboBuilder/QueryBuilder/QueryBuilder.js +8 -7
  12. package/dist/DboBuilder/QueryBuilder/QueryBuilder.js.map +1 -1
  13. package/dist/DboBuilder/QueryBuilder/makeSelectQuery.d.ts.map +1 -1
  14. package/dist/DboBuilder/QueryBuilder/makeSelectQuery.js +4 -4
  15. package/dist/DboBuilder/QueryBuilder/makeSelectQuery.js.map +1 -1
  16. package/dist/DboBuilder/TableHandler.d.ts.map +1 -1
  17. package/dist/DboBuilder/TableHandler.js +18 -20
  18. package/dist/DboBuilder/TableHandler.js.map +1 -1
  19. package/dist/DboBuilder/ViewHandler.d.ts +0 -10
  20. package/dist/DboBuilder/ViewHandler.d.ts.map +1 -1
  21. package/dist/DboBuilder/ViewHandler.js +50 -63
  22. package/dist/DboBuilder/ViewHandler.js.map +1 -1
  23. package/dist/DboBuilder/delete.js +0 -1
  24. package/dist/DboBuilder/delete.js.map +1 -1
  25. package/dist/DboBuilder/getColumns.js +5 -4
  26. package/dist/DboBuilder/getColumns.js.map +1 -1
  27. package/dist/DboBuilder/getCondition.d.ts.map +1 -1
  28. package/dist/DboBuilder/getCondition.js +6 -6
  29. package/dist/DboBuilder/getCondition.js.map +1 -1
  30. package/dist/DboBuilder/insert.js +15 -16
  31. package/dist/DboBuilder/insert.js.map +1 -1
  32. package/dist/DboBuilder/insertDataParse.d.ts.map +1 -1
  33. package/dist/DboBuilder/insertDataParse.js +9 -10
  34. package/dist/DboBuilder/insertDataParse.js.map +1 -1
  35. package/dist/DboBuilder/parseUpdateRules.js +2 -2
  36. package/dist/DboBuilder/parseUpdateRules.js.map +1 -1
  37. package/dist/DboBuilder/runSQL.d.ts.map +1 -1
  38. package/dist/DboBuilder/runSQL.js +5 -5
  39. package/dist/DboBuilder/runSQL.js.map +1 -1
  40. package/dist/DboBuilder/subscribe.js +3 -3
  41. package/dist/DboBuilder/subscribe.js.map +1 -1
  42. package/dist/DboBuilder/update.js +5 -6
  43. package/dist/DboBuilder/update.js.map +1 -1
  44. package/dist/DboBuilder/uploadFile.d.ts.map +1 -1
  45. package/dist/DboBuilder/uploadFile.js +1 -1
  46. package/dist/DboBuilder/uploadFile.js.map +1 -1
  47. package/dist/DboBuilder.d.ts.map +1 -1
  48. package/dist/DboBuilder.js +13 -14
  49. package/dist/DboBuilder.js.map +1 -1
  50. package/dist/FileManager.d.ts.map +1 -1
  51. package/dist/FileManager.js +3 -5
  52. package/dist/FileManager.js.map +1 -1
  53. package/dist/Filtering.js +7 -7
  54. package/dist/Filtering.js.map +1 -1
  55. package/dist/JSONBValidation/validate_jsonb_schema_sql.d.ts +3 -0
  56. package/dist/JSONBValidation/validate_jsonb_schema_sql.d.ts.map +1 -0
  57. package/dist/JSONBValidation/validate_jsonb_schema_sql.js +295 -0
  58. package/dist/JSONBValidation/validate_jsonb_schema_sql.js.map +1 -0
  59. package/dist/JSONBValidation/validation.d.ts +108 -0
  60. package/dist/JSONBValidation/validation.d.ts.map +1 -0
  61. package/dist/JSONBValidation/validation.js +222 -0
  62. package/dist/JSONBValidation/validation.js.map +1 -0
  63. package/dist/PostgresNotifListenManager.js +1 -1
  64. package/dist/PostgresNotifListenManager.js.map +1 -1
  65. package/dist/Prostgles.d.ts.map +1 -1
  66. package/dist/Prostgles.js +20 -20
  67. package/dist/Prostgles.js.map +1 -1
  68. package/dist/PubSubManager/PubSubManager.d.ts.map +1 -1
  69. package/dist/PubSubManager/PubSubManager.js +10 -8
  70. package/dist/PubSubManager/PubSubManager.js.map +1 -1
  71. package/dist/PubSubManager/initPubSubManager.d.ts.map +1 -1
  72. package/dist/PubSubManager/initPubSubManager.js +10 -7
  73. package/dist/PubSubManager/initPubSubManager.js.map +1 -1
  74. package/dist/PublishParser.d.ts.map +1 -1
  75. package/dist/PublishParser.js +122 -125
  76. package/dist/PublishParser.js.map +1 -1
  77. package/dist/SyncReplication.d.ts.map +1 -1
  78. package/dist/SyncReplication.js +19 -16
  79. package/dist/SyncReplication.js.map +1 -1
  80. package/dist/TableConfig.d.ts +9 -5
  81. package/dist/TableConfig.d.ts.map +1 -1
  82. package/dist/TableConfig.js +33 -12
  83. package/dist/TableConfig.js.map +1 -1
  84. package/dist/index.js +1 -1
  85. package/dist/index.js.map +1 -1
  86. package/dist/shortestPath.js +11 -11
  87. package/dist/shortestPath.js.map +1 -1
  88. package/dist/validation.d.ts +50 -24
  89. package/dist/validation.d.ts.map +1 -1
  90. package/dist/validation.js +177 -53
  91. package/dist/validation.js.map +1 -1
  92. package/lib/AuthHandler.d.ts +11 -11
  93. package/lib/AuthHandler.d.ts.map +1 -1
  94. package/lib/DBEventsManager.js +1 -1
  95. package/lib/DBEventsManager.ts +1 -1
  96. package/lib/DBSchemaBuilder.d.ts +3 -3
  97. package/lib/DBSchemaBuilder.d.ts.map +1 -1
  98. package/lib/DBSchemaBuilder.js +6 -6
  99. package/lib/DBSchemaBuilder.ts +10 -9
  100. package/lib/DboBuilder/QueryBuilder/Functions.d.ts +3 -3
  101. package/lib/DboBuilder/QueryBuilder/Functions.d.ts.map +1 -1
  102. package/lib/DboBuilder/QueryBuilder/Functions.js +9 -9
  103. package/lib/DboBuilder/QueryBuilder/Functions.ts +13 -13
  104. package/lib/DboBuilder/QueryBuilder/QueryBuilder.d.ts +3 -3
  105. package/lib/DboBuilder/QueryBuilder/QueryBuilder.d.ts.map +1 -1
  106. package/lib/DboBuilder/QueryBuilder/QueryBuilder.js +8 -7
  107. package/lib/DboBuilder/QueryBuilder/QueryBuilder.ts +12 -12
  108. package/lib/DboBuilder/QueryBuilder/makeSelectQuery.d.ts.map +1 -1
  109. package/lib/DboBuilder/QueryBuilder/makeSelectQuery.js +4 -4
  110. package/lib/DboBuilder/QueryBuilder/makeSelectQuery.ts +5 -5
  111. package/lib/DboBuilder/TableHandler.d.ts +1 -1
  112. package/lib/DboBuilder/TableHandler.d.ts.map +1 -1
  113. package/lib/DboBuilder/TableHandler.js +18 -20
  114. package/lib/DboBuilder/TableHandler.ts +21 -20
  115. package/lib/DboBuilder/ViewHandler.d.ts +1 -11
  116. package/lib/DboBuilder/ViewHandler.d.ts.map +1 -1
  117. package/lib/DboBuilder/ViewHandler.js +50 -63
  118. package/lib/DboBuilder/ViewHandler.ts +68 -97
  119. package/lib/DboBuilder/delete.js +0 -1
  120. package/lib/DboBuilder/delete.ts +1 -1
  121. package/lib/DboBuilder/getColumns.js +5 -4
  122. package/lib/DboBuilder/getColumns.ts +5 -5
  123. package/lib/DboBuilder/getCondition.d.ts.map +1 -1
  124. package/lib/DboBuilder/getCondition.js +6 -6
  125. package/lib/DboBuilder/getCondition.ts +7 -7
  126. package/lib/DboBuilder/insert.js +15 -16
  127. package/lib/DboBuilder/insert.ts +18 -18
  128. package/lib/DboBuilder/insertDataParse.d.ts.map +1 -1
  129. package/lib/DboBuilder/insertDataParse.js +9 -10
  130. package/lib/DboBuilder/insertDataParse.ts +42 -43
  131. package/lib/DboBuilder/parseUpdateRules.js +2 -2
  132. package/lib/DboBuilder/parseUpdateRules.ts +2 -2
  133. package/lib/DboBuilder/runSQL.d.ts.map +1 -1
  134. package/lib/DboBuilder/runSQL.js +5 -5
  135. package/lib/DboBuilder/runSQL.ts +6 -6
  136. package/lib/DboBuilder/subscribe.d.ts +1 -1
  137. package/lib/DboBuilder/subscribe.d.ts.map +1 -1
  138. package/lib/DboBuilder/subscribe.js +3 -3
  139. package/lib/DboBuilder/subscribe.ts +3 -3
  140. package/lib/DboBuilder/update.js +5 -6
  141. package/lib/DboBuilder/update.ts +6 -6
  142. package/lib/DboBuilder/uploadFile.d.ts.map +1 -1
  143. package/lib/DboBuilder/uploadFile.js +1 -1
  144. package/lib/DboBuilder/uploadFile.ts +2 -2
  145. package/lib/DboBuilder.d.ts +22 -22
  146. package/lib/DboBuilder.d.ts.map +1 -1
  147. package/lib/DboBuilder.js +13 -14
  148. package/lib/DboBuilder.ts +19 -19
  149. package/lib/FileManager.d.ts +6 -6
  150. package/lib/FileManager.d.ts.map +1 -1
  151. package/lib/FileManager.js +3 -5
  152. package/lib/FileManager.ts +7 -6
  153. package/lib/Filtering.d.ts +1 -1
  154. package/lib/Filtering.d.ts.map +1 -1
  155. package/lib/Filtering.js +7 -7
  156. package/lib/Filtering.ts +7 -7
  157. package/lib/JSONBValidation/validate_jsonb_schema_sql.d.ts +3 -0
  158. package/lib/JSONBValidation/validate_jsonb_schema_sql.d.ts.map +1 -0
  159. package/lib/JSONBValidation/validate_jsonb_schema_sql.js +294 -0
  160. package/lib/JSONBValidation/validate_jsonb_schema_sql.ts +293 -0
  161. package/lib/JSONBValidation/validation.d.ts +108 -0
  162. package/lib/JSONBValidation/validation.d.ts.map +1 -0
  163. package/lib/JSONBValidation/validation.js +221 -0
  164. package/lib/JSONBValidation/validation.ts +332 -0
  165. package/lib/PostgresNotifListenManager.d.ts +1 -1
  166. package/lib/PostgresNotifListenManager.d.ts.map +1 -1
  167. package/lib/PostgresNotifListenManager.js +1 -1
  168. package/lib/PostgresNotifListenManager.ts +1 -1
  169. package/lib/Prostgles.d.ts +14 -14
  170. package/lib/Prostgles.d.ts.map +1 -1
  171. package/lib/Prostgles.js +20 -19
  172. package/lib/Prostgles.ts +22 -21
  173. package/lib/PubSubManager/PubSubManager.d.ts +7 -7
  174. package/lib/PubSubManager/PubSubManager.d.ts.map +1 -1
  175. package/lib/PubSubManager/PubSubManager.js +10 -8
  176. package/lib/PubSubManager/PubSubManager.ts +13 -8
  177. package/lib/PubSubManager/initPubSubManager.d.ts.map +1 -1
  178. package/lib/PubSubManager/initPubSubManager.js +10 -7
  179. package/lib/PubSubManager/initPubSubManager.ts +12 -7
  180. package/lib/PublishParser.d.ts +32 -32
  181. package/lib/PublishParser.d.ts.map +1 -1
  182. package/lib/PublishParser.js +121 -124
  183. package/lib/PublishParser.ts +125 -127
  184. package/lib/SchemaWatch.d.ts +1 -1
  185. package/lib/SchemaWatch.d.ts.map +1 -1
  186. package/lib/SyncReplication.d.ts +5 -5
  187. package/lib/SyncReplication.d.ts.map +1 -1
  188. package/lib/SyncReplication.js +19 -16
  189. package/lib/SyncReplication.ts +470 -471
  190. package/lib/TableConfig.d.ts +28 -24
  191. package/lib/TableConfig.d.ts.map +1 -1
  192. package/lib/TableConfig.js +33 -12
  193. package/lib/TableConfig.ts +55 -21
  194. package/lib/index.js +1 -1
  195. package/lib/index.ts +1 -1
  196. package/lib/shortestPath.d.ts +1 -1
  197. package/lib/shortestPath.d.ts.map +1 -1
  198. package/lib/shortestPath.js +11 -11
  199. package/lib/shortestPath.ts +11 -11
  200. package/package.json +10 -6
  201. package/tests/client/PID.txt +1 -1
  202. package/tests/client/package-lock.json +53 -31
  203. package/tests/client/package.json +4 -1
  204. package/tests/isomorphic_queries.d.ts.map +1 -1
  205. package/tests/isomorphic_queries.js +28 -26
  206. package/tests/isomorphic_queries.ts +30 -29
  207. package/tests/server/DBoGenerated.d.ts +1 -1
  208. package/tests/server/index.js +8 -7
  209. package/tests/server/index.ts +10 -8
  210. package/tests/server/package-lock.json +76 -58
  211. package/tests/server/package.json +2 -2
  212. package/tests/server/server.ts +2 -3
  213. package/lib/validation.d.ts +0 -100
  214. package/lib/validation.d.ts.map +0 -1
  215. package/lib/validation.js +0 -280
  216. package/lib/validation.ts +0 -360
@@ -4,519 +4,518 @@ import { OrderBy, WAL, AnyObject, SyncBatchParams } from "prostgles-types";
4
4
  import { TableHandler } from "./DboBuilder/TableHandler";
5
5
 
6
6
  export type ClientSyncInfo = Partial<{
7
- c_fr: AnyObject;
8
- c_lr: AnyObject;
9
- /**
10
- * PG count is ussually string due to bigint
11
- */
12
- c_count: number | string;
7
+ c_fr: AnyObject;
8
+ c_lr: AnyObject;
9
+ /**
10
+ * PG count is ussually string due to bigint
11
+ */
12
+ c_count: number | string;
13
13
  }>;
14
14
 
15
15
  export type ServerSyncInfo = Partial<{
16
- s_fr: AnyObject;
17
- s_lr: AnyObject;
18
- /**
19
- * PG count is ussually string due to bigint
20
- */
21
- s_count: number | string;
16
+ s_fr: AnyObject;
17
+ s_lr: AnyObject;
18
+ /**
19
+ * PG count is ussually string due to bigint
20
+ */
21
+ s_count: number | string;
22
22
  }>
23
23
 
24
24
  export type SyncBatchInfo = Partial<{
25
- from_synced: number | null;
26
- to_synced: number | null;
27
- end_offset: number | null;
25
+ from_synced: number | null;
26
+ to_synced: number | null;
27
+ end_offset: number | null;
28
28
  }>;
29
29
 
30
30
  export type onSyncRequestResponse = {
31
- onSyncRequest?: ClientSyncInfo
31
+ onSyncRequest?: ClientSyncInfo
32
32
  } | {
33
- err: AnyObject | string;
33
+ err: AnyObject | string;
34
34
  };
35
35
 
36
36
  export type ClientExpressData = ClientSyncInfo & {
37
- data?: AnyObject[];
38
- deleted?: AnyObject[];
37
+ data?: AnyObject[];
38
+ deleted?: AnyObject[];
39
39
  }
40
40
 
41
41
  function getNumbers(numberArr: (null | undefined | string | number)[]): number[] {
42
- return numberArr.filter(v => v !== null && v !== undefined && Number.isFinite(+v)) as any;
42
+ return numberArr.filter(v => v !== null && v !== undefined && Number.isFinite(+v)) as any;
43
43
  }
44
44
 
45
45
  export const syncData = async (_this: PubSubManager, sync: SyncParams, clientData: ClientExpressData | undefined, source: "trigger" | "client") => {
46
- log("syncData", { clientData, sync: pickKeys(sync, ["filter", "last_synced", "lr", "is_syncing"]) , source })
47
- const {
48
- socket_id, channel_name, table_name, filter,
49
- table_rules, allow_delete = false, params,
50
- synced_field, id_fields = [], batch_size,
51
- wal, throttle = 0
52
- } = sync,
53
- socket = _this.sockets[socket_id];
54
-
55
- if(!socket) {
56
- console.error("Orphaned socket", { sync, clientData });
57
- return;
58
- }
46
+ log("syncData", { clientData, sync: pickKeys(sync, ["filter", "last_synced", "lr", "is_syncing"]), source })
47
+ const {
48
+ socket_id, channel_name, table_name, filter,
49
+ table_rules, allow_delete = false, params,
50
+ synced_field, id_fields = [], batch_size,
51
+ wal, throttle = 0
52
+ } = sync,
53
+ socket = _this.sockets[socket_id];
54
+
55
+ if (!socket) {
56
+ console.error("Orphaned socket", { sync, clientData });
57
+ return;
58
+ }
59
+
60
+ const sync_fields = [synced_field, ...id_fields.sort()],
61
+ orderByAsc = sync_fields.reduce((a, v) => ({ ...a, [v]: true }), {}),
62
+ // orderByDesc = sync_fields.reduce((a, v) => ({ ...a, [v]: false }), {}),
63
+ // desc_params = { orderBy: [{ [synced_field]: false }].concat(id_fields.map(f => ({ [f]: false }) )) },
64
+ // asc_params = { orderBy: [synced_field].concat(id_fields) },
65
+ rowsIdsMatch = (a?: AnyObject, b?: AnyObject) => {
66
+ return a && b && !id_fields.find(key => (a[key]).toString() !== (b[key]).toString())
67
+ },
68
+ rowsFullyMatch = (a?: AnyObject, b?: AnyObject) => {
69
+ return rowsIdsMatch(a, b) && a?.[synced_field].toString() === b?.[synced_field].toString();
70
+ },
71
+ getServerRowInfo = async (args: SyncBatchParams = {}): Promise<ServerSyncInfo> => {
72
+ const { from_synced = null, to_synced = null, offset = 0, limit } = args;
73
+ const _filter: AnyObject = { ...filter };
74
+
75
+ if (from_synced || to_synced) {
76
+ _filter[synced_field] = {
77
+ ...(from_synced ? { $gte: from_synced } : {}),
78
+ ...(to_synced ? { $lte: to_synced } : {})
79
+ }
80
+ }
81
+ if (_this.dbo?.[table_name]?.find === undefined || _this?.dbo?.[table_name]?.count === undefined) {
82
+ throw `dbo.${table_name}.find or .count are missing or not allowed`;
83
+ }
84
+
85
+ const first_rows = await _this.dbo?.[table_name]?.find?.(_filter, { orderBy: (orderByAsc as OrderBy), select: sync_fields, limit, offset }, undefined, table_rules);
86
+ const last_rows = first_rows?.slice(-1);
87
+ // const last_rows = await _this?.dbo[table_name]?.find?.(_filter, { orderBy: (orderByDesc as OrderBy), select: sync_fields, limit: 1, offset: -offset || 0 }, null, table_rules);
88
+ const count = await _this.dbo?.[table_name]?.count?.(_filter, undefined, undefined, table_rules);
89
+
90
+ return { s_fr: first_rows?.[0] || null, s_lr: last_rows?.[0] || null, s_count: count }
91
+ },
92
+ getClientRowInfo = (args: SyncBatchInfo = {}) => {
93
+ const { from_synced = null, to_synced = null, end_offset = null } = args;
94
+ const res = new Promise<any>((resolve, reject) => {
95
+ const onSyncRequest = { from_synced, to_synced, end_offset };//, forReal: true };
96
+ socket.emit(channel_name, { onSyncRequest }, (resp?: onSyncRequestResponse) => {
97
+ if (resp && "onSyncRequest" in resp && resp?.onSyncRequest) {
98
+ const c_fr = resp.onSyncRequest.c_fr,
99
+ c_lr = resp.onSyncRequest.c_lr,
100
+ c_count = resp.onSyncRequest.c_count;
101
+
102
+ // console.log(onSyncRequest, { c_fr, c_lr, c_count }, socket._user);
103
+ return resolve({ c_fr, c_lr, c_count });
104
+ } else if (resp && "err" in resp && resp?.err) {
105
+ reject(resp.err);
106
+ }
107
+ });
108
+ });
109
+
110
+ return res;
111
+ },
112
+ getClientData = (from_synced = 0, offset = 0): Promise<AnyObject[]> => {
113
+ return new Promise((resolve, reject) => {
114
+ const onPullRequest = { from_synced: from_synced || 0, offset: offset || 0, limit: batch_size };
115
+ socket.emit(channel_name, { onPullRequest }, async (resp?: { data?: AnyObject[] }) => {
116
+ if (resp && resp.data && Array.isArray(resp.data)) {
117
+ // console.log({ onPullRequest, resp }, socket._user)
118
+ resolve(sortClientData(resp.data));
119
+ } else {
120
+ reject("unexpected onPullRequest response: " + JSON.stringify(resp));
121
+ }
122
+ });
123
+ });
124
+
125
+ function sortClientData(data: AnyObject[]) {
126
+ return data.sort((a, b) => {
127
+ /* Order by increasing synced and ids (sorted alphabetically) */
128
+ return (+a[synced_field] - +b[synced_field]) || id_fields.sort().map(idKey => a[idKey] < b[idKey] ? -1 : a[idKey] > b[idKey] ? 1 : 0).find(v => v) || 0;
129
+ });
130
+ }
131
+ },
132
+ getServerData = async (from_synced = 0, offset = 0): Promise<AnyObject[]> => {
133
+ const _filter = {
134
+ ...filter,
135
+ [synced_field]: { $gte: from_synced || 0 }
136
+ };
137
+
138
+ if (!_this?.dbo?.[table_name]?.find) throw "_this?.dbo?.[table_name]?.find is missing";
139
+
140
+ try {
141
+ const res = _this?.dbo?.[table_name]?.find?.(
142
+ _filter,
143
+ {
144
+ select: params.select,
145
+ orderBy: (orderByAsc as OrderBy),
146
+ offset: offset || 0,
147
+ limit: batch_size
148
+ },
149
+ undefined,
150
+ table_rules
151
+ );
152
+
153
+ if (!res) throw "_this?.dbo?.[table_name]?.find is missing";
154
+
155
+ return res;
156
+ } catch (e) {
157
+ console.error("Sync getServerData failed: ", e);
158
+ throw "INTERNAL ERROR"
159
+ }
160
+ },
161
+ deleteData = async (deleted: AnyObject[]) => {
162
+ // console.log("deleteData deleteData deleteData " + deleted.length);
163
+ if (allow_delete) {
164
+ return Promise.all(deleted.map(async d => {
165
+ const id_filter = pickKeys(d, id_fields);
166
+ try {
167
+ await (_this.dbo[table_name] as TableHandler).delete(id_filter, undefined, undefined, table_rules);
168
+ return 1;
169
+ } catch (e) {
170
+ console.error(e)
171
+ }
172
+ return 0;
173
+ }))
174
+ } else {
175
+ console.warn("client tried to delete data without permission (allow_delete is false)")
176
+ }
177
+ return false;
178
+ },
59
179
 
60
- const sync_fields = [synced_field, ...id_fields.sort()],
61
- orderByAsc = sync_fields.reduce((a, v) => ({ ...a, [v]: true }), {}),
62
- orderByDesc = sync_fields.reduce((a, v) => ({ ...a, [v]: false }), {}),
63
- // desc_params = { orderBy: [{ [synced_field]: false }].concat(id_fields.map(f => ({ [f]: false }) )) },
64
- // asc_params = { orderBy: [synced_field].concat(id_fields) },
65
- rowsIdsMatch = (a?: AnyObject, b?: AnyObject) => {
66
- return a && b && !id_fields.find(key => (a[key]).toString() !== (b[key]).toString())
67
- },
68
- rowsFullyMatch = (a?: AnyObject, b?: AnyObject) => {
69
- return rowsIdsMatch(a, b) && a?.[synced_field].toString() === b?.[synced_field].toString();
70
- },
71
- getServerRowInfo = async (args: SyncBatchParams = {}): Promise<ServerSyncInfo> => {
72
- const { from_synced = null, to_synced = null, offset = 0, limit } = args;
73
- let _filter: AnyObject = { ...filter };
74
-
75
- if(from_synced || to_synced){
76
- _filter[synced_field] = {
77
- ...(from_synced? { $gte: from_synced } : {}),
78
- ...(to_synced? { $lte: to_synced } : {})
79
- }
80
- }
81
- if(_this.dbo?.[table_name]?.find === undefined || _this?.dbo?.[table_name]?.count === undefined) {
82
- throw `dbo.${table_name}.find or .count are missing or not allowed`;
83
- }
84
-
85
- const first_rows = await _this.dbo?.[table_name]?.find?.(_filter, { orderBy: (orderByAsc as OrderBy), select: sync_fields, limit, offset }, undefined, table_rules);
86
- const last_rows = first_rows?.slice(-1);
87
- // const last_rows = await _this?.dbo[table_name]?.find?.(_filter, { orderBy: (orderByDesc as OrderBy), select: sync_fields, limit: 1, offset: -offset || 0 }, null, table_rules);
88
- const count = await _this.dbo?.[table_name]?.count?.(_filter, undefined, undefined, table_rules);
89
-
90
- return { s_fr: first_rows?.[0] || null, s_lr: last_rows?.[0] || null, s_count: count }
91
- },
92
- getClientRowInfo = (args: SyncBatchInfo = {}) => {
93
- const { from_synced = null, to_synced = null, end_offset = null } = args;
94
- let res = new Promise<any>((resolve, reject) => {
95
- let onSyncRequest = { from_synced, to_synced, end_offset };//, forReal: true };
96
- socket.emit(channel_name, { onSyncRequest }, (resp?: onSyncRequestResponse) => {
97
- if(resp && "onSyncRequest" in resp && resp?.onSyncRequest){
98
- let c_fr = resp.onSyncRequest.c_fr,
99
- c_lr = resp.onSyncRequest.c_lr,
100
- c_count = resp.onSyncRequest.c_count;
101
-
102
- // console.log(onSyncRequest, { c_fr, c_lr, c_count }, socket._user);
103
- return resolve({ c_fr, c_lr, c_count });
104
- } else if(resp && "err" in resp && resp?.err){
105
- reject(resp.err);
106
- }
107
- });
108
- });
180
+ /**
181
+ * Upserts the given client data where synced_field is higher than on server
182
+ */
183
+ upsertData = (data: AnyObject[], isExpress = false) => {
184
+
185
+ // console.log("isExpress", isExpress, data);
186
+
187
+ return _this.dboBuilder.getTX(async (dbTX) => {
188
+ const tbl = dbTX[table_name] as TableHandler;
189
+ const existingData = await tbl.find(
190
+ { $or: data.map(d => pickKeys(d, id_fields)) },
191
+ {
192
+ select: [synced_field, ...id_fields],
193
+ orderBy: (orderByAsc as OrderBy),
194
+ },
195
+ undefined,
196
+ table_rules
197
+ );
198
+ let inserts = data.filter(d => !existingData.find(ed => rowsIdsMatch(ed, d)));
199
+ let updates = data.filter(d => existingData.find(ed => rowsIdsMatch(ed, d) && +ed[synced_field] < +d[synced_field]));
200
+ try {
201
+ if (!table_rules) throw "table_rules missing"
202
+ if (table_rules.update && updates.length) {
203
+ const updateData: [any, any][] = [];
204
+ await Promise.all(updates.map(upd => {
205
+ const id_filter = pickKeys(upd, id_fields);
206
+ const syncSafeFilter = { $and: [id_filter, { [synced_field]: { "<": upd[synced_field] } }] }
207
+
208
+ updateData.push([syncSafeFilter, omitKeys(upd, id_fields)])
209
+ }));
210
+ await tbl.updateBatch(updateData, { fixIssues: true }, table_rules);
211
+ } else {
212
+ updates = [];
213
+ }
214
+
215
+ if (table_rules.insert && inserts.length) {
216
+ // const qs = await tbl.insert(inserts, { fixIssues: true }, null, table_rules, { returnQuery: true });
217
+ // console.log("inserts", qs)
218
+ await tbl.insert(inserts, { fixIssues: true }, undefined, table_rules);
219
+ } else {
220
+ inserts = [];
221
+ }
222
+
223
+ return { inserts, updates };
224
+ } catch (e) {
225
+ console.trace(e);
226
+ throw e;
227
+ }
109
228
 
110
- return res;
111
- },
112
- getClientData = (from_synced = 0, offset = 0): Promise<AnyObject[]> => {
113
- return new Promise((resolve, reject) => {
114
- const onPullRequest = { from_synced: from_synced || 0, offset: offset || 0, limit: batch_size };
115
- socket.emit(channel_name, { onPullRequest } , async (resp?: { data?: AnyObject[] }) => {
116
- if(resp && resp.data && Array.isArray(resp.data)){
117
- // console.log({ onPullRequest, resp }, socket._user)
118
- resolve(sortClientData(resp.data));
119
- } else {
120
- reject("unexpected onPullRequest response: " + JSON.stringify(resp));
121
- }
122
- });
123
- });
229
+ }).then(({ inserts, updates }) => {
230
+ log(`upsertData: inserted( ${inserts.length} ) updated( ${updates.length} ) total( ${data.length} ) \n last insert ${JSON.stringify(inserts.at(-1))} \n last update ${JSON.stringify(updates.at(-1))}`);
231
+ return { inserted: inserts.length, updated: updates.length, total: data.length };
232
+ })
233
+ .catch(err => {
234
+ console.trace("Something went wrong with syncing to server: \n ->", err, data.length, id_fields);
235
+ return Promise.reject("Something went wrong with syncing to server: ")
236
+ });
237
+ },
124
238
 
125
- function sortClientData(data: AnyObject[]){
126
- return data.sort((a, b) => {
127
- /* Order by increasing synced and ids (sorted alphabetically) */
128
- return (+a[synced_field] - +b[synced_field]) || id_fields.sort().map(idKey => a[idKey] < b[idKey]? -1 : a[idKey] > b[idKey]? 1 : 0).find(v => v) || 0;
129
- });
130
- }
131
- },
132
- getServerData = async (from_synced = 0, offset = 0): Promise<AnyObject[]> => {
133
- let _filter = {
134
- ...filter,
135
- [synced_field]: { $gte: from_synced || 0 }
136
- };
137
-
138
- if(!_this?.dbo?.[table_name]?.find) throw "_this?.dbo?.[table_name]?.find is missing";
139
-
140
- try {
141
- let res = _this?.dbo?.[table_name]?.find?.(
142
- _filter,
143
- {
144
- select: params.select,
145
- orderBy: (orderByAsc as OrderBy),
146
- offset: offset || 0,
147
- limit: batch_size
148
- },
149
- undefined,
150
- table_rules
151
- );
152
-
153
- if(!res) throw "_this?.dbo?.[table_name]?.find is missing";
154
-
155
- return res;
156
- } catch(e){
157
- console.error("Sync getServerData failed: ", e);
158
- throw "INTERNAL ERROR"
159
- }
160
- },
161
- deleteData = async (deleted: AnyObject[]) => {
162
- // console.log("deleteData deleteData deleteData " + deleted.length);
163
- if(allow_delete){
164
- return Promise.all(deleted.map(async d => {
165
- const id_filter = pickKeys(d, id_fields);
166
- try {
167
- await (_this.dbo[table_name] as TableHandler).delete(id_filter, undefined, undefined, table_rules);
168
- return 1;
169
- } catch (e){
170
- console.error(e)
171
- }
172
- return 0;
173
- }))
174
- } else {
175
- console.warn("client tried to delete data without permission (allow_delete is false)")
176
- }
177
- return false;
178
- },
239
+ /**
240
+ * Pushes the given data to client
241
+ * @param isSynced = true if
242
+ */
243
+ pushData = async (data?: AnyObject[], isSynced = false, err: any = null) => {
244
+ return new Promise((resolve, reject) => {
245
+ socket.emit(channel_name, { data, isSynced }, (resp?: { ok: boolean }) => {
246
+
247
+ if (resp && resp.ok) {
248
+ // console.log("PUSHED to client: fr/lr", data[0], data[data.length - 1]);
249
+ resolve({ pushed: data?.length, resp })
250
+
251
+ } else {
252
+ reject(resp);
253
+ console.error("Unexpected response");
254
+ }
255
+ });
256
+ });
257
+ },
179
258
 
180
- /**
181
- * Upserts the given client data where synced_field is higher than on server
182
- */
183
- upsertData = (data: AnyObject[], isExpress = false) => {
184
-
185
- // console.log("isExpress", isExpress, data);
186
-
187
- return _this.dboBuilder.getTX(async (dbTX) => {
188
- const tbl = dbTX[table_name] as TableHandler;
189
- const existingData = await tbl.find(
190
- { $or: data.map(d => pickKeys(d, id_fields)) },
191
- {
192
- select: [synced_field, ...id_fields] ,
193
- orderBy: (orderByAsc as OrderBy),
194
- },
195
- undefined,
196
- table_rules
197
- );
198
- let inserts = data.filter(d => !existingData.find(ed => rowsIdsMatch(ed, d)));
199
- let updates = data.filter(d => existingData.find(ed => rowsIdsMatch(ed, d) && +ed[synced_field] < +d[synced_field]) );
200
- try {
201
- if(!table_rules) throw "table_rules missing"
202
- if(table_rules.update && updates.length){
203
- let updateData: [any, any][] = [];
204
- await Promise.all(updates.map(upd => {
205
- const id_filter = pickKeys(upd, id_fields);
206
- const syncSafeFilter = { $and: [id_filter, { [synced_field]: { "<": upd[synced_field] } } ] }
207
-
208
- updateData.push([syncSafeFilter, omitKeys(upd, id_fields)])
209
- }));
210
- await tbl.updateBatch(updateData, { fixIssues: true }, table_rules);
211
- } else {
212
- updates = [];
213
- }
214
-
215
- if(table_rules.insert && inserts.length){
216
- // const qs = await tbl.insert(inserts, { fixIssues: true }, null, table_rules, { returnQuery: true });
217
- // console.log("inserts", qs)
218
- await tbl.insert(inserts, { fixIssues: true }, undefined, table_rules);
219
- } else {
220
- inserts = [];
221
- }
222
-
223
- return { inserts, updates };
224
- } catch(e) {
225
- console.trace(e);
226
- throw e;
227
- }
228
-
229
- }).then(({ inserts, updates }) => {
230
- log(`upsertData: inserted( ${inserts.length} ) updated( ${updates.length} ) total( ${data.length} ) \n last insert ${JSON.stringify(inserts.at(-1))} \n last update ${JSON.stringify(updates.at(-1))}`);
231
- return { inserted: inserts.length , updated: updates.length , total: data.length };
232
- })
233
- .catch(err => {
234
- console.trace("Something went wrong with syncing to server: \n ->", err, data.length, id_fields);
235
- return Promise.reject("Something went wrong with syncing to server: ")
236
- });
237
- },
259
+ /**
260
+ * Returns the lowest synced_field between server and client by checking client and server sync data.
261
+ * If last rows don't match it will find an earlier matching last row and use that last matching from_synced
262
+ * If no rows or fully synced (c_lr and s_lr match) then returns null
263
+ */
264
+ getLastSynced = async (clientSyncInfo?: ClientSyncInfo): Promise<number | null> => {
238
265
 
239
- /**
240
- * Pushes the given data to client
241
- * @param isSynced = true if
242
- */
243
- pushData = async (data?: AnyObject[], isSynced = false, err: any = null) => {
244
- return new Promise((resolve, reject) => {
245
- socket.emit(channel_name, { data, isSynced }, (resp?: { ok: boolean }) => {
246
-
247
- if(resp && resp.ok){
248
- // console.log("PUSHED to client: fr/lr", data[0], data[data.length - 1]);
249
- resolve({ pushed: data?.length, resp })
250
-
251
- } else {
252
- reject(resp);
253
- console.error("Unexpected response");
254
- }
255
- });
256
- });
257
- },
266
+ // Get latest row info
267
+ const { c_fr, c_lr, c_count } = clientSyncInfo || await getClientRowInfo();
268
+ const { s_fr, s_lr, s_count } = await getServerRowInfo();
258
269
 
259
- /**
260
- * Returns the lowest synced_field between server and client by checking client and server sync data.
261
- * If last rows don't match it will find an earlier matching last row and use that last matching from_synced
262
- * If no rows or fully synced (c_lr and s_lr match) then returns null
263
- */
264
- getLastSynced = async (clientSyncInfo?: ClientSyncInfo): Promise<number | null> => {
265
-
266
- // Get latest row info
267
- const { c_fr, c_lr, c_count } = clientSyncInfo || await getClientRowInfo();
268
- const { s_fr, s_lr, s_count } = await getServerRowInfo();
269
-
270
- // console.log("getLastSynced", clientData, socket._user )
271
-
272
- let result = null;
273
-
274
- /* Nothing to sync */
275
- if( !c_fr && !s_fr || rowsFullyMatch(c_lr, s_lr)){ // c_count === s_count &&
276
- // sync.last_synced = null;
277
- result = null;
278
-
279
- /* Sync Everything */
280
- } else if(!rowsFullyMatch(c_fr, s_fr)) {
281
- if(c_fr && s_fr){
282
- result = Math.min(c_fr[synced_field], s_fr[synced_field]);
283
-
284
- } else if(c_fr || s_fr) {
285
- result = (c_fr || s_fr)[synced_field];
286
- }
287
-
288
- /* Sync from last matching synced value */
289
- } else if (rowsFullyMatch(c_fr, s_fr)){
290
-
291
- if(s_lr && c_lr){
292
- result = Math.min(...getNumbers([c_lr[synced_field], s_lr[synced_field]]));
293
- } else {
294
- result = Math.min(...getNumbers([c_fr[synced_field], s_fr?.[synced_field]]));
295
- }
296
-
297
- let min_count = Math.min(...getNumbers([c_count, s_count]));
298
- let end_offset = 1;// Math.min(s_count, c_count) - 1;
299
- let step = 0;
300
-
301
- while(min_count > 5 && end_offset < min_count){
302
- const { c_lr = null } = await getClientRowInfo({ from_synced: 0, to_synced: result, end_offset });
303
- // console.log("getLastSynced... end_offset > " + end_offset);
304
- let server_row;
305
-
306
- if(c_lr){
307
- let _filter: AnyObject = {};
308
- sync_fields.map(key => {
309
- _filter[key] = c_lr[key];
310
- });
311
- server_row = await _this?.dbo?.[table_name]?.find?.(_filter, { select: sync_fields, limit: 1 }, undefined, table_rules);
312
- }
313
-
314
- // if(rowsFullyMatch(c_lr, s_lr)){ //c_count === s_count &&
315
- if(server_row && server_row.length){
316
- server_row = server_row[0];
317
-
318
- result = +server_row[synced_field];
319
- end_offset = min_count;
320
- // console.log(`getLastSynced found for ${table_name} -> ${result}`);
321
- } else {
322
- end_offset += 1 + step * (step > 4? 2 : 1);
323
- // console.log(`getLastSynced NOT found for ${table_name} -> ${result}`);
324
- }
325
-
326
- step++;
327
- }
328
- }
329
-
330
- return result;
331
- },
332
-
333
- updateSyncLR = (data: AnyObject) => {
334
- if(data.length){
335
- const lastRow = data[data.length - 1];
336
- if(sync.lr?.[synced_field] && +sync.lr?.[synced_field] > +lastRow[synced_field]){
337
- console.error({ syncIssue: "sync.lr[synced_field] is greater than lastRow[synced_field]"})
338
- }
339
- sync.lr = lastRow;
340
- sync.last_synced = +sync.lr?.[synced_field];
341
- }
342
- },
270
+ // console.log("getLastSynced", clientData, socket._user )
343
271
 
344
- /**
345
- * Will push pull sync between client and server from a given from_synced value
346
- */
347
- syncBatch = async (from_synced: SyncBatchInfo["from_synced"]) => {
348
- let offset = 0,
349
- limit = batch_size,
350
- canContinue = true,
351
- min_synced = from_synced || 0,
352
- max_synced = from_synced;
353
-
354
- let inserted = 0, updated = 0, pushed = 0, deleted = 0, total = 0;
355
-
356
- // console.log("syncBatch", from_synced)
357
-
358
- while (canContinue){
359
- let cData = await getClientData(min_synced, offset);
360
-
361
-
362
- if(cData.length){
363
- let res = await upsertData(cData);
364
- inserted += res.inserted;
365
- updated += res.updated;
366
- }
367
- let sData: AnyObject[] | undefined;
368
-
369
- try {
370
- sData = await getServerData(min_synced, offset);
371
- } catch(e){
372
- console.trace("sync getServerData err", e);
373
- await pushData(undefined, undefined, "Internal error. Check server logs");
374
- throw " d"
375
- }
376
-
377
- // console.log("allow_delete", table_rules.delete);
378
- if(allow_delete && table_rules?.delete){
379
- const to_delete = sData.filter(d => {
380
- !cData.find(c => rowsIdsMatch(c, d) )
381
- });
382
- await Promise.all(to_delete.map(d => {
383
- deleted++;
384
- return (_this.dbo[table_name] as TableHandler).delete(pickKeys(d, id_fields), { }, undefined, table_rules);
385
- }));
386
- sData = await getServerData(min_synced, offset);
387
- }
388
-
389
- let forClient = sData.filter(s => {
390
- return !cData.find(c =>
391
- rowsIdsMatch(c, s) &&
392
- +c[synced_field] >= +s[synced_field]
393
- );
394
- });
395
- if(forClient.length){
396
- let res: any = await pushData(forClient.filter(d => !sync.wal || !sync.wal.isInHistory(d)));
397
- pushed += res.pushed;
398
- }
399
-
400
- if(sData.length){
401
- updateSyncLR(sData);
402
- total += sData.length;
403
- }
404
- offset += sData.length;
405
-
406
- // canContinue = offset >= limit;
407
- canContinue = sData.length >= limit;
408
- // console.log(`sData ${sData.length} limit ${limit}`);
409
- }
410
-
411
- // console.log(`syncBatch ${table_name}: inserted( ${inserted} ) updated( ${updated} ) deleted( ${deleted} ) pushed( ${pushed} ) total( ${total} )`, socket._user );
412
-
413
- return true;
414
- };
415
-
416
- if(!wal){
417
- /* Used to throttle and merge incomming updates */
418
- sync.wal = new WAL({
419
- id_fields, synced_field, throttle, batch_size,
420
- DEBUG_MODE: _this.dboBuilder.prostgles.opts.DEBUG_MODE,
421
- onSendStart: () => {
422
- sync.is_syncing = true;
423
- },
424
- onSend: async (data) => {
425
- // console.log("WAL upsertData START", data)
426
- const res = await upsertData(data, true);
427
- // const max_incoming_synced = Math.max(...data.map(d => +d[synced_field]));
428
- // if(Number.isFinite(max_incoming_synced) && max_incoming_synced > +sync.last_synced){
429
- // sync.last_synced = max_incoming_synced;
430
- // }
431
- // console.log("WAL upsertData END")
432
-
433
- /******** */
434
- /* TO DO -> Store and push patch updates instead of full data if and where possible */
435
- /******** */
436
- // 1. Store successfully upserted wal items for a couple of seconds
437
- // 2. When pushing data to clients check if any matching wal items exist
438
- // 3. Replace text fields with matching patched data
439
-
440
- return res;
441
- },
442
- onSendEnd: (batch) => {
443
- updateSyncLR(batch);
444
- sync.is_syncing = false;
445
- // console.log("syncData from WAL.onSendEnd")
446
-
447
- /**
448
- * After all data was inserted request SyncInfo from client and sync again if necessary
449
- */
450
- _this.syncData(sync, undefined, source);
451
- },
452
- })
453
- }
272
+ let result = null;
273
+
274
+ /* Nothing to sync */
275
+ if (!c_fr && !s_fr || rowsFullyMatch(c_lr, s_lr)) { // c_count === s_count &&
276
+ // sync.last_synced = null;
277
+ result = null;
454
278
 
455
- /* Debounce sync requests */
456
- if(!sync.wal) throw "sync.wal missing";
457
- if(!sync.wal.isSending() && sync.is_syncing) {
458
- if(!_this.syncTimeout){
459
- _this.syncTimeout = setTimeout(() => {
460
- _this.syncTimeout = undefined;
461
- // console.log("SYNC FROM TIMEOUT")
462
- _this.syncData(sync, undefined, source);
463
- }, throttle)
279
+ /* Sync Everything */
280
+ } else if (!rowsFullyMatch(c_fr, s_fr)) {
281
+ if (c_fr && s_fr) {
282
+ result = Math.min(c_fr[synced_field], s_fr[synced_field]);
283
+
284
+ } else if (c_fr || s_fr) {
285
+ result = (c_fr || s_fr)[synced_field];
464
286
  }
465
- // console.log("SYNC THROTTLE")
466
- return;
467
- }
468
287
 
469
- // console.log("syncData", clientData)
288
+ /* Sync from last matching synced value */
289
+ } else if (rowsFullyMatch(c_fr, s_fr)) {
290
+
291
+ if (s_lr && c_lr) {
292
+ result = Math.min(...getNumbers([c_lr[synced_field], s_lr[synced_field]]));
293
+ } else {
294
+ result = Math.min(...getNumbers([c_fr[synced_field], s_fr?.[synced_field]]));
295
+ }
296
+
297
+ const min_count = Math.min(...getNumbers([c_count, s_count]));
298
+ let end_offset = 1;// Math.min(s_count, c_count) - 1;
299
+ let step = 0;
300
+
301
+ while (min_count > 5 && end_offset < min_count) {
302
+ const { c_lr = null } = await getClientRowInfo({ from_synced: 0, to_synced: result, end_offset });
303
+ // console.log("getLastSynced... end_offset > " + end_offset);
304
+ let server_row;
305
+
306
+ if (c_lr) {
307
+ const _filter: AnyObject = {};
308
+ sync_fields.map(key => {
309
+ _filter[key] = c_lr[key];
310
+ });
311
+ server_row = await _this?.dbo?.[table_name]?.find?.(_filter, { select: sync_fields, limit: 1 }, undefined, table_rules);
312
+ }
313
+
314
+ // if(rowsFullyMatch(c_lr, s_lr)){ //c_count === s_count &&
315
+ if (server_row && server_row.length) {
316
+ server_row = server_row[0];
317
+
318
+ result = +server_row[synced_field];
319
+ end_offset = min_count;
320
+ // console.log(`getLastSynced found for ${table_name} -> ${result}`);
321
+ } else {
322
+ end_offset += 1 + step * (step > 4 ? 2 : 1);
323
+ // console.log(`getLastSynced NOT found for ${table_name} -> ${result}`);
324
+ }
325
+
326
+ step++;
327
+ }
328
+ }
329
+
330
+ return result;
331
+ },
332
+
333
+ updateSyncLR = (data: AnyObject) => {
334
+ if (data.length) {
335
+ const lastRow = data[data.length - 1];
336
+ if (sync.lr?.[synced_field] && +sync.lr?.[synced_field] > +lastRow[synced_field]) {
337
+ console.error({ syncIssue: "sync.lr[synced_field] is greater than lastRow[synced_field]" })
338
+ }
339
+ sync.lr = lastRow;
340
+ sync.last_synced = +sync.lr?.[synced_field];
341
+ }
342
+ },
470
343
 
471
344
  /**
472
- * Express data sent from a client that has already been synced
473
- * Add to WAL manager which will sync at the end
345
+ * Will push pull sync between client and server from a given from_synced value
474
346
  */
475
- if(clientData){
476
- if(clientData.data && Array.isArray(clientData.data) && clientData.data.length){
477
- if(!sync.wal) throw "sync.wal missing";
347
+ syncBatch = async (from_synced: SyncBatchInfo["from_synced"]) => {
348
+ let offset = 0,
349
+ canContinue = true;
350
+ const limit = batch_size,
351
+ min_synced = from_synced || 0,
352
+ max_synced = from_synced;
478
353
 
479
- sync.wal.addData(clientData.data.map(d => ({ current: d })));
480
- return;
481
- // await upsertData(clientData.data, true);
354
+ let inserted = 0, updated = 0, pushed = 0, deleted = 0, total = 0;
355
+
356
+ // console.log("syncBatch", from_synced)
357
+
358
+ while (canContinue) {
359
+ const cData = await getClientData(min_synced, offset);
482
360
 
483
- /* Not expecting this anymore. use normal db.table.delete channel */
484
- } else if(clientData.deleted && Array.isArray(clientData.deleted) && clientData.deleted.length){
485
- await deleteData(clientData.deleted);
486
- }
487
- } else {
488
-
489
- }
490
- if(sync.wal.isSending()) return;
491
361
 
492
- sync.is_syncing = true;
362
+ if (cData.length) {
363
+ const res = await upsertData(cData);
364
+ inserted += res.inserted;
365
+ updated += res.updated;
366
+ }
367
+ let sData: AnyObject[] | undefined;
368
+
369
+ try {
370
+ sData = await getServerData(min_synced, offset);
371
+ } catch (e) {
372
+ console.trace("sync getServerData err", e);
373
+ await pushData(undefined, undefined, "Internal error. Check server logs");
374
+ throw " d"
375
+ }
493
376
 
494
- // from synced does not make sense. It should be sync.lr only!!!
495
- let from_synced = null;
377
+ // console.log("allow_delete", table_rules.delete);
378
+ if (allow_delete && table_rules?.delete) {
379
+ const to_delete = sData.filter(d => {
380
+ !cData.find(c => rowsIdsMatch(c, d))
381
+ });
382
+ await Promise.all(to_delete.map(d => {
383
+ deleted++;
384
+ return (_this.dbo[table_name] as TableHandler).delete(pickKeys(d, id_fields), {}, undefined, table_rules);
385
+ }));
386
+ sData = await getServerData(min_synced, offset);
387
+ }
496
388
 
497
- /** Sync was already synced */
498
- if(sync.lr){
499
- const { s_lr } = await getServerRowInfo();
389
+ const forClient = sData.filter(s => {
390
+ return !cData.find(c =>
391
+ rowsIdsMatch(c, s) &&
392
+ +c[synced_field] >= +s[synced_field]
393
+ );
394
+ });
395
+ if (forClient.length) {
396
+ const res: any = await pushData(forClient.filter(d => !sync.wal || !sync.wal.isInHistory(d)));
397
+ pushed += res.pushed;
398
+ }
500
399
 
501
- /* Make sure trigger is not firing on freshly synced data */
502
- if(!rowsFullyMatch(sync.lr, s_lr)){
503
- from_synced = sync.last_synced;
504
- } else {
505
- // console.log("rowsFullyMatch")
400
+ if (sData.length) {
401
+ updateSyncLR(sData);
402
+ total += sData.length;
506
403
  }
507
- // console.log(table_name, sync.lr[synced_field])
508
- } else {
509
- from_synced = await getLastSynced(clientData);
404
+ offset += sData.length;
405
+
406
+ // canContinue = offset >= limit;
407
+ canContinue = sData.length >= limit;
408
+ // console.log(`sData ${sData.length} limit ${limit}`);
409
+ }
410
+ log(`server.syncBatch ${table_name}: inserted( ${inserted} ) updated( ${updated} ) deleted( ${deleted} ) pushed to client( ${pushed} ) total( ${total} )`, socket._user );
411
+
412
+ return true;
413
+ };
414
+
415
+ if (!wal) {
416
+ /* Used to throttle and merge incomming updates */
417
+ sync.wal = new WAL({
418
+ id_fields, synced_field, throttle, batch_size,
419
+ DEBUG_MODE: _this.dboBuilder.prostgles.opts.DEBUG_MODE,
420
+ onSendStart: () => {
421
+ sync.is_syncing = true;
422
+ },
423
+ onSend: async (data) => {
424
+ // console.log("WAL upsertData START", data)
425
+ const res = await upsertData(data, true);
426
+ // const max_incoming_synced = Math.max(...data.map(d => +d[synced_field]));
427
+ // if(Number.isFinite(max_incoming_synced) && max_incoming_synced > +sync.last_synced){
428
+ // sync.last_synced = max_incoming_synced;
429
+ // }
430
+ // console.log("WAL upsertData END")
431
+
432
+ /******** */
433
+ /* TO DO -> Store and push patch updates instead of full data if and where possible */
434
+ /******** */
435
+ // 1. Store successfully upserted wal items for a couple of seconds
436
+ // 2. When pushing data to clients check if any matching wal items exist
437
+ // 3. Replace text fields with matching patched data
438
+
439
+ return res;
440
+ },
441
+ onSendEnd: (batch) => {
442
+ updateSyncLR(batch);
443
+ sync.is_syncing = false;
444
+ // console.log("syncData from WAL.onSendEnd")
445
+
446
+ /**
447
+ * After all data was inserted request SyncInfo from client and sync again if necessary
448
+ */
449
+ _this.syncData(sync, undefined, source);
450
+ },
451
+ })
452
+ }
453
+
454
+ /* Debounce sync requests */
455
+ if (!sync.wal) throw "sync.wal missing";
456
+ if (!sync.wal.isSending() && sync.is_syncing) {
457
+ if (!_this.syncTimeout) {
458
+ _this.syncTimeout = setTimeout(() => {
459
+ _this.syncTimeout = undefined;
460
+ // console.log("SYNC FROM TIMEOUT")
461
+ _this.syncData(sync, undefined, source);
462
+ }, throttle)
463
+ }
464
+ // console.log("SYNC THROTTLE")
465
+ return;
466
+ }
467
+
468
+ // console.log("syncData", clientData)
469
+
470
+ /**
471
+ * Express data sent from a client that has already been synced
472
+ * Add to WAL manager which will sync at the end
473
+ */
474
+ if (clientData) {
475
+ if (clientData.data && Array.isArray(clientData.data) && clientData.data.length) {
476
+ if (!sync.wal) throw "sync.wal missing";
477
+
478
+ sync.wal.addData(clientData.data.map(d => ({ current: d })));
479
+ return;
480
+ // await upsertData(clientData.data, true);
481
+
482
+ /* Not expecting this anymore. use normal db.table.delete channel */
483
+ } else if (clientData.deleted && Array.isArray(clientData.deleted) && clientData.deleted.length) {
484
+ await deleteData(clientData.deleted);
510
485
  }
511
-
512
- if(from_synced !== null){
513
- await syncBatch(from_synced);
486
+ } else {
487
+ // do nothing
488
+ }
489
+ if (sync.wal.isSending()) return;
490
+
491
+ sync.is_syncing = true;
492
+
493
+ // from synced does not make sense. It should be sync.lr only!!!
494
+ let from_synced = null;
495
+
496
+ /** Sync was already synced */
497
+ if (sync.lr) {
498
+ const { s_lr } = await getServerRowInfo();
499
+
500
+ /* Make sure trigger is not firing on freshly synced data */
501
+ if (!rowsFullyMatch(sync.lr, s_lr)) {
502
+ from_synced = sync.last_synced;
514
503
  } else {
515
- // console.log("from_synced is null")
504
+ // console.log("rowsFullyMatch")
516
505
  }
506
+ // console.log(table_name, sync.lr[synced_field])
507
+ } else {
508
+ from_synced = await getLastSynced(clientData);
509
+ }
510
+
511
+ if (from_synced !== null) {
512
+ await syncBatch(from_synced);
513
+ } else {
514
+ // console.log("from_synced is null")
515
+ }
517
516
 
518
- await pushData([], true);
517
+ await pushData([], true);
519
518
 
520
- sync.is_syncing = false;
521
- // console.log(`Finished sync for ${table_name}`, socket._user);
519
+ sync.is_syncing = false;
520
+ // console.log(`Finished sync for ${table_name}`, socket._user);
522
521
  }