prostgles-server 2.0.178 → 2.0.179
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/dist/AuthHandler.d.ts +4 -4
- package/dist/AuthHandler.d.ts.map +1 -1
- package/dist/DBSchemaBuilder.d.ts +6 -6
- package/dist/DBSchemaBuilder.d.ts.map +1 -1
- package/dist/DBSchemaBuilder.js +25 -8
- package/dist/DBSchemaBuilder.js.map +1 -1
- package/dist/DboBuilder.d.ts +20 -21
- package/dist/DboBuilder.d.ts.map +1 -1
- package/dist/DboBuilder.js +1 -1
- package/dist/DboBuilder.js.map +1 -1
- package/dist/Prostgles.d.ts +8 -10
- package/dist/Prostgles.d.ts.map +1 -1
- package/dist/Prostgles.js.map +1 -1
- package/dist/PublishParser.d.ts +37 -37
- package/dist/PublishParser.d.ts.map +1 -1
- package/dist/PublishParser.js.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/lib/AuthHandler.d.ts +148 -0
- package/lib/AuthHandler.d.ts.map +1 -0
- package/lib/AuthHandler.js +411 -0
- package/lib/AuthHandler.ts +3 -3
- package/lib/DBEventsManager.d.ts +38 -0
- package/lib/DBEventsManager.d.ts.map +1 -0
- package/lib/DBEventsManager.js +136 -0
- package/lib/DBSchemaBuilder.d.ts +11 -0
- package/lib/DBSchemaBuilder.d.ts.map +1 -0
- package/lib/DBSchemaBuilder.js +102 -0
- package/lib/DBSchemaBuilder.ts +62 -27
- package/lib/DboBuilder.d.ts +428 -0
- package/lib/DboBuilder.d.ts.map +1 -0
- package/lib/DboBuilder.js +3078 -0
- package/lib/DboBuilder.ts +25 -25
- package/lib/FileManager.d.ts +168 -0
- package/lib/FileManager.d.ts.map +1 -0
- package/lib/FileManager.js +474 -0
- package/lib/Filtering.d.ts +15 -0
- package/lib/Filtering.d.ts.map +1 -0
- package/lib/Filtering.js +299 -0
- package/lib/PostgresNotifListenManager.d.ts +27 -0
- package/lib/PostgresNotifListenManager.d.ts.map +1 -0
- package/lib/PostgresNotifListenManager.js +122 -0
- package/lib/Prostgles.d.ts +193 -0
- package/lib/Prostgles.d.ts.map +1 -0
- package/lib/Prostgles.js +579 -0
- package/lib/Prostgles.ts +6 -6
- package/lib/PubSubManager.d.ts +157 -0
- package/lib/PubSubManager.d.ts.map +1 -0
- package/lib/PubSubManager.js +1400 -0
- package/lib/PublishParser.d.ts +262 -0
- package/lib/PublishParser.d.ts.map +1 -0
- package/lib/PublishParser.js +390 -0
- package/lib/PublishParser.ts +39 -38
- package/lib/QueryBuilder.d.ts +124 -0
- package/lib/QueryBuilder.d.ts.map +1 -0
- package/lib/QueryBuilder.js +1349 -0
- package/lib/SyncReplication.d.ts +34 -0
- package/lib/SyncReplication.d.ts.map +1 -0
- package/lib/SyncReplication.js +411 -0
- package/lib/TableConfig.d.ts +175 -0
- package/lib/TableConfig.d.ts.map +1 -0
- package/lib/TableConfig.js +231 -0
- package/lib/index.d.ts +10 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +45 -0
- package/lib/index.ts +3 -4
- package/lib/shortestPath.d.ts +10 -0
- package/lib/shortestPath.d.ts.map +1 -0
- package/lib/shortestPath.js +111 -0
- package/lib/utils.d.ts +2 -0
- package/lib/utils.d.ts.map +1 -0
- package/lib/utils.js +5 -0
- package/package.json +3 -3
- package/tests/client/PID.txt +1 -1
- package/tests/client/index.d.ts +1 -1
- package/tests/client/index.d.ts.map +1 -1
- package/tests/client_only_queries.d.ts +4 -0
- package/tests/client_only_queries.d.ts.map +1 -0
- package/tests/isomorphic_queries.d.ts +6 -0
- package/tests/isomorphic_queries.d.ts.map +1 -0
- package/tests/server/DBoGenerated.d.ts +97 -193
- package/tests/server/dboTypeCheck.d.ts +2 -0
- package/tests/server/dboTypeCheck.d.ts.map +1 -0
- package/tests/server/dboTypeCheck.js +14 -0
- package/tests/server/dboTypeCheck.ts +17 -0
- package/tests/server/index.d.ts +2 -0
- package/tests/server/index.d.ts.map +1 -0
- package/tests/server/index.js +11 -11
- package/tests/server/index.ts +23 -16
- package/tests/server/package-lock.json +5 -5
- package/tests/server/publishTypeCheck.d.ts +2 -0
- package/tests/server/publishTypeCheck.d.ts.map +1 -0
- package/tests/server/publishTypeCheck.js +120 -0
- package/tests/server/publishTypeCheck.ts +129 -0
- package/tests/server/tsconfig.json +4 -5
- package/tests/server_only_queries.d.ts +2 -0
- package/tests/server_only_queries.d.ts.map +1 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { PubSubManager, SyncParams } from "./PubSubManager";
|
|
2
|
+
import { AnyObject } from "prostgles-types";
|
|
3
|
+
export declare type ClientSyncInfo = Partial<{
|
|
4
|
+
c_fr: AnyObject;
|
|
5
|
+
c_lr: AnyObject;
|
|
6
|
+
/**
|
|
7
|
+
* PG count is ussually string due to bigint
|
|
8
|
+
*/
|
|
9
|
+
c_count: number | string;
|
|
10
|
+
}>;
|
|
11
|
+
export declare type ServerSyncInfo = Partial<{
|
|
12
|
+
s_fr: AnyObject;
|
|
13
|
+
s_lr: AnyObject;
|
|
14
|
+
/**
|
|
15
|
+
* PG count is ussually string due to bigint
|
|
16
|
+
*/
|
|
17
|
+
s_count: number | string;
|
|
18
|
+
}>;
|
|
19
|
+
export declare type SyncBatchInfo = Partial<{
|
|
20
|
+
from_synced: number | null;
|
|
21
|
+
to_synced: number | null;
|
|
22
|
+
end_offset: number | null;
|
|
23
|
+
}>;
|
|
24
|
+
export declare type onSyncRequestResponse = {
|
|
25
|
+
onSyncRequest?: ClientSyncInfo;
|
|
26
|
+
} | {
|
|
27
|
+
err: AnyObject | string;
|
|
28
|
+
};
|
|
29
|
+
export declare type ClientExpressData = ClientSyncInfo & {
|
|
30
|
+
data?: AnyObject[];
|
|
31
|
+
deleted?: AnyObject[];
|
|
32
|
+
};
|
|
33
|
+
export declare const syncData: (_this: PubSubManager, sync: SyncParams, clientData: ClientExpressData | undefined, source: "trigger" | "client") => Promise<void>;
|
|
34
|
+
//# sourceMappingURL=SyncReplication.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SyncReplication.d.ts","sourceRoot":"","sources":["SyncReplication.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,UAAU,EAA2B,MAAM,iBAAiB,CAAC;AACrF,OAAO,EAAgB,SAAS,EAAmB,MAAM,iBAAiB,CAAC;AAG3E,oBAAY,cAAc,GAAG,OAAO,CAAC;IACjC,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,SAAS,CAAC;IAChB;;OAEG;IACH,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;CAC5B,CAAC,CAAC;AAEH,oBAAY,cAAc,GAAG,OAAO,CAAC;IACjC,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,SAAS,CAAC;IAChB;;OAEG;IACH,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;CAC5B,CAAC,CAAA;AAEF,oBAAY,aAAa,GAAG,OAAO,CAAC;IAChC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,CAAC,CAAC;AAEH,oBAAY,qBAAqB,GAAG;IAChC,aAAa,CAAC,EAAE,cAAc,CAAA;CACjC,GAAG;IACA,GAAG,EAAE,SAAS,GAAG,MAAM,CAAC;CAC3B,CAAC;AAEF,oBAAY,iBAAiB,GAAG,cAAc,GAAG;IAC7C,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC;CACzB,CAAA;AAMD,eAAO,MAAM,QAAQ,UAAiB,aAAa,QAAQ,UAAU,cAAc,iBAAiB,GAAG,SAAS,UAAU,SAAS,GAAG,QAAQ,kBA6d7I,CAAA"}
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.syncData = void 0;
|
|
4
|
+
const PubSubManager_1 = require("./PubSubManager");
|
|
5
|
+
const prostgles_types_1 = require("prostgles-types");
|
|
6
|
+
function getNumbers(numberArr) {
|
|
7
|
+
return numberArr.filter(v => v !== null && v !== undefined && Number.isFinite(+v));
|
|
8
|
+
}
|
|
9
|
+
const syncData = async (_this, sync, clientData, source) => {
|
|
10
|
+
(0, PubSubManager_1.log)("syncData", { clientData, sync: (0, PubSubManager_1.pickKeys)(sync, ["filter", "last_synced", "lr", "is_syncing"]), source });
|
|
11
|
+
const { socket_id, channel_name, table_name, filter, table_rules, allow_delete = false, params, synced_field, id_fields = [], batch_size, wal, throttle = 0 } = sync, socket = _this.sockets[socket_id];
|
|
12
|
+
if (!socket) {
|
|
13
|
+
console.error("Orphaned socket", { sync, clientData });
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const sync_fields = [synced_field, ...id_fields.sort()], orderByAsc = sync_fields.reduce((a, v) => ({ ...a, [v]: true }), {}), orderByDesc = sync_fields.reduce((a, v) => ({ ...a, [v]: false }), {}),
|
|
17
|
+
// desc_params = { orderBy: [{ [synced_field]: false }].concat(id_fields.map(f => ({ [f]: false }) )) },
|
|
18
|
+
// asc_params = { orderBy: [synced_field].concat(id_fields) },
|
|
19
|
+
rowsIdsMatch = (a, b) => {
|
|
20
|
+
return a && b && !id_fields.find(key => (a[key]).toString() !== (b[key]).toString());
|
|
21
|
+
}, rowsFullyMatch = (a, b) => {
|
|
22
|
+
return rowsIdsMatch(a, b) && a?.[synced_field].toString() === b?.[synced_field].toString();
|
|
23
|
+
}, getServerRowInfo = async (args = {}) => {
|
|
24
|
+
const { from_synced = null, to_synced = null, offset = 0, limit } = args;
|
|
25
|
+
let _filter = { ...filter };
|
|
26
|
+
if (from_synced || to_synced) {
|
|
27
|
+
_filter[synced_field] = {
|
|
28
|
+
...(from_synced ? { $gte: from_synced } : {}),
|
|
29
|
+
...(to_synced ? { $lte: to_synced } : {})
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (_this.dbo?.[table_name]?.find === undefined || _this?.dbo?.[table_name]?.count === undefined) {
|
|
33
|
+
throw `dbo.${table_name}.find or .count are missing or not allowed`;
|
|
34
|
+
}
|
|
35
|
+
const first_rows = await _this.dbo?.[table_name]?.find?.(_filter, { orderBy: orderByAsc, select: sync_fields, limit, offset }, undefined, table_rules);
|
|
36
|
+
const last_rows = first_rows?.slice(-1);
|
|
37
|
+
// 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);
|
|
38
|
+
const count = await _this.dbo?.[table_name]?.count?.(_filter, undefined, undefined, table_rules);
|
|
39
|
+
return { s_fr: first_rows?.[0] || null, s_lr: last_rows?.[0] || null, s_count: count };
|
|
40
|
+
}, getClientRowInfo = (args = {}) => {
|
|
41
|
+
const { from_synced = null, to_synced = null, end_offset = null } = args;
|
|
42
|
+
let res = new Promise((resolve, reject) => {
|
|
43
|
+
let onSyncRequest = { from_synced, to_synced, end_offset }; //, forReal: true };
|
|
44
|
+
socket.emit(channel_name, { onSyncRequest }, (resp) => {
|
|
45
|
+
if (resp && "onSyncRequest" in resp && resp?.onSyncRequest) {
|
|
46
|
+
let c_fr = resp.onSyncRequest.c_fr, c_lr = resp.onSyncRequest.c_lr, c_count = resp.onSyncRequest.c_count;
|
|
47
|
+
// console.log(onSyncRequest, { c_fr, c_lr, c_count }, socket._user);
|
|
48
|
+
return resolve({ c_fr, c_lr, c_count });
|
|
49
|
+
}
|
|
50
|
+
else if (resp && "err" in resp && resp?.err) {
|
|
51
|
+
reject(resp.err);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
return res;
|
|
56
|
+
}, getClientData = (from_synced = 0, offset = 0) => {
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const onPullRequest = { from_synced: from_synced || 0, offset: offset || 0, limit: batch_size };
|
|
59
|
+
socket.emit(channel_name, { onPullRequest }, async (resp) => {
|
|
60
|
+
if (resp && resp.data && Array.isArray(resp.data)) {
|
|
61
|
+
// console.log({ onPullRequest, resp }, socket._user)
|
|
62
|
+
resolve(sortClientData(resp.data));
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
reject("unexpected onPullRequest response: " + JSON.stringify(resp));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
function sortClientData(data) {
|
|
70
|
+
return data.sort((a, b) => {
|
|
71
|
+
/* Order by increasing synced and ids (sorted alphabetically) */
|
|
72
|
+
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;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}, getServerData = async (from_synced = 0, offset = 0) => {
|
|
76
|
+
let _filter = {
|
|
77
|
+
...filter,
|
|
78
|
+
[synced_field]: { $gte: from_synced || 0 }
|
|
79
|
+
};
|
|
80
|
+
if (!_this?.dbo?.[table_name]?.find)
|
|
81
|
+
throw "_this?.dbo?.[table_name]?.find is missing";
|
|
82
|
+
try {
|
|
83
|
+
let res = _this?.dbo?.[table_name]?.find?.(_filter, {
|
|
84
|
+
select: params.select,
|
|
85
|
+
orderBy: orderByAsc,
|
|
86
|
+
offset: offset || 0,
|
|
87
|
+
limit: batch_size
|
|
88
|
+
}, undefined, table_rules);
|
|
89
|
+
if (!res)
|
|
90
|
+
throw "_this?.dbo?.[table_name]?.find is missing";
|
|
91
|
+
return res;
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
console.error("Sync getServerData failed: ", e);
|
|
95
|
+
throw "INTERNAL ERROR";
|
|
96
|
+
}
|
|
97
|
+
}, deleteData = async (deleted) => {
|
|
98
|
+
// console.log("deleteData deleteData deleteData " + deleted.length);
|
|
99
|
+
if (allow_delete) {
|
|
100
|
+
return Promise.all(deleted.map(async (d) => {
|
|
101
|
+
const id_filter = (0, PubSubManager_1.pickKeys)(d, id_fields);
|
|
102
|
+
try {
|
|
103
|
+
await _this.dbo[table_name].delete(id_filter, undefined, undefined, table_rules);
|
|
104
|
+
return 1;
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
console.error(e);
|
|
108
|
+
}
|
|
109
|
+
return 0;
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
console.warn("client tried to delete data without permission (allow_delete is false)");
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
},
|
|
117
|
+
/**
|
|
118
|
+
* Upserts the given client data where synced_field is higher than on server
|
|
119
|
+
*/
|
|
120
|
+
upsertData = (data, isExpress = false) => {
|
|
121
|
+
// console.log("isExpress", isExpress, data);
|
|
122
|
+
return _this.dboBuilder.getTX(async (dbTX) => {
|
|
123
|
+
const tbl = dbTX[table_name];
|
|
124
|
+
const existingData = await tbl.find({ $or: data.map(d => (0, PubSubManager_1.pickKeys)(d, id_fields)) }, {
|
|
125
|
+
select: [synced_field, ...id_fields],
|
|
126
|
+
orderBy: orderByAsc,
|
|
127
|
+
}, undefined, table_rules);
|
|
128
|
+
let inserts = data.filter(d => !existingData.find(ed => rowsIdsMatch(ed, d)));
|
|
129
|
+
let updates = data.filter(d => existingData.find(ed => rowsIdsMatch(ed, d) && +ed[synced_field] < +d[synced_field]));
|
|
130
|
+
try {
|
|
131
|
+
if (!table_rules)
|
|
132
|
+
throw "table_rules missing";
|
|
133
|
+
if (table_rules.update && updates.length) {
|
|
134
|
+
let updateData = [];
|
|
135
|
+
await Promise.all(updates.map(upd => {
|
|
136
|
+
const id_filter = (0, PubSubManager_1.pickKeys)(upd, id_fields);
|
|
137
|
+
const syncSafeFilter = { $and: [id_filter, { [synced_field]: { "<": upd[synced_field] } }] };
|
|
138
|
+
updateData.push([syncSafeFilter, (0, PubSubManager_1.omitKeys)(upd, id_fields)]);
|
|
139
|
+
}));
|
|
140
|
+
await tbl.updateBatch(updateData, { fixIssues: true }, table_rules);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
updates = [];
|
|
144
|
+
}
|
|
145
|
+
if (table_rules.insert && inserts.length) {
|
|
146
|
+
// const qs = await tbl.insert(inserts, { fixIssues: true }, null, table_rules, { returnQuery: true });
|
|
147
|
+
// console.log("inserts", qs)
|
|
148
|
+
await tbl.insert(inserts, { fixIssues: true }, undefined, table_rules);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
inserts = [];
|
|
152
|
+
}
|
|
153
|
+
return { inserts, updates };
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
console.trace(e);
|
|
157
|
+
throw e;
|
|
158
|
+
}
|
|
159
|
+
}).then(({ inserts, updates }) => {
|
|
160
|
+
(0, PubSubManager_1.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))}`);
|
|
161
|
+
return { inserted: inserts.length, updated: updates.length, total: data.length };
|
|
162
|
+
})
|
|
163
|
+
.catch(err => {
|
|
164
|
+
console.trace("Something went wrong with syncing to server: \n ->", err, data.length, id_fields);
|
|
165
|
+
return Promise.reject("Something went wrong with syncing to server: ");
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
/**
|
|
169
|
+
* Pushes the given data to client
|
|
170
|
+
* @param isSynced = true if
|
|
171
|
+
*/
|
|
172
|
+
pushData = async (data, isSynced = false, err = null) => {
|
|
173
|
+
return new Promise((resolve, reject) => {
|
|
174
|
+
socket.emit(channel_name, { data, isSynced }, (resp) => {
|
|
175
|
+
if (resp && resp.ok) {
|
|
176
|
+
// console.log("PUSHED to client: fr/lr", data[0], data[data.length - 1]);
|
|
177
|
+
resolve({ pushed: data?.length, resp });
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
reject(resp);
|
|
181
|
+
console.error("Unexpected response");
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
},
|
|
186
|
+
/**
|
|
187
|
+
* Returns the lowest synced_field between server and client by checking client and server sync data.
|
|
188
|
+
* If last rows don't match it will find an earlier matching last row and use that last matching from_synced
|
|
189
|
+
* If no rows or fully synced (c_lr and s_lr match) then returns null
|
|
190
|
+
*/
|
|
191
|
+
getLastSynced = async (clientSyncInfo) => {
|
|
192
|
+
// Get latest row info
|
|
193
|
+
const { c_fr, c_lr, c_count } = clientSyncInfo || await getClientRowInfo();
|
|
194
|
+
const { s_fr, s_lr, s_count } = await getServerRowInfo();
|
|
195
|
+
// console.log("getLastSynced", clientData, socket._user )
|
|
196
|
+
let result = null;
|
|
197
|
+
/* Nothing to sync */
|
|
198
|
+
if (!c_fr && !s_fr || rowsFullyMatch(c_lr, s_lr)) { // c_count === s_count &&
|
|
199
|
+
// sync.last_synced = null;
|
|
200
|
+
result = null;
|
|
201
|
+
/* Sync Everything */
|
|
202
|
+
}
|
|
203
|
+
else if (!rowsFullyMatch(c_fr, s_fr)) {
|
|
204
|
+
if (c_fr && s_fr) {
|
|
205
|
+
result = Math.min(c_fr[synced_field], s_fr[synced_field]);
|
|
206
|
+
}
|
|
207
|
+
else if (c_fr || s_fr) {
|
|
208
|
+
result = (c_fr || s_fr)[synced_field];
|
|
209
|
+
}
|
|
210
|
+
/* Sync from last matching synced value */
|
|
211
|
+
}
|
|
212
|
+
else if (rowsFullyMatch(c_fr, s_fr)) {
|
|
213
|
+
if (s_lr && c_lr) {
|
|
214
|
+
result = Math.min(...getNumbers([c_lr[synced_field], s_lr[synced_field]]));
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
result = Math.min(...getNumbers([c_fr[synced_field], s_fr?.[synced_field]]));
|
|
218
|
+
}
|
|
219
|
+
let min_count = Math.min(...getNumbers([c_count, s_count]));
|
|
220
|
+
let end_offset = 1; // Math.min(s_count, c_count) - 1;
|
|
221
|
+
let step = 0;
|
|
222
|
+
while (min_count > 5 && end_offset < min_count) {
|
|
223
|
+
const { c_lr = null } = await getClientRowInfo({ from_synced: 0, to_synced: result, end_offset });
|
|
224
|
+
// console.log("getLastSynced... end_offset > " + end_offset);
|
|
225
|
+
let server_row;
|
|
226
|
+
if (c_lr) {
|
|
227
|
+
let _filter = {};
|
|
228
|
+
sync_fields.map(key => {
|
|
229
|
+
_filter[key] = c_lr[key];
|
|
230
|
+
});
|
|
231
|
+
server_row = await _this?.dbo?.[table_name]?.find?.(_filter, { select: sync_fields, limit: 1 }, undefined, table_rules);
|
|
232
|
+
}
|
|
233
|
+
// if(rowsFullyMatch(c_lr, s_lr)){ //c_count === s_count &&
|
|
234
|
+
if (server_row && server_row.length) {
|
|
235
|
+
server_row = server_row[0];
|
|
236
|
+
result = +server_row[synced_field];
|
|
237
|
+
end_offset = min_count;
|
|
238
|
+
// console.log(`getLastSynced found for ${table_name} -> ${result}`);
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
end_offset += 1 + step * (step > 4 ? 2 : 1);
|
|
242
|
+
// console.log(`getLastSynced NOT found for ${table_name} -> ${result}`);
|
|
243
|
+
}
|
|
244
|
+
step++;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return result;
|
|
248
|
+
}, updateSyncLR = (data) => {
|
|
249
|
+
if (data.length) {
|
|
250
|
+
const lastRow = data[data.length - 1];
|
|
251
|
+
if (sync.lr?.[synced_field] && +sync.lr?.[synced_field] > +lastRow[synced_field]) {
|
|
252
|
+
console.error({ syncIssue: "sync.lr[synced_field] is greater than lastRow[synced_field]" });
|
|
253
|
+
}
|
|
254
|
+
sync.lr = lastRow;
|
|
255
|
+
sync.last_synced = +sync.lr?.[synced_field];
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
/**
|
|
259
|
+
* Will push pull sync between client and server from a given from_synced value
|
|
260
|
+
*/
|
|
261
|
+
syncBatch = async (from_synced) => {
|
|
262
|
+
let offset = 0, limit = batch_size, canContinue = true, min_synced = from_synced || 0, max_synced = from_synced;
|
|
263
|
+
let inserted = 0, updated = 0, pushed = 0, deleted = 0, total = 0;
|
|
264
|
+
// console.log("syncBatch", from_synced)
|
|
265
|
+
while (canContinue) {
|
|
266
|
+
let cData = await getClientData(min_synced, offset);
|
|
267
|
+
if (cData.length) {
|
|
268
|
+
let res = await upsertData(cData);
|
|
269
|
+
inserted += res.inserted;
|
|
270
|
+
updated += res.updated;
|
|
271
|
+
}
|
|
272
|
+
let sData;
|
|
273
|
+
try {
|
|
274
|
+
sData = await getServerData(min_synced, offset);
|
|
275
|
+
}
|
|
276
|
+
catch (e) {
|
|
277
|
+
console.trace("sync getServerData err", e);
|
|
278
|
+
await pushData(undefined, undefined, "Internal error. Check server logs");
|
|
279
|
+
throw " d";
|
|
280
|
+
}
|
|
281
|
+
// console.log("allow_delete", table_rules.delete);
|
|
282
|
+
if (allow_delete && table_rules?.delete) {
|
|
283
|
+
const to_delete = sData.filter(d => {
|
|
284
|
+
!cData.find(c => rowsIdsMatch(c, d));
|
|
285
|
+
});
|
|
286
|
+
await Promise.all(to_delete.map(d => {
|
|
287
|
+
deleted++;
|
|
288
|
+
return _this.dbo[table_name].delete((0, PubSubManager_1.pickKeys)(d, id_fields), {}, undefined, table_rules);
|
|
289
|
+
}));
|
|
290
|
+
sData = await getServerData(min_synced, offset);
|
|
291
|
+
}
|
|
292
|
+
let forClient = sData.filter(s => {
|
|
293
|
+
return !cData.find(c => rowsIdsMatch(c, s) &&
|
|
294
|
+
+c[synced_field] >= +s[synced_field]);
|
|
295
|
+
});
|
|
296
|
+
if (forClient.length) {
|
|
297
|
+
let res = await pushData(forClient.filter(d => !sync.wal || !sync.wal.isInHistory(d)));
|
|
298
|
+
pushed += res.pushed;
|
|
299
|
+
}
|
|
300
|
+
if (sData.length) {
|
|
301
|
+
updateSyncLR(sData);
|
|
302
|
+
total += sData.length;
|
|
303
|
+
}
|
|
304
|
+
offset += sData.length;
|
|
305
|
+
// canContinue = offset >= limit;
|
|
306
|
+
canContinue = sData.length >= limit;
|
|
307
|
+
// console.log(`sData ${sData.length} limit ${limit}`);
|
|
308
|
+
}
|
|
309
|
+
// console.log(`syncBatch ${table_name}: inserted( ${inserted} ) updated( ${updated} ) deleted( ${deleted} ) pushed( ${pushed} ) total( ${total} )`, socket._user );
|
|
310
|
+
return true;
|
|
311
|
+
};
|
|
312
|
+
if (!wal) {
|
|
313
|
+
/* Used to throttle and merge incomming updates */
|
|
314
|
+
sync.wal = new prostgles_types_1.WAL({
|
|
315
|
+
id_fields, synced_field, throttle, batch_size,
|
|
316
|
+
DEBUG_MODE: _this.dboBuilder.prostgles.opts.DEBUG_MODE,
|
|
317
|
+
onSendStart: () => {
|
|
318
|
+
sync.is_syncing = true;
|
|
319
|
+
},
|
|
320
|
+
onSend: async (data) => {
|
|
321
|
+
// console.log("WAL upsertData START", data)
|
|
322
|
+
const res = await upsertData(data, true);
|
|
323
|
+
// const max_incoming_synced = Math.max(...data.map(d => +d[synced_field]));
|
|
324
|
+
// if(Number.isFinite(max_incoming_synced) && max_incoming_synced > +sync.last_synced){
|
|
325
|
+
// sync.last_synced = max_incoming_synced;
|
|
326
|
+
// }
|
|
327
|
+
// console.log("WAL upsertData END")
|
|
328
|
+
/******** */
|
|
329
|
+
/* TO DO -> Store and push patch updates instead of full data if and where possible */
|
|
330
|
+
/******** */
|
|
331
|
+
// 1. Store successfully upserted wal items for a couple of seconds
|
|
332
|
+
// 2. When pushing data to clients check if any matching wal items exist
|
|
333
|
+
// 3. Replace text fields with matching patched data
|
|
334
|
+
return res;
|
|
335
|
+
},
|
|
336
|
+
onSendEnd: (batch) => {
|
|
337
|
+
updateSyncLR(batch);
|
|
338
|
+
sync.is_syncing = false;
|
|
339
|
+
// console.log("syncData from WAL.onSendEnd")
|
|
340
|
+
/**
|
|
341
|
+
* After all data was inserted request SyncInfo from client and sync again if necessary
|
|
342
|
+
*/
|
|
343
|
+
_this.syncData(sync, undefined, source);
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
/* Debounce sync requests */
|
|
348
|
+
if (!sync.wal)
|
|
349
|
+
throw "sync.wal missing";
|
|
350
|
+
if (!sync.wal.isSending() && sync.is_syncing) {
|
|
351
|
+
if (!_this.syncTimeout) {
|
|
352
|
+
_this.syncTimeout = setTimeout(() => {
|
|
353
|
+
_this.syncTimeout = undefined;
|
|
354
|
+
// console.log("SYNC FROM TIMEOUT")
|
|
355
|
+
_this.syncData(sync, undefined, source);
|
|
356
|
+
}, throttle);
|
|
357
|
+
}
|
|
358
|
+
// console.log("SYNC THROTTLE")
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
// console.log("syncData", clientData)
|
|
362
|
+
/**
|
|
363
|
+
* Express data sent from a client that has already been synced
|
|
364
|
+
* Add to WAL manager which will sync at the end
|
|
365
|
+
*/
|
|
366
|
+
if (clientData) {
|
|
367
|
+
if (clientData.data && Array.isArray(clientData.data) && clientData.data.length) {
|
|
368
|
+
if (!sync.wal)
|
|
369
|
+
throw "sync.wal missing";
|
|
370
|
+
sync.wal.addData(clientData.data.map(d => ({ current: d })));
|
|
371
|
+
return;
|
|
372
|
+
// await upsertData(clientData.data, true);
|
|
373
|
+
/* Not expecting this anymore. use normal db.table.delete channel */
|
|
374
|
+
}
|
|
375
|
+
else if (clientData.deleted && Array.isArray(clientData.deleted) && clientData.deleted.length) {
|
|
376
|
+
await deleteData(clientData.deleted);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
}
|
|
381
|
+
if (sync.wal.isSending())
|
|
382
|
+
return;
|
|
383
|
+
sync.is_syncing = true;
|
|
384
|
+
// from synced does not make sense. It should be sync.lr only!!!
|
|
385
|
+
let from_synced = null;
|
|
386
|
+
/** Sync was already synced */
|
|
387
|
+
if (sync.lr) {
|
|
388
|
+
const { s_lr } = await getServerRowInfo();
|
|
389
|
+
/* Make sure trigger is not firing on freshly synced data */
|
|
390
|
+
if (!rowsFullyMatch(sync.lr, s_lr)) {
|
|
391
|
+
from_synced = sync.last_synced;
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
// console.log("rowsFullyMatch")
|
|
395
|
+
}
|
|
396
|
+
// console.log(table_name, sync.lr[synced_field])
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
from_synced = await getLastSynced(clientData);
|
|
400
|
+
}
|
|
401
|
+
if (from_synced !== null) {
|
|
402
|
+
await syncBatch(from_synced);
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
// console.log("from_synced is null")
|
|
406
|
+
}
|
|
407
|
+
await pushData([], true);
|
|
408
|
+
sync.is_syncing = false;
|
|
409
|
+
// console.log(`Finished sync for ${table_name}`, socket._user);
|
|
410
|
+
};
|
|
411
|
+
exports.syncData = syncData;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { JoinInfo } from "./DboBuilder";
|
|
2
|
+
import { ALLOWED_EXTENSION, ALLOWED_CONTENT_TYPE } from "./FileManager";
|
|
3
|
+
import { DB, DBHandlerServer, Prostgles } from "./Prostgles";
|
|
4
|
+
declare type ColExtraInfo = {
|
|
5
|
+
min?: string | number;
|
|
6
|
+
max?: string | number;
|
|
7
|
+
hint?: string;
|
|
8
|
+
};
|
|
9
|
+
declare type BaseTableDefinition = {
|
|
10
|
+
dropIfExistsCascade?: boolean;
|
|
11
|
+
dropIfExists?: boolean;
|
|
12
|
+
};
|
|
13
|
+
declare type LookupTableDefinition<LANG_IDS> = {
|
|
14
|
+
isLookupTable: {
|
|
15
|
+
values: {
|
|
16
|
+
[id_value: string]: {} | {
|
|
17
|
+
[lang_id in keyof LANG_IDS]: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
declare type BaseColumn<LANG_IDS> = {
|
|
23
|
+
/**
|
|
24
|
+
* Will add these values to .getColumns() result
|
|
25
|
+
*/
|
|
26
|
+
info?: ColExtraInfo;
|
|
27
|
+
label?: string | Partial<{
|
|
28
|
+
[lang_id in keyof LANG_IDS]: string;
|
|
29
|
+
}>;
|
|
30
|
+
};
|
|
31
|
+
declare type SQLDefColumn = {
|
|
32
|
+
/**
|
|
33
|
+
* Raw sql statement used in creating/adding column
|
|
34
|
+
*/
|
|
35
|
+
sqlDefinition?: string;
|
|
36
|
+
};
|
|
37
|
+
declare type TextColDef = {
|
|
38
|
+
defaultValue?: string;
|
|
39
|
+
nullable?: boolean;
|
|
40
|
+
};
|
|
41
|
+
declare type TextColumn = TextColDef & {
|
|
42
|
+
isText: true;
|
|
43
|
+
/**
|
|
44
|
+
* Value will be trimmed before update/insert
|
|
45
|
+
*/
|
|
46
|
+
trimmed?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Value will be lower cased before update/insert
|
|
49
|
+
*/
|
|
50
|
+
lowerCased?: boolean;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Allows referencing media to this table.
|
|
54
|
+
* Requires this table to have a primary key AND a valid fileTable config
|
|
55
|
+
*/
|
|
56
|
+
declare type MediaColumn = ({
|
|
57
|
+
name: string;
|
|
58
|
+
label?: string;
|
|
59
|
+
files: "one" | "many";
|
|
60
|
+
} & ({
|
|
61
|
+
/**
|
|
62
|
+
* https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept
|
|
63
|
+
*/
|
|
64
|
+
allowedContentType?: Record<Partial<("audio/*" | "video/*" | "image/*" | "text/*" | ALLOWED_CONTENT_TYPE)>, 1>;
|
|
65
|
+
} | {
|
|
66
|
+
allowedExtensions?: Record<Partial<ALLOWED_EXTENSION>, 1>;
|
|
67
|
+
}));
|
|
68
|
+
declare type ReferencedColumn = {
|
|
69
|
+
/**
|
|
70
|
+
* Will create a lookup table that this column will reference
|
|
71
|
+
*/
|
|
72
|
+
references?: TextColDef & {
|
|
73
|
+
tableName: string;
|
|
74
|
+
/**
|
|
75
|
+
* Defaults to id
|
|
76
|
+
*/
|
|
77
|
+
columnName?: string;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
declare type JoinDef = {
|
|
81
|
+
sourceTable: string;
|
|
82
|
+
targetTable: string;
|
|
83
|
+
/**
|
|
84
|
+
* E.g.: [sourceCol: string, targetCol: string][];
|
|
85
|
+
*/
|
|
86
|
+
on: [string, string][];
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Used in specifying a join path to a table. This column name can then be used in select
|
|
90
|
+
*/
|
|
91
|
+
declare type NamedJoinColumn = {
|
|
92
|
+
label?: string;
|
|
93
|
+
joinDef: JoinDef[];
|
|
94
|
+
};
|
|
95
|
+
declare type ColumnConfig<LANG_IDS = {
|
|
96
|
+
en: 1;
|
|
97
|
+
}> = NamedJoinColumn | MediaColumn | (BaseColumn<LANG_IDS> & (SQLDefColumn | ReferencedColumn | TextColumn));
|
|
98
|
+
declare type TableDefinition<LANG_IDS> = {
|
|
99
|
+
columns: {
|
|
100
|
+
[column_name: string]: ColumnConfig<LANG_IDS>;
|
|
101
|
+
};
|
|
102
|
+
constraints?: {
|
|
103
|
+
[constraint_name: string]: string;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Similar to unique constraints but expressions are allowed inside definition
|
|
107
|
+
*/
|
|
108
|
+
replaceUniqueIndexes?: boolean;
|
|
109
|
+
indexes?: {
|
|
110
|
+
[index_name: string]: {
|
|
111
|
+
/**
|
|
112
|
+
* Overrides replaceUniqueIndexes
|
|
113
|
+
*/
|
|
114
|
+
replace?: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Causes the system to check for duplicate values in the table when the index is created (if data already exist) and each time data is added.
|
|
117
|
+
* Attempts to insert or update data which would result in duplicate entries will generate an error.
|
|
118
|
+
*/
|
|
119
|
+
unique?: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* When this option is used, PostgreSQL will build the index without taking any locks that prevent
|
|
122
|
+
* concurrent inserts, updates, or deletes on the table; whereas a standard index build locks out writes (but not reads) on the table until it's done.
|
|
123
|
+
* There are several caveats to be aware of when using this option — see Building Indexes Concurrently.
|
|
124
|
+
*/
|
|
125
|
+
concurrently?: boolean;
|
|
126
|
+
/**
|
|
127
|
+
* Table name
|
|
128
|
+
*/
|
|
129
|
+
/**
|
|
130
|
+
* Raw sql statement used excluding parentheses. e.g.: column_name
|
|
131
|
+
*/
|
|
132
|
+
definition: string;
|
|
133
|
+
/**
|
|
134
|
+
* The name of the index method to be used.
|
|
135
|
+
* Choices are btree, hash, gist, and gin. The default method is btree.
|
|
136
|
+
*/
|
|
137
|
+
using?: "btree" | "hash" | "gist" | "gin";
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Helper utility to create lookup tables for TEXT columns
|
|
143
|
+
*/
|
|
144
|
+
export declare type TableConfig<LANG_IDS = {
|
|
145
|
+
en: 1;
|
|
146
|
+
}> = {
|
|
147
|
+
[table_name: string]: BaseTableDefinition & (TableDefinition<LANG_IDS> | LookupTableDefinition<LANG_IDS>);
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Will be run between initSQL and fileTable
|
|
151
|
+
*/
|
|
152
|
+
export default class TableConfigurator {
|
|
153
|
+
config?: TableConfig;
|
|
154
|
+
get dbo(): DBHandlerServer;
|
|
155
|
+
get db(): DB;
|
|
156
|
+
prostgles: Prostgles;
|
|
157
|
+
constructor(prostgles: Prostgles);
|
|
158
|
+
getColumnConfig: (tableName: string, colName: string) => ColumnConfig | undefined;
|
|
159
|
+
getColInfo: (params: {
|
|
160
|
+
col: string;
|
|
161
|
+
table: string;
|
|
162
|
+
lang?: string;
|
|
163
|
+
}) => (ColExtraInfo & {
|
|
164
|
+
label?: string;
|
|
165
|
+
}) | undefined;
|
|
166
|
+
checkColVal: (params: {
|
|
167
|
+
col: string;
|
|
168
|
+
table: string;
|
|
169
|
+
value: any;
|
|
170
|
+
}) => void;
|
|
171
|
+
getJoinInfo: (sourceTable: string, targetTable: string) => JoinInfo | undefined;
|
|
172
|
+
init(): Promise<void>;
|
|
173
|
+
}
|
|
174
|
+
export {};
|
|
175
|
+
//# sourceMappingURL=TableConfig.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TableConfig.d.ts","sourceRoot":"","sources":["TableConfig.ts"],"names":[],"mappings":"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"}
|