prostgles-server 4.2.544 → 4.2.546

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 (55) hide show
  1. package/dist/DboBuilder/DboBuilder.js.map +1 -1
  2. package/dist/DboBuilder/QueryBuilder/Functions/Functions.d.ts.map +1 -1
  3. package/dist/DboBuilder/QueryBuilder/Functions/Functions.js +19 -376
  4. package/dist/DboBuilder/QueryBuilder/Functions/Functions.js.map +1 -1
  5. package/dist/DboBuilder/QueryBuilder/Functions/HASHING_FUNCTIONS.d.ts +3 -0
  6. package/dist/DboBuilder/QueryBuilder/Functions/HASHING_FUNCTIONS.d.ts.map +1 -0
  7. package/dist/DboBuilder/QueryBuilder/Functions/HASHING_FUNCTIONS.js +107 -0
  8. package/dist/DboBuilder/QueryBuilder/Functions/HASHING_FUNCTIONS.js.map +1 -0
  9. package/dist/DboBuilder/QueryBuilder/Functions/TEXT_FUNCTIONS.d.ts +3 -0
  10. package/dist/DboBuilder/QueryBuilder/Functions/TEXT_FUNCTIONS.d.ts.map +1 -0
  11. package/dist/DboBuilder/QueryBuilder/Functions/TEXT_FUNCTIONS.js +276 -0
  12. package/dist/DboBuilder/QueryBuilder/Functions/TEXT_FUNCTIONS.js.map +1 -0
  13. package/dist/DboBuilder/QueryBuilder/Functions/utils.d.ts +3 -0
  14. package/dist/DboBuilder/QueryBuilder/Functions/utils.d.ts.map +1 -0
  15. package/dist/DboBuilder/QueryBuilder/Functions/utils.js +6 -0
  16. package/dist/DboBuilder/QueryBuilder/Functions/utils.js.map +1 -0
  17. package/dist/DboBuilder/TableHandler/update.js.map +1 -1
  18. package/dist/DboBuilder/ViewHandler/getExistsCondition.js +5 -5
  19. package/dist/DboBuilder/ViewHandler/getExistsCondition.js.map +1 -1
  20. package/dist/DboBuilder/ViewHandler/parseFieldFilter.js.map +1 -1
  21. package/dist/DboBuilder/runSql/runSQL.js +1 -1
  22. package/dist/DboBuilder/runSql/runSQL.js.map +1 -1
  23. package/dist/FileManager/FileManager.js.map +1 -1
  24. package/dist/Logging.d.ts +1 -1
  25. package/dist/Logging.d.ts.map +1 -1
  26. package/dist/PubSubManager/PubSubManager.d.ts +0 -1
  27. package/dist/PubSubManager/PubSubManager.d.ts.map +1 -1
  28. package/dist/PubSubManager/PubSubManager.js.map +1 -1
  29. package/dist/PubSubManager/SyncReplication/getSyncUtilFunctions.d.ts +7 -2
  30. package/dist/PubSubManager/SyncReplication/getSyncUtilFunctions.d.ts.map +1 -1
  31. package/dist/PubSubManager/SyncReplication/getSyncUtilFunctions.js +21 -16
  32. package/dist/PubSubManager/SyncReplication/getSyncUtilFunctions.js.map +1 -1
  33. package/dist/PubSubManager/SyncReplication/syncData.d.ts.map +1 -1
  34. package/dist/PubSubManager/SyncReplication/syncData.js +16 -14
  35. package/dist/PubSubManager/SyncReplication/syncData.js.map +1 -1
  36. package/dist/TableConfig/TableConfig.js.map +1 -1
  37. package/dist/runClientRequest.js +1 -1
  38. package/dist/runClientRequest.js.map +1 -1
  39. package/lib/DboBuilder/DboBuilder.ts +1 -1
  40. package/lib/DboBuilder/QueryBuilder/Functions/Functions.ts +142 -557
  41. package/lib/DboBuilder/QueryBuilder/Functions/HASHING_FUNCTIONS.ts +120 -0
  42. package/lib/DboBuilder/QueryBuilder/Functions/TEXT_FUNCTIONS.ts +302 -0
  43. package/lib/DboBuilder/QueryBuilder/Functions/utils.ts +3 -0
  44. package/lib/DboBuilder/TableHandler/update.ts +1 -1
  45. package/lib/DboBuilder/ViewHandler/getExistsCondition.ts +4 -4
  46. package/lib/DboBuilder/ViewHandler/parseFieldFilter.ts +1 -1
  47. package/lib/DboBuilder/runSql/runSQL.ts +1 -1
  48. package/lib/FileManager/FileManager.ts +1 -1
  49. package/lib/Logging.ts +1 -1
  50. package/lib/PubSubManager/PubSubManager.ts +0 -1
  51. package/lib/PubSubManager/SyncReplication/getSyncUtilFunctions.ts +28 -19
  52. package/lib/PubSubManager/SyncReplication/syncData.ts +16 -14
  53. package/lib/TableConfig/TableConfig.ts +1 -1
  54. package/lib/runClientRequest.ts +1 -1
  55. package/package.json +10 -10
