meadow-integration 1.0.6 → 1.0.7
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
|
@@ -39,6 +39,7 @@ class MeadowSyncEntityInitial extends libFableServiceProviderBase
|
|
|
39
39
|
|
|
40
40
|
this.DefaultIdentifier = this.EntitySchema.MeadowSchema.DefaultIdentifier;
|
|
41
41
|
this.PageSize = this.options.PageSize || 100;
|
|
42
|
+
this.SyncDeletedRecords = this.options.SyncDeletedRecords || false;
|
|
42
43
|
|
|
43
44
|
this.Meadow = false;
|
|
44
45
|
|
|
@@ -139,6 +140,136 @@ class MeadowSyncEntityInitial extends libFableServiceProviderBase
|
|
|
139
140
|
return tmpRecordToCommit;
|
|
140
141
|
}
|
|
141
142
|
|
|
143
|
+
syncDeletedRecords(fCallback)
|
|
144
|
+
{
|
|
145
|
+
const tmpDeletedColumn = this.EntitySchema.Columns.find((c) => c.Column == 'Deleted');
|
|
146
|
+
if (!tmpDeletedColumn)
|
|
147
|
+
{
|
|
148
|
+
this.fable.log.info(`No Deleted column for ${this.EntitySchema.TableName}; skipping delete sync.`);
|
|
149
|
+
return fCallback();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
this.fable.log.info(`Checking for deleted records on server for ${this.EntitySchema.TableName}...`);
|
|
153
|
+
|
|
154
|
+
// Get the count of deleted records from the server.
|
|
155
|
+
// The explicit FBV~Deleted~EQ~1 filter overrides foxhound's automatic Deleted=0 filter.
|
|
156
|
+
this.fable.MeadowCloneRestClient.getJSON(`${this.EntitySchema.TableName}s/Count/FilteredTo/FBV~Deleted~EQ~1`,
|
|
157
|
+
(pError, pResponse, pBody) =>
|
|
158
|
+
{
|
|
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...`);
|
|
173
|
+
|
|
174
|
+
// Generate paginated URLs for deleted records
|
|
175
|
+
const tmpDeleteURLPartials = [];
|
|
176
|
+
for (let i = 0; i < tmpDeletedCount; i += this.PageSize)
|
|
177
|
+
{
|
|
178
|
+
tmpDeleteURLPartials.push(`${this.EntitySchema.TableName}s/FilteredTo/FBV~Deleted~EQ~1~FSF~${this.DefaultIdentifier}~ASC~ASC/${i}/${this.PageSize}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
this.fable.Utility.eachLimit(tmpDeleteURLPartials, 1,
|
|
182
|
+
(pURLPartial, fPageComplete) =>
|
|
183
|
+
{
|
|
184
|
+
this.fable.MeadowCloneRestClient.getJSON(pURLPartial,
|
|
185
|
+
(pDownloadError, pResponse, pBody) =>
|
|
186
|
+
{
|
|
187
|
+
if (pDownloadError || !pBody || !Array.isArray(pBody) || pBody.length < 1)
|
|
188
|
+
{
|
|
189
|
+
return fPageComplete();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
this.fable.Utility.eachLimit(pBody, 5,
|
|
193
|
+
(pEntityRecord, fRecordComplete) =>
|
|
194
|
+
{
|
|
195
|
+
const tmpRecordID = pEntityRecord[this.DefaultIdentifier];
|
|
196
|
+
if (!tmpRecordID || tmpRecordID < 1)
|
|
197
|
+
{
|
|
198
|
+
return fRecordComplete();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Read local record with delete tracking disabled so we can see all records
|
|
202
|
+
const tmpQuery = this.Meadow.query;
|
|
203
|
+
tmpQuery.addFilter(this.DefaultIdentifier, tmpRecordID);
|
|
204
|
+
tmpQuery.setDisableDeleteTracking(true);
|
|
205
|
+
|
|
206
|
+
this.Meadow.doRead(tmpQuery,
|
|
207
|
+
(pReadError, pQuery, pRecord) =>
|
|
208
|
+
{
|
|
209
|
+
if (pReadError || !pRecord)
|
|
210
|
+
{
|
|
211
|
+
// Record doesn't exist locally -- create it as deleted
|
|
212
|
+
const tmpRecordToCommit = this.marshalRecord(pEntityRecord);
|
|
213
|
+
|
|
214
|
+
const tmpCreateQuery = this.Meadow.query.addRecord(tmpRecordToCommit);
|
|
215
|
+
tmpCreateQuery.setDisableAutoIdentity(true);
|
|
216
|
+
tmpCreateQuery.setDisableAutoDateStamp(true);
|
|
217
|
+
tmpCreateQuery.setDisableAutoUserStamp(true);
|
|
218
|
+
tmpCreateQuery.setDisableDeleteTracking(true);
|
|
219
|
+
tmpCreateQuery.AllowIdentityInsert = true;
|
|
220
|
+
|
|
221
|
+
this.Meadow.doCreate(tmpCreateQuery,
|
|
222
|
+
(pCreateError) =>
|
|
223
|
+
{
|
|
224
|
+
if (pCreateError)
|
|
225
|
+
{
|
|
226
|
+
this.log.error(`Error creating deleted record ${this.EntitySchema.TableName} ID ${tmpRecordID}: ${pCreateError}`);
|
|
227
|
+
}
|
|
228
|
+
return fRecordComplete();
|
|
229
|
+
});
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (pRecord.Deleted == 1)
|
|
234
|
+
{
|
|
235
|
+
// Already marked deleted locally
|
|
236
|
+
return fRecordComplete();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Record exists locally but is not deleted -- update it
|
|
240
|
+
const tmpRecordToCommit = this.marshalRecord(pEntityRecord);
|
|
241
|
+
|
|
242
|
+
const tmpUpdateQuery = this.Meadow.query.addRecord(tmpRecordToCommit);
|
|
243
|
+
tmpUpdateQuery.setDisableAutoIdentity(true);
|
|
244
|
+
tmpUpdateQuery.setDisableAutoDateStamp(true);
|
|
245
|
+
tmpUpdateQuery.setDisableAutoUserStamp(true);
|
|
246
|
+
tmpUpdateQuery.setDisableDeleteTracking(true);
|
|
247
|
+
|
|
248
|
+
this.Meadow.doUpdate(tmpUpdateQuery,
|
|
249
|
+
(pUpdateError) =>
|
|
250
|
+
{
|
|
251
|
+
if (pUpdateError)
|
|
252
|
+
{
|
|
253
|
+
this.log.error(`Error marking record as deleted ${this.EntitySchema.TableName} ID ${tmpRecordID}: ${pUpdateError}`);
|
|
254
|
+
}
|
|
255
|
+
return fRecordComplete();
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
},
|
|
259
|
+
(pRecordSyncError) =>
|
|
260
|
+
{
|
|
261
|
+
return fPageComplete();
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
},
|
|
265
|
+
(pDeleteSyncError) =>
|
|
266
|
+
{
|
|
267
|
+
this.fable.log.info(`Delete sync complete for ${this.EntitySchema.TableName} (${tmpDeletedCount} deleted records processed).`);
|
|
268
|
+
return fCallback();
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
142
273
|
sync(fCallback)
|
|
143
274
|
{
|
|
144
275
|
this.operation.createTimeStamp('EntityInitialSync');
|
|
@@ -356,7 +487,11 @@ class MeadowSyncEntityInitial extends libFableServiceProviderBase
|
|
|
356
487
|
if (pError)
|
|
357
488
|
{
|
|
358
489
|
this.fable.log.error(`Error performing sync ${this.EntitySchema.TableName}: ${pError}`, { Error: pError });
|
|
359
|
-
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (this.SyncDeletedRecords)
|
|
493
|
+
{
|
|
494
|
+
return this.syncDeletedRecords(() => { return fCallback(); });
|
|
360
495
|
}
|
|
361
496
|
|
|
362
497
|
return fCallback();
|
|
@@ -39,6 +39,7 @@ class MeadowSyncEntityOngoing extends libFableServiceProviderBase
|
|
|
39
39
|
|
|
40
40
|
this.DefaultIdentifier = this.EntitySchema.MeadowSchema.DefaultIdentifier;
|
|
41
41
|
this.PageSize = this.options.PageSize || 100;
|
|
42
|
+
this.SyncDeletedRecords = this.options.SyncDeletedRecords || false;
|
|
42
43
|
|
|
43
44
|
this.Meadow = false;
|
|
44
45
|
|
|
@@ -132,6 +133,136 @@ class MeadowSyncEntityOngoing extends libFableServiceProviderBase
|
|
|
132
133
|
return tmpRecordToCommit;
|
|
133
134
|
}
|
|
134
135
|
|
|
136
|
+
syncDeletedRecords(fCallback)
|
|
137
|
+
{
|
|
138
|
+
const tmpDeletedColumn = this.EntitySchema.Columns.find((c) => c.Column == 'Deleted');
|
|
139
|
+
if (!tmpDeletedColumn)
|
|
140
|
+
{
|
|
141
|
+
this.fable.log.info(`No Deleted column for ${this.EntitySchema.TableName}; skipping delete sync.`);
|
|
142
|
+
return fCallback();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
this.fable.log.info(`Checking for deleted records on server for ${this.EntitySchema.TableName}...`);
|
|
146
|
+
|
|
147
|
+
// Get the count of deleted records from the server.
|
|
148
|
+
// The explicit FBV~Deleted~EQ~1 filter overrides foxhound's automatic Deleted=0 filter.
|
|
149
|
+
this.fable.MeadowCloneRestClient.getJSON(`${this.EntitySchema.TableName}s/Count/FilteredTo/FBV~Deleted~EQ~1`,
|
|
150
|
+
(pError, pResponse, pBody) =>
|
|
151
|
+
{
|
|
152
|
+
if (pError || !pBody || !pBody.hasOwnProperty('Count'))
|
|
153
|
+
{
|
|
154
|
+
this.fable.log.warn(`Could not get deleted record count for ${this.EntitySchema.TableName}; skipping delete sync.`);
|
|
155
|
+
return fCallback();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const tmpDeletedCount = pBody.Count;
|
|
159
|
+
if (tmpDeletedCount < 1)
|
|
160
|
+
{
|
|
161
|
+
this.fable.log.info(`No deleted records on server for ${this.EntitySchema.TableName}.`);
|
|
162
|
+
return fCallback();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
this.fable.log.info(`Found ${tmpDeletedCount} deleted records on server for ${this.EntitySchema.TableName}; syncing deletions...`);
|
|
166
|
+
|
|
167
|
+
// Generate paginated URLs for deleted records
|
|
168
|
+
const tmpDeleteURLPartials = [];
|
|
169
|
+
for (let i = 0; i < tmpDeletedCount; i += this.PageSize)
|
|
170
|
+
{
|
|
171
|
+
tmpDeleteURLPartials.push(`${this.EntitySchema.TableName}s/FilteredTo/FBV~Deleted~EQ~1~FSF~${this.DefaultIdentifier}~ASC~ASC/${i}/${this.PageSize}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
this.fable.Utility.eachLimit(tmpDeleteURLPartials, 1,
|
|
175
|
+
(pURLPartial, fPageComplete) =>
|
|
176
|
+
{
|
|
177
|
+
this.fable.MeadowCloneRestClient.getJSON(pURLPartial,
|
|
178
|
+
(pDownloadError, pResponse, pBody) =>
|
|
179
|
+
{
|
|
180
|
+
if (pDownloadError || !pBody || !Array.isArray(pBody) || pBody.length < 1)
|
|
181
|
+
{
|
|
182
|
+
return fPageComplete();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this.fable.Utility.eachLimit(pBody, 5,
|
|
186
|
+
(pEntityRecord, fRecordComplete) =>
|
|
187
|
+
{
|
|
188
|
+
const tmpRecordID = pEntityRecord[this.DefaultIdentifier];
|
|
189
|
+
if (!tmpRecordID || tmpRecordID < 1)
|
|
190
|
+
{
|
|
191
|
+
return fRecordComplete();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Read local record with delete tracking disabled so we can see all records
|
|
195
|
+
const tmpQuery = this.Meadow.query;
|
|
196
|
+
tmpQuery.addFilter(this.DefaultIdentifier, tmpRecordID);
|
|
197
|
+
tmpQuery.setDisableDeleteTracking(true);
|
|
198
|
+
|
|
199
|
+
this.Meadow.doRead(tmpQuery,
|
|
200
|
+
(pReadError, pQuery, pRecord) =>
|
|
201
|
+
{
|
|
202
|
+
if (pReadError || !pRecord)
|
|
203
|
+
{
|
|
204
|
+
// Record doesn't exist locally -- create it as deleted
|
|
205
|
+
const tmpRecordToCommit = this.marshalRecord(pEntityRecord);
|
|
206
|
+
|
|
207
|
+
const tmpCreateQuery = this.Meadow.query.addRecord(tmpRecordToCommit);
|
|
208
|
+
tmpCreateQuery.setDisableAutoIdentity(true);
|
|
209
|
+
tmpCreateQuery.setDisableAutoDateStamp(true);
|
|
210
|
+
tmpCreateQuery.setDisableAutoUserStamp(true);
|
|
211
|
+
tmpCreateQuery.setDisableDeleteTracking(true);
|
|
212
|
+
tmpCreateQuery.AllowIdentityInsert = true;
|
|
213
|
+
|
|
214
|
+
this.Meadow.doCreate(tmpCreateQuery,
|
|
215
|
+
(pCreateError) =>
|
|
216
|
+
{
|
|
217
|
+
if (pCreateError)
|
|
218
|
+
{
|
|
219
|
+
this.log.error(`Error creating deleted record ${this.EntitySchema.TableName} ID ${tmpRecordID}: ${pCreateError}`);
|
|
220
|
+
}
|
|
221
|
+
return fRecordComplete();
|
|
222
|
+
});
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (pRecord.Deleted == 1)
|
|
227
|
+
{
|
|
228
|
+
// Already marked deleted locally
|
|
229
|
+
return fRecordComplete();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Record exists locally but is not deleted -- update it
|
|
233
|
+
const tmpRecordToCommit = this.marshalRecord(pEntityRecord);
|
|
234
|
+
|
|
235
|
+
const tmpUpdateQuery = this.Meadow.query.addRecord(tmpRecordToCommit);
|
|
236
|
+
tmpUpdateQuery.setDisableAutoIdentity(true);
|
|
237
|
+
tmpUpdateQuery.setDisableAutoDateStamp(true);
|
|
238
|
+
tmpUpdateQuery.setDisableAutoUserStamp(true);
|
|
239
|
+
tmpUpdateQuery.setDisableDeleteTracking(true);
|
|
240
|
+
|
|
241
|
+
this.Meadow.doUpdate(tmpUpdateQuery,
|
|
242
|
+
(pUpdateError) =>
|
|
243
|
+
{
|
|
244
|
+
if (pUpdateError)
|
|
245
|
+
{
|
|
246
|
+
this.log.error(`Error marking record as deleted ${this.EntitySchema.TableName} ID ${tmpRecordID}: ${pUpdateError}`);
|
|
247
|
+
}
|
|
248
|
+
return fRecordComplete();
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
},
|
|
252
|
+
(pRecordSyncError) =>
|
|
253
|
+
{
|
|
254
|
+
return fPageComplete();
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
},
|
|
258
|
+
(pDeleteSyncError) =>
|
|
259
|
+
{
|
|
260
|
+
this.fable.log.info(`Delete sync complete for ${this.EntitySchema.TableName} (${tmpDeletedCount} deleted records processed).`);
|
|
261
|
+
return fCallback();
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
135
266
|
addSyncAnticipateEntry(tmpSyncState, tmpAnticipate)
|
|
136
267
|
{
|
|
137
268
|
tmpAnticipate.anticipate(
|
|
@@ -446,7 +577,11 @@ class MeadowSyncEntityOngoing extends libFableServiceProviderBase
|
|
|
446
577
|
if (pError)
|
|
447
578
|
{
|
|
448
579
|
this.fable.log.error(`Error performing Update sync ${this.EntitySchema.TableName}: ${pError}`, { Error: pError });
|
|
449
|
-
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (this.SyncDeletedRecords)
|
|
583
|
+
{
|
|
584
|
+
return this.syncDeletedRecords(() => { return fCallback(); });
|
|
450
585
|
}
|
|
451
586
|
|
|
452
587
|
return fCallback();
|
|
@@ -43,6 +43,17 @@ class MeadowSync extends libFableServiceProviderBase
|
|
|
43
43
|
this.SyncEntityOptions = JSON.parse(JSON.stringify(this.options.SyncEntityOptions));
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
// When true, after syncing active records, also sync records marked Deleted=1 on the source.
|
|
47
|
+
this.SyncDeletedRecords = false;
|
|
48
|
+
if (this.fable.ProgramConfiguration.hasOwnProperty('SyncDeletedRecords'))
|
|
49
|
+
{
|
|
50
|
+
this.SyncDeletedRecords = !!this.fable.ProgramConfiguration.SyncDeletedRecords;
|
|
51
|
+
}
|
|
52
|
+
else if (this.options.hasOwnProperty('SyncDeletedRecords'))
|
|
53
|
+
{
|
|
54
|
+
this.SyncDeletedRecords = !!this.options.SyncDeletedRecords;
|
|
55
|
+
}
|
|
56
|
+
|
|
46
57
|
this.MeadowSchema = false;
|
|
47
58
|
this.MeadowSchemaTableList = false;
|
|
48
59
|
|
|
@@ -70,6 +81,7 @@ class MeadowSync extends libFableServiceProviderBase
|
|
|
70
81
|
MeadowEntitySchema: tmpEntitySchema,
|
|
71
82
|
ConnectionPool: this.options.ConnectionPool,
|
|
72
83
|
PageSize: this.options.PageSize || 100,
|
|
84
|
+
SyncDeletedRecords: this.SyncDeletedRecords,
|
|
73
85
|
};
|
|
74
86
|
|
|
75
87
|
let tmpSyncEntity;
|