meadow-integration 1.0.25 → 1.0.27
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/package.json
CHANGED
|
@@ -211,11 +211,21 @@ class MeadowSyncEntityComparisonOnly extends libMeadowSyncEntityOngoing
|
|
|
211
211
|
|
|
212
212
|
this.fable.log.info(`${this.EntitySchema.TableName}: compare subdividing range ${pMinID}-${pMaxID} at ID ${tmpMidID} (depth ${pDepth})`);
|
|
213
213
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
214
|
+
// Use setImmediate to break the recursive call chain. With synchronous
|
|
215
|
+
// database providers (e.g. better-sqlite3), the entire bisection tree
|
|
216
|
+
// would otherwise execute in a single call stack, exhausting it for
|
|
217
|
+
// large ID ranges.
|
|
218
|
+
setImmediate(() =>
|
|
219
|
+
{
|
|
220
|
+
this._compareRange(pMinID, tmpMidID, pDepth + 1, pReport,
|
|
221
|
+
() =>
|
|
222
|
+
{
|
|
223
|
+
setImmediate(() =>
|
|
224
|
+
{
|
|
225
|
+
this._compareRange(tmpMidID + 1, pMaxID, pDepth + 1, pReport, fCallback);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
});
|
|
219
229
|
}
|
|
220
230
|
|
|
221
231
|
_syncInternal(fCallback)
|
|
@@ -122,11 +122,171 @@ class MeadowSyncEntityOngoingEventualConsistency extends libMeadowSyncEntityOngo
|
|
|
122
122
|
this.fable.log.info(`${this.EntitySchema.TableName}: subdividing range ${pMinID}-${pMaxID} at ID ${tmpMidID} (depth ${pDepth}, upper half first)`);
|
|
123
123
|
|
|
124
124
|
// Upper half first (reversed from standard bisection)
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
// Use setImmediate to break the recursive call chain for synchronous
|
|
126
|
+
// database providers (e.g. better-sqlite3).
|
|
127
|
+
setImmediate(() =>
|
|
128
|
+
{
|
|
129
|
+
this._bisectRangeWithTimeBudget(tmpMidID + 1, pMaxID, pDepth + 1, pStartTime, pTimeLimit,
|
|
130
|
+
() =>
|
|
131
|
+
{
|
|
132
|
+
// Then lower half (if time remains -- checked at entry of next call)
|
|
133
|
+
setImmediate(() =>
|
|
134
|
+
{
|
|
135
|
+
this._bisectRangeWithTimeBudget(pMinID, tmpMidID, pDepth + 1, pStartTime, pTimeLimit, fCallback);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Override deleted record sync with a time-budgeted version.
|
|
142
|
+
// The base syncDeletedRecords() walks ALL deleted records on the server
|
|
143
|
+
// with no limit, which defeats the purpose of eventual consistency.
|
|
144
|
+
// This version processes pages until BackSyncTimeLimit is exhausted.
|
|
145
|
+
syncDeletedRecords(fCallback)
|
|
146
|
+
{
|
|
147
|
+
const tmpDeletedColumn = this.EntitySchema.Columns.find((c) => c.Column == 'Deleted');
|
|
148
|
+
if (!tmpDeletedColumn)
|
|
149
|
+
{
|
|
150
|
+
this.fable.log.info(`No Deleted column for ${this.EntitySchema.TableName}; skipping delete sync.`);
|
|
151
|
+
return fCallback();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.fable.log.info(`Checking for deleted records on server for ${this.EntitySchema.TableName} (time-budgeted)...`);
|
|
155
|
+
|
|
156
|
+
this.fable.MeadowCloneRestClient.getJSON(this._appendDeletedQueryString(`${this.EntitySchema.TableName}s/Count/FilteredTo/FBV~Deleted~EQ~1`),
|
|
157
|
+
(pError, pResponse, pBody) =>
|
|
127
158
|
{
|
|
128
|
-
|
|
129
|
-
|
|
159
|
+
if (pError || !pBody || !pBody.hasOwnProperty('Count'))
|
|
160
|
+
{
|
|
161
|
+
this.fable.log.warn(`Could not get deleted record count for ${this.EntitySchema.TableName}; skipping delete sync.`);
|
|
162
|
+
return fCallback();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const tmpDeletedCount = pBody.Count;
|
|
166
|
+
if (tmpDeletedCount < 1)
|
|
167
|
+
{
|
|
168
|
+
this.fable.log.info(`No deleted records on server for ${this.EntitySchema.TableName}.`);
|
|
169
|
+
return fCallback();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
this.fable.log.info(`Found ${tmpDeletedCount} deleted records on server for ${this.EntitySchema.TableName}; syncing deletions with ${this.BackSyncTimeLimit}ms budget...`);
|
|
173
|
+
|
|
174
|
+
let tmpDeleteCap = (this.MaxRecordsPerEntity > 0)
|
|
175
|
+
? Math.min(tmpDeletedCount, this.MaxRecordsPerEntity)
|
|
176
|
+
: tmpDeletedCount;
|
|
177
|
+
const tmpDeleteURLPartials = [];
|
|
178
|
+
for (let i = 0; i < tmpDeleteCap; i += this.PageSize)
|
|
179
|
+
{
|
|
180
|
+
tmpDeleteURLPartials.push(this._appendDeletedQueryString(`${this.EntitySchema.TableName}s/FilteredTo/FBV~Deleted~EQ~1~FSF~${this.DefaultIdentifier}~ASC~ASC/${i}/${this.PageSize}`));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const tmpStartTime = Date.now();
|
|
184
|
+
let tmpProcessed = 0;
|
|
185
|
+
let tmpTimeBudgetExhausted = false;
|
|
186
|
+
|
|
187
|
+
this.fable.Utility.eachLimit(tmpDeleteURLPartials, 1,
|
|
188
|
+
(pURLPartial, fPageComplete) =>
|
|
189
|
+
{
|
|
190
|
+
// Check time budget before each page
|
|
191
|
+
if (Date.now() - tmpStartTime >= this.BackSyncTimeLimit)
|
|
192
|
+
{
|
|
193
|
+
tmpTimeBudgetExhausted = true;
|
|
194
|
+
return fPageComplete();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
this.fable.MeadowCloneRestClient.getJSON(pURLPartial,
|
|
198
|
+
(pDownloadError, pResponse, pBody) =>
|
|
199
|
+
{
|
|
200
|
+
if (pDownloadError || !pBody || !Array.isArray(pBody) || pBody.length < 1)
|
|
201
|
+
{
|
|
202
|
+
return fPageComplete();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.fable.Utility.eachLimit(pBody, 5,
|
|
206
|
+
(pEntityRecord, fRecordComplete) =>
|
|
207
|
+
{
|
|
208
|
+
const tmpRecordID = pEntityRecord[this.DefaultIdentifier];
|
|
209
|
+
if (!tmpRecordID || tmpRecordID < 1)
|
|
210
|
+
{
|
|
211
|
+
return fRecordComplete();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const tmpQuery = this.Meadow.query;
|
|
215
|
+
tmpQuery.addFilter(this.DefaultIdentifier, tmpRecordID);
|
|
216
|
+
tmpQuery.setDisableDeleteTracking(true);
|
|
217
|
+
|
|
218
|
+
this.Meadow.doRead(tmpQuery,
|
|
219
|
+
(pReadError, pQuery, pRecord) =>
|
|
220
|
+
{
|
|
221
|
+
if (pReadError || !pRecord)
|
|
222
|
+
{
|
|
223
|
+
const tmpRecordToCommit = this.marshalRecord(pEntityRecord);
|
|
224
|
+
|
|
225
|
+
const tmpCreateQuery = this.Meadow.query.addRecord(tmpRecordToCommit);
|
|
226
|
+
tmpCreateQuery.setDisableAutoIdentity(true);
|
|
227
|
+
tmpCreateQuery.setDisableAutoDateStamp(true);
|
|
228
|
+
tmpCreateQuery.setDisableAutoUserStamp(true);
|
|
229
|
+
tmpCreateQuery.setDisableDeleteTracking(true);
|
|
230
|
+
tmpCreateQuery.AllowIdentityInsert = true;
|
|
231
|
+
|
|
232
|
+
this.Meadow.doCreate(tmpCreateQuery,
|
|
233
|
+
(pCreateError) =>
|
|
234
|
+
{
|
|
235
|
+
if (pCreateError)
|
|
236
|
+
{
|
|
237
|
+
this.log.error(`Error creating deleted record ${this.EntitySchema.TableName} ID ${tmpRecordID}: ${pCreateError}`);
|
|
238
|
+
}
|
|
239
|
+
tmpProcessed++;
|
|
240
|
+
return fRecordComplete();
|
|
241
|
+
});
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (pRecord.Deleted == 1)
|
|
246
|
+
{
|
|
247
|
+
tmpProcessed++;
|
|
248
|
+
return fRecordComplete();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const tmpRecordToCommit = this.marshalRecord(pEntityRecord);
|
|
252
|
+
|
|
253
|
+
const tmpUpdateQuery = this.Meadow.query.addRecord(tmpRecordToCommit);
|
|
254
|
+
tmpUpdateQuery.setDisableAutoIdentity(true);
|
|
255
|
+
tmpUpdateQuery.setDisableAutoDateStamp(true);
|
|
256
|
+
tmpUpdateQuery.setDisableAutoUserStamp(true);
|
|
257
|
+
tmpUpdateQuery.setDisableDeleteTracking(true);
|
|
258
|
+
|
|
259
|
+
this.Meadow.doUpdate(tmpUpdateQuery,
|
|
260
|
+
(pUpdateError) =>
|
|
261
|
+
{
|
|
262
|
+
if (pUpdateError)
|
|
263
|
+
{
|
|
264
|
+
this.log.error(`Error marking record as deleted ${this.EntitySchema.TableName} ID ${tmpRecordID}: ${pUpdateError}`);
|
|
265
|
+
}
|
|
266
|
+
tmpProcessed++;
|
|
267
|
+
return fRecordComplete();
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
},
|
|
271
|
+
(pRecordSyncError) =>
|
|
272
|
+
{
|
|
273
|
+
return fPageComplete();
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
},
|
|
277
|
+
(pDeleteSyncError) =>
|
|
278
|
+
{
|
|
279
|
+
const tmpElapsed = Date.now() - tmpStartTime;
|
|
280
|
+
if (tmpTimeBudgetExhausted)
|
|
281
|
+
{
|
|
282
|
+
this.fable.log.info(`Delete sync time budget exhausted for ${this.EntitySchema.TableName} after ${tmpElapsed}ms (${tmpProcessed} of ${tmpDeletedCount} deleted records processed).`);
|
|
283
|
+
}
|
|
284
|
+
else
|
|
285
|
+
{
|
|
286
|
+
this.fable.log.info(`Delete sync complete for ${this.EntitySchema.TableName} (${tmpProcessed} of ${tmpDeletedCount} deleted records processed in ${tmpElapsed}ms).`);
|
|
287
|
+
}
|
|
288
|
+
return fCallback();
|
|
289
|
+
});
|
|
130
290
|
});
|
|
131
291
|
}
|
|
132
292
|
|