@@ -0,0 +1,120 @@
1
+ import * as pgPromise from "pg-promise";
2
+ import { asNameAlias } from "../../../utils/asNameAlias";
3
+ import type { FunctionSpec } from "./Functions";
4
+
5
+ const pgp = pgPromise();
6
+
7
+ const MAX_COL_NUM = 1600;
8
+
9
+ export const HASHING_FUNCTIONS: FunctionSpec[] = [
10
+ // Hashing
11
+ {
12
+ name: "$md5_multi",
13
+ description: ` :[...column_names] -> md5 hash of the column content`,
14
+ type: "function",
15
+ singleColArg: false,
16
+ numArgs: MAX_COL_NUM,
17
+ getFields: (args: any[]) => args,
18
+ getQuery: ({ args, tableAliasRaw: tableAlias }) => {
19
+ const q = pgp.as.format(
20
+ "md5(" +
21
+ args
22
+ .map((fname) => "COALESCE( " + asNameAlias(fname, tableAlias) + "::text, '' )")
23
+ .join(" || ") +
24
+ ")",
25
+ );
26
+ return q;
27
+ },
28
+ },
29
+ {
30
+ name: "$md5_multi_agg",
31
+ description: ` :[...column_names] -> md5 hash of the string aggregation of column content`,
32
+ type: "aggregation",
33
+ singleColArg: false,
34
+ numArgs: MAX_COL_NUM,
35
+ getFields: (args: any[]) => args,
36
+ getQuery: ({ args, tableAliasRaw: tableAlias }) => {
37
+ const q = pgp.as.format(
38
+ "md5(string_agg(" +
39
+ args
40
+ .map((fname) => "COALESCE( " + asNameAlias(fname, tableAlias) + "::text, '' )")
41
+ .join(" || ") +
42
+ ", ','))",
43
+ );
44
+ return q;
45
+ },
46
+ },
47
+
48
+ {
49
+ name: "$sha256_multi",
50
+ description: ` :[...column_names] -> sha256 hash of the of column content`,
51
+ type: "function",
52
+ singleColArg: false,
53
+ numArgs: MAX_COL_NUM,
54
+ getFields: (args: any[]) => args,
55
+ getQuery: ({ args, tableAliasRaw: tableAlias }) => {
56
+ const q = pgp.as.format(
57
+ "encode(sha256((" +
58
+ args
59
+ .map((fname) => "COALESCE( " + asNameAlias(fname, tableAlias) + ", '' )")
60
+ .join(" || ") +
61
+ ")::text::bytea), 'hex')",
62
+ );
63
+ return q;
64
+ },
65
+ },
66
+ {
67
+ name: "$sha256_multi_agg",
68
+ description: ` :[...column_names] -> sha256 hash of the string aggregation of column content`,
69
+ type: "aggregation",
70
+ singleColArg: false,
71
+ numArgs: MAX_COL_NUM,
72
+ getFields: (args: any[]) => args,
73
+ getQuery: ({ args, tableAliasRaw: tableAlias }) => {
74
+ const q = pgp.as.format(
75
+ "encode(sha256(string_agg(" +
76
+ args
77
+ .map((fname) => "COALESCE( " + asNameAlias(fname, tableAlias) + ", '' )")
78
+ .join(" || ") +
79
+ ", ',')::text::bytea), 'hex')",
80
+ );
81
+ return q;
82
+ },
83
+ },
84
+ {
85
+ name: "$sha512_multi",
86
+ description: ` :[...column_names] -> sha512 hash of the of column content`,
87
+ type: "function",
88
+ singleColArg: false,
89
+ numArgs: MAX_COL_NUM,
90
+ getFields: (args: any[]) => args,
91
+ getQuery: ({ args, tableAliasRaw: tableAlias }) => {
92
+ const q = pgp.as.format(
93
+ "encode(sha512((" +
94
+ args
95
+ .map((fname) => "COALESCE( " + asNameAlias(fname, tableAlias) + ", '' )")
96
+ .join(" || ") +
97
+ ")::text::bytea), 'hex')",
98
+ );
99
+ return q;
100
+ },
101
+ },
102
+ {
103
+ name: "$sha512_multi_agg",
104
+ description: ` :[...column_names] -> sha512 hash of the string aggregation of column content`,
105
+ type: "aggregation",
106
+ singleColArg: false,
107
+ numArgs: MAX_COL_NUM,
108
+ getFields: (args: any[]) => args,
109
+ getQuery: ({ args, tableAliasRaw: tableAlias }) => {
110
+ const q = pgp.as.format(
111
+ "encode(sha512(string_agg(" +
112
+ args
113
+ .map((fname) => "COALESCE( " + asNameAlias(fname, tableAlias) + ", '' )")
114
+ .join(" || ") +
115
+ ", ',')::text::bytea), 'hex')",
116
+ );
117
+ return q;
118
+ },
119
+ },
120
+ ];
@@ -0,0 +1,302 @@
1
+ import { isEmpty, postgresToTsType } from "prostgles-types";
2
+ import { asValue } from "../../../PubSubManager/PubSubManagerUtils";
3
+ import { parseFieldFilter } from "../../ViewHandler/parseFieldFilter";
4
+ import { asFunction } from "./utils";
5
+ import type { FunctionSpec } from "./Functions";
6
+ import * as pgPromise from "pg-promise";
7
+ import { asNameAlias } from "../../../utils/asNameAlias";
8
+ const pgp = pgPromise();
9
+
10
+ export const TEXT_FUNCTIONS: FunctionSpec[] = [
11
+ {
12
+ name: "$left",
13
+ description: ` :[column_name, number] -> substring`,
14
+ type: "function",
15
+ numArgs: 2,
16
+ singleColArg: false,
17
+ getFields: (args: any[]) => [args[0]],
18
+ getQuery: ({ allowedFields, args, tableAliasRaw: tableAlias }) => {
19
+ return pgp.as.format("LEFT(" + asNameAlias(args[0], tableAlias) + ", $1)", [args[1]]);
20
+ },
21
+ },
22
+ {
23
+ name: "$column",
24
+ description: ` :[column_name] -> Returns the column value as is`,
25
+ type: "function",
26
+ numArgs: 1,
27
+ singleColArg: false,
28
+ getFields: (args: any[]) => [args[0]],
29
+ getQuery: ({ allowedFields, args, tableAliasRaw: tableAlias }) => {
30
+ const aliasedColumnName = args[0];
31
+ if (!aliasedColumnName) {
32
+ throw `$column: column_name is required`;
33
+ }
34
+ return pgp.as.format(asNameAlias(aliasedColumnName, tableAlias));
35
+ },
36
+ },
37
+ {
38
+ name: "$unnest_words",
39
+ description: ` :[column_name] -> Splits string at spaces`,
40
+ type: "function",
41
+ numArgs: 1,
42
+ singleColArg: true,
43
+ getFields: (args: any[]) => [args[0]],
44
+ getQuery: ({ allowedFields, args, tableAliasRaw: tableAlias }) => {
45
+ return pgp.as.format(
46
+ "unnest(string_to_array(" + asNameAlias(args[0], tableAlias) + "::TEXT , ' '))",
47
+ ); //, [args[1]]
48
+ },
49
+ } satisfies FunctionSpec,
50
+ {
51
+ name: "$right",
52
+ description: ` :[column_name, number] -> substring`,
53
+ type: "function",
54
+ numArgs: 2,
55
+ singleColArg: false,
56
+ getFields: (args: any[]) => [args[0]],
57
+ getQuery: ({ allowedFields, args, tableAliasRaw: tableAlias }) => {
58
+ return pgp.as.format("RIGHT(" + asNameAlias(args[0], tableAlias) + ", $1)", [args[1]]);
59
+ },
60
+ },
61
+
62
+ {
63
+ name: "$to_char",
64
+ type: "function",
65
+ description: ` :[column_name, format<string>] -> format dates and strings. Eg: [current_timestamp, 'HH12:MI:SS']`,
66
+ singleColArg: false,
67
+ numArgs: 2,
68
+ getFields: (args: any[]) => [args[0]],
69
+ getQuery: ({ allowedFields, args, tableAliasRaw: tableAlias }) => {
70
+ if (args.length === 3) {
71
+ return pgp.as.format("to_char(" + asNameAlias(args[0], tableAlias) + ", $2, $3)", [
72
+ args[0],
73
+ args[1],
74
+ args[2],
75
+ ]);
76
+ }
77
+ return pgp.as.format("to_char(" + asNameAlias(args[0], tableAlias) + ", $2)", [
78
+ args[0],
79
+ args[1],
80
+ ]);
81
+ },
82
+ },
83
+
84
+ /* Text col and value funcs */
85
+ ...["position", "position_lower"].map((funcName) =>
86
+ asFunction({
87
+ name: "$" + funcName,
88
+ type: "function",
89
+ numArgs: 1,
90
+ singleColArg: false,
91
+ getFields: (args: any[]) => [args[1]],
92
+ getQuery: ({ args, tableAliasRaw: tableAlias }) => {
93
+ let a1 = asValue(args[0]),
94
+ a2 = asNameAlias(args[1], tableAlias);
95
+ if (funcName === "position_lower") {
96
+ a1 = `LOWER(${a1}::text)`;
97
+ a2 = `LOWER(${a2}::text)`;
98
+ }
99
+ return `position( ${a1} IN ${a2} )`;
100
+ },
101
+ }),
102
+ ),
103
+ ...["template_string"].map((funcName) =>
104
+ asFunction({
105
+ name: "$" + funcName,
106
+ type: "function",
107
+ numArgs: 1,
108
+ minCols: 0,
109
+ singleColArg: false,
110
+ getFields: (args: any[]) => [] as string[], // Fields not validated because we'll use the allowed ones anyway
111
+ getQuery: ({ allowedFields, args, tableAliasRaw: tableAlias }) => {
112
+ if (typeof args[0] !== "string")
113
+ throw "First argument must be a string. E.g.: '{col1} ..text {col2} ...' ";
114
+
115
+ const rawValue = args[0];
116
+ let finalValue = rawValue;
117
+ const usedColumns = allowedFields.filter((fName) => rawValue.includes(`{${fName}}`));
118
+ usedColumns.forEach((colName, idx) => {
119
+ finalValue = finalValue.split(`{${colName}}`).join(`%${idx + 1}$s`);
120
+ });
121
+ finalValue = asValue(finalValue);
122
+
123
+ if (usedColumns.length) {
124
+ return `format(${finalValue}, ${usedColumns.map((c) => `${asNameAlias(c, tableAlias)}::TEXT`).join(", ")})`;
125
+ }
126
+
127
+ return `format(${finalValue})`;
128
+ },
129
+ }),
130
+ ),
131
+
132
+ /** Custom highlight -> myterm => ['some text and', ['myterm'], ' and some other text']
133
+ * (fields: "*" | string[], term: string, { edgeTruncate: number = -1; noFields: boolean = false }) => string | (string | [string])[]
134
+ * edgeTruncate = maximum extra characters left and right of matches
135
+ * noFields = exclude field names in search
136
+ * */
137
+ asFunction({
138
+ name: "$term_highlight" /* */,
139
+ description: ` :[column_names<string[] | "*">, search_term<string>, opts?<{ returnIndex?: number; edgeTruncate?: number; noFields?: boolean }>] -> get case-insensitive text match highlight`,
140
+ type: "function",
141
+ numArgs: 1,
142
+ singleColArg: true,
143
+ canBeUsedForFilter: true,
144
+ getFields: (args: any[]) => args[0],
145
+ getQuery: ({ allowedFields, args, tableAliasRaw: tableAlias, allColumns }) => {
146
+ const cols = parseFieldFilter(args[0], false, allowedFields);
147
+ let term = args[1];
148
+ const rawTerm = args[1];
149
+ const { edgeTruncate, noFields = false, returnType, matchCase = false } = args[2] || {};
150
+ if (!isEmpty(args[2])) {
151
+ const keys = Object.keys(args[2]);
152
+ const validKeys = ["edgeTruncate", "noFields", "returnType", "matchCase"];
153
+ const bad_keys = keys.filter((k) => !validKeys.includes(k));
154
+ if (bad_keys.length)
155
+ throw (
156
+ "Invalid options provided for $term_highlight. Expecting one of: " +
157
+ validKeys.join(", ")
158
+ );
159
+ }
160
+ if (!cols.length) throw "Cols are empty/invalid";
161
+ if (typeof term !== "string") throw "Non string term provided: " + term;
162
+ if (edgeTruncate !== undefined && (!Number.isInteger(edgeTruncate) || edgeTruncate < -1))
163
+ throw "Invalid edgeTruncate. expecting a positive integer";
164
+ if (typeof noFields !== "boolean") throw "Invalid noFields. expecting boolean";
165
+ const RETURN_TYPES = ["index", "boolean", "object"];
166
+ if (returnType && !RETURN_TYPES.includes(returnType)) {
167
+ throw `returnType can only be one of: ${RETURN_TYPES}`;
168
+ }
169
+
170
+ const makeTextMatcherArray = (rawText: string, _term: string) => {
171
+ let matchText = rawText,
172
+ term = _term;
173
+ if (!matchCase) {
174
+ matchText = `LOWER(${rawText})`;
175
+ term = `LOWER(${term})`;
176
+ }
177
+ let leftStr = `substr(${rawText}, 1, position(${term} IN ${matchText}) - 1 )`,
178
+ rightStr = `substr(${rawText}, position(${term} IN ${matchText}) + length(${term}) )`;
179
+ if (edgeTruncate) {
180
+ leftStr = `RIGHT(${leftStr}, ${asValue(edgeTruncate)})`;
181
+ rightStr = `LEFT(${rightStr}, ${asValue(edgeTruncate)})`;
182
+ }
183
+ return `
184
+ CASE WHEN position(${term} IN ${matchText}) > 0 AND ${term} <> ''
185
+ THEN array_to_json(ARRAY[
186
+ to_json( ${leftStr}::TEXT ),
187
+ array_to_json(
188
+ ARRAY[substr(${rawText}, position(${term} IN ${matchText}), length(${term}) )::TEXT ]
189
+ ),
190
+ to_json(${rightStr}::TEXT )
191
+ ])
192
+ ELSE
193
+ array_to_json(ARRAY[(${rawText})::TEXT])
194
+ END
195
+ `;
196
+ };
197
+
198
+ const colRaw =
199
+ "( " +
200
+ cols
201
+ .map(
202
+ (c) =>
203
+ `${noFields ? "" : asValue(c + ": ") + " || "} COALESCE(${asNameAlias(c, tableAlias)}::TEXT, '')`,
204
+ )
205
+ .join(" || ', ' || ") +
206
+ " )";
207
+ let col = colRaw;
208
+ term = asValue(term);
209
+ if (!matchCase) {
210
+ col = "LOWER" + col;
211
+ term = `LOWER(${term})`;
212
+ }
213
+
214
+ let leftStr = `substr(${colRaw}, 1, position(${term} IN ${col}) - 1 )`,
215
+ rightStr = `substr(${colRaw}, position(${term} IN ${col}) + length(${term}) )`;
216
+ if (edgeTruncate) {
217
+ leftStr = `RIGHT(${leftStr}, ${asValue(edgeTruncate)})`;
218
+ rightStr = `LEFT(${rightStr}, ${asValue(edgeTruncate)})`;
219
+ }
220
+
221
+ // console.log(col);
222
+ let res = "";
223
+ if (returnType === "index") {
224
+ res = `CASE WHEN position(${term} IN ${col}) > 0 THEN position(${term} IN ${col}) - 1 ELSE -1 END`;
225
+
226
+ // } else if(returnType === "boolean"){
227
+ // res = `CASE WHEN position(${term} IN ${col}) > 0 THEN TRUE ELSE FALSE END`;
228
+ } else if (returnType === "object" || returnType === "boolean") {
229
+ const hasChars = Boolean(rawTerm && /[a-z]/i.test(rawTerm));
230
+ const validCols = cols
231
+ .map((c) => {
232
+ const colInfo = allColumns.find((ac) => ac.name === c);
233
+ return {
234
+ key: c,
235
+ colInfo,
236
+ };
237
+ })
238
+ .filter((c) => c.colInfo && c.colInfo.udt_name !== "bytea");
239
+
240
+ const _cols = validCols.filter(
241
+ (c) =>
242
+ /** Exclude numeric columns when the search tern contains a character */
243
+ !hasChars || postgresToTsType(c.colInfo!.udt_name) !== "number",
244
+ );
245
+
246
+ /** This will break GROUP BY (non-integer constant in GROUP BY) */
247
+ if (!_cols.length) {
248
+ if (validCols.length && hasChars)
249
+ throw `You're searching the impossible: characters in numeric fields. Use this to prevent making such a request in future: /[a-z]/i.test(your_term) `;
250
+ return returnType === "boolean" ? "FALSE" : "NULL";
251
+ }
252
+ res = `CASE
253
+ ${_cols
254
+ .map((c) => {
255
+ const colNameEscaped = asNameAlias(c.key, tableAlias);
256
+ let colSelect = `${colNameEscaped}::TEXT`;
257
+ const isTstamp = c.colInfo?.udt_name.startsWith("timestamp");
258
+ if (isTstamp || c.colInfo?.udt_name === "date") {
259
+ colSelect = `( CASE WHEN ${colNameEscaped} IS NULL THEN ''
260
+ ELSE concat_ws(' ',
261
+ trim(to_char(${colNameEscaped}, 'YYYY-MM-DD HH24:MI:SS')),
262
+ trim(to_char(${colNameEscaped}, 'Day Month')),
263
+ 'Q' || trim(to_char(${colNameEscaped}, 'Q')),
264
+ 'WK' || trim(to_char(${colNameEscaped}, 'WW'))
265
+ ) END)`;
266
+ }
267
+ const colTxt = `COALESCE(${colSelect}, '')`; // position(${term} IN ${colTxt}) > 0
268
+ if (returnType === "boolean") {
269
+ return `
270
+ WHEN ${colTxt} ${matchCase ? "LIKE" : "ILIKE"} ${asValue("%" + rawTerm + "%")}
271
+ THEN TRUE
272
+ `;
273
+ }
274
+ return `
275
+ WHEN ${colTxt} ${matchCase ? "LIKE" : "ILIKE"} ${asValue("%" + rawTerm + "%")}
276
+ THEN json_build_object(
277
+ ${asValue(c.key)},
278
+ ${makeTextMatcherArray(colTxt, term)}
279
+ )::jsonb
280
+ `;
281
+ })
282
+ .join(" ")}
283
+ ELSE ${returnType === "boolean" ? "FALSE" : "NULL"}
284
+
285
+ END`;
286
+
287
+ // console.log(res)
288
+ } else {
289
+ /* If no match or empty search THEN return full row as string within first array element */
290
+ res = `CASE WHEN position(${term} IN ${col}) > 0 AND ${term} <> '' THEN array_to_json(ARRAY[
291
+ to_json( ${leftStr}::TEXT ),
292
+ array_to_json(
293
+ ARRAY[substr(${colRaw}, position(${term} IN ${col}), length(${term}) )::TEXT ]
294
+ ),
295
+ to_json(${rightStr}::TEXT )
296
+ ]) ELSE array_to_json(ARRAY[(${colRaw})::TEXT]) END`;
297
+ }
298
+
299
+ return res;
300
+ },
301
+ } satisfies FunctionSpec),
302
+ ];
@@ -0,0 +1,3 @@
1
+ import type { FunctionSpec } from "./Functions";
2
+
3
+ export const asFunction = (def: FunctionSpec) => def;
@@ -112,7 +112,7 @@ export async function update(
112
112
  }
113
113
  await Promise.all(
114
114
  nestedInserts.map(async (nestedInsert) => {
115
- const nesedTableHandler = finalDBtx[nestedInsert.tableName] as TableHandler | undefined;
115
+ const nesedTableHandler = finalDBtx[nestedInsert.tableName];
116
116
  if (!nesedTableHandler)
117
117
  throw `nestedInsert Tablehandler not found for ${nestedInsert.tableName}`;
118
118
  const refTableRules =
@@ -21,7 +21,7 @@ export async function getExistsCondition(
21
21
  if (Object.keys(targetTableFilter).find((fk) => EXISTS_KEYS.includes(fk as EXISTS_KEY))) {
22
22
  throw {
23
23
  stack: ["prepareExistCondition()"],
24
- message: "Nested exists dissallowed",
24
+ message: "Nested exists disallowed",
25
25
  };
26
26
  }
27
27
 
@@ -35,16 +35,16 @@ export async function getExistsCondition(
35
35
  }
36
36
  const targetTable = eConfig.isJoined ? eConfig.parsedPath.at(-1)!.table : eConfig.targetTable;
37
37
  if (localParams?.clientReq && this.dboBuilder.publishParser) {
38
- t2Rules = (await this.dboBuilder.publishParser.getValidatedRequestRuleWusr(
38
+ t2Rules = await this.dboBuilder.publishParser.getValidatedRequestRuleWusr(
39
39
  {
40
40
  tableName: targetTable,
41
41
  command: "find",
42
42
  clientReq: localParams.clientReq,
43
43
  },
44
44
  localParams.scope,
45
- )) as ParsedTableRule | undefined;
45
+ );
46
46
 
47
- if (!t2Rules || !t2Rules.select) throw "Dissallowed";
47
+ if (!t2Rules.select) throw "Disallowed";
48
48
  ({ forcedFilter, filterFields } = t2Rules.select);
49
49
  }
50
50
 
@@ -50,7 +50,7 @@ export const parseFieldFilter = <AllowedKeys extends string[]>(
50
50
  ["field1", "field2", "field3"]
51
51
  */
52
52
  } else {
53
- colNames = fieldParams.slice(0) as AllowedKeys;
53
+ colNames = fieldParams.slice(0);
54
54
  }
55
55
 
56
56
  /*
@@ -37,7 +37,7 @@ export async function runSQL(
37
37
  throw "Not allowed to run SQL";
38
38
  }
39
39
 
40
- const { returnType, allowListen, hasParams = true }: SQLOptions = options || ({} as SQLOptions);
40
+ const { returnType, allowListen, hasParams = true }: SQLOptions = options || ({});
41
41
  const { socket } = localParams?.clientReq ?? {};
42
42
 
43
43
  const db = localParams?.tx?.t || this.db;
@@ -340,7 +340,7 @@ export const getFileType = async (
340
340
  /* Set correct/missing extension */
341
341
  const nameExt = fileNameMime.ext;
342
342
  if (["xml", "txt", "csv", "tsv", "svg", "sql"].includes(nameExt)) {
343
- return fileNameMime as any;
343
+ return fileNameMime;
344
344
  }
345
345
 
346
346
  throw new Error("Could not get the file type from file buffer");
package/lib/Logging.ts CHANGED
@@ -43,7 +43,7 @@ export namespace EventTypes {
43
43
  | "sync.wal.isSending()"
44
44
  | "initial"
45
45
  | "socket?.connected"
46
- | "sync.last_synced"
46
+ | "sync.lr"
47
47
  | "rowsFullyMatch"
48
48
  | "getLastSynced.rowsFullyMatch(lr)"
49
49
  | "getLastSynced.nothingToSync"
@@ -54,7 +54,6 @@ export type SyncParams = {
54
54
  wal?: WAL;
55
55
  throttle?: number;
56
56
  lr?: AnyObject;
57
- last_synced: number;
58
57
  is_syncing: boolean;
59
58
  handlers: ReturnType<(typeof ReplicationProtocol)["getServerHandlers"]>;
60
59
  };
@@ -104,8 +104,8 @@ export const getSyncUtilFunctions = ({
104
104
  getClientData = (from_synced: number | undefined, offset = 0): Promise<AnyObject[]> => {
105
105
  return new Promise(async (resolve, reject) => {
106
106
  const onPullRequest = {
107
- from_synced: from_synced,
108
- offset: offset || 0,
107
+ from_synced,
108
+ offset,
109
109
  limit: batch_size,
110
110
  to_synced: undefined,
111
111
  };
@@ -169,13 +169,13 @@ export const getSyncUtilFunctions = ({
169
169
  /**
170
170
  * Upserts the given client data where synced_field is higher than on server
171
171
  */
172
- upsertData = async (data: AnyObject[]) => {
172
+ upsertData = async (data: AnyObject[], source: "WAL" | "client") => {
173
173
  const start = Date.now();
174
174
  const result = await pubSubManager.dboBuilder
175
175
  .getTX(async (dbTX) => {
176
176
  const tableHandlerTx = dbTX[table_name] as TableHandler;
177
- const existingData = await tableHandlerTx.find(
178
- { $or: data.map((d) => pickKeys(d, id_fields)) },
177
+ const existingData = (await tableHandlerTx.find(
178
+ { $or: data.map((row) => pickKeys(row, id_fields)) },
179
179
  {
180
180
  select: [synced_field, ...id_fields],
181
181
  orderBy: orderByAsc,
@@ -183,11 +183,16 @@ export const getSyncUtilFunctions = ({
183
183
  undefined,
184
184
  table_rules,
185
185
  { clientReq: { socket } },
186
+ )) as AnyObject[];
187
+ let rowsToInsert = data.filter(
188
+ (incomingRow) =>
189
+ !existingData.find((existingRow) => rowsIdsMatch(existingRow, incomingRow)),
186
190
  );
187
- let rowsToInsert = data.filter((d) => !existingData.find((ed) => rowsIdsMatch(ed, d)));
188
- let rowsToUpdate = data.filter((d) =>
191
+ let rowsToUpdate = data.filter((incomingRow) =>
189
192
  existingData.find(
190
- (ed) => rowsIdsMatch(ed, d) && Number(ed[synced_field]) < Number(d[synced_field]),
193
+ (existingRow) =>
194
+ rowsIdsMatch(existingRow, incomingRow) &&
195
+ Number(existingRow[synced_field]) < Number(incomingRow[synced_field]),
191
196
  ),
192
197
  );
193
198
 
@@ -227,7 +232,7 @@ export const getSyncUtilFunctions = ({
227
232
  })
228
233
  .then(({ inserts, updates }) => {
229
234
  log(
230
- `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))}`,
235
+ `upsertData (from ${source}): 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
236
  );
232
237
  return {
233
238
  inserted: inserts.length,
@@ -263,17 +268,22 @@ export const getSyncUtilFunctions = ({
263
268
  * Pushes the given data to client
264
269
  * @param isSynced = true if
265
270
  */
266
- pushData = async (data: AnyObject[], isSynced = false) => {
271
+ pushData = async (
272
+ request: { state: "syncing-data"; data: AnyObject[] } | { state: "synced" },
273
+ ) => {
274
+ const items = request.state === "syncing-data" ? request.data : undefined;
267
275
  const start = Date.now();
268
276
  const result = await new Promise<{
269
277
  pushed: number;
270
278
  }>(async (resolve, reject) => {
271
279
  const resp = await handlers.UpdateRequest(
272
- isSynced ? { state: "synced", isSynced: true } : { state: "syncing", data },
280
+ request.state === "synced" ?
281
+ { state: "synced", isSynced: true }
282
+ : { state: "syncing", data: request.data },
273
283
  );
274
284
  if (resp.success) {
275
285
  // console.log("PUSHED to client: fr/lr", data[0], data[data.length - 1]);
276
- resolve({ pushed: data.length });
286
+ resolve({ pushed: items?.length ?? 0 });
277
287
  } else {
278
288
  reject(resp);
279
289
  console.error("Unexpected response");
@@ -284,7 +294,7 @@ export const getSyncUtilFunctions = ({
284
294
  type: "sync",
285
295
  command: "pushData",
286
296
  tableName: sync.table_name,
287
- rows: data.length,
297
+ rows: items?.length ?? 0,
288
298
  socketId: socket_id,
289
299
  duration: Date.now() - start,
290
300
  sid: sync.sid,
@@ -392,7 +402,6 @@ export const getSyncUtilFunctions = ({
392
402
  );
393
403
  }
394
404
  sync.lr = lastRow;
395
- sync.last_synced = +sync.lr[synced_field];
396
405
  },
397
406
  /**
398
407
  * Will push pull sync between client and server from a given from_synced value
@@ -415,7 +424,7 @@ export const getSyncUtilFunctions = ({
415
424
  const clientData = await getClientData(min_synced, offset);
416
425
 
417
426
  if (clientData.length) {
418
- const res = await upsertData(clientData);
427
+ const res = await upsertData(clientData, "client");
419
428
  inserted += res.inserted;
420
429
  updated += res.updated;
421
430
  }
@@ -425,7 +434,6 @@ export const getSyncUtilFunctions = ({
425
434
  serverData = await getServerData(min_synced, offset);
426
435
  } catch (e) {
427
436
  console.trace("sync getServerData err", e);
428
- // await pushData(undefined, undefined, "Internal error. Check server logs");
429
437
  throw " d";
430
438
  }
431
439
 
@@ -455,9 +463,10 @@ export const getSyncUtilFunctions = ({
455
463
  );
456
464
  });
457
465
  if (forClient.length) {
458
- const res = await pushData(
459
- forClient.filter((d) => !sync.wal || !sync.wal.isInHistory(d)),
460
- );
466
+ const res = await pushData({
467
+ state: "syncing-data",
468
+ data: forClient.filter((d) => !sync.wal || !sync.wal.isInHistory(d)),
469
+ });
461
470
  pushed += res.pushed;
462
471
  }
463
472