meadow-integration 1.0.13 → 1.0.15
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "meadow-integration",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"description": "Meadow Data Integration",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mdwint": "source/cli/Meadow-Integration-CLI-Run.js"
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"fable": "^3.1.63",
|
|
41
41
|
"fable-serviceproviderbase": "^3.0.19",
|
|
42
|
-
"meadow": "^2.0.
|
|
42
|
+
"meadow": "^2.0.33",
|
|
43
43
|
"meadow-connection-mssql": "^1.0.16",
|
|
44
44
|
"meadow-connection-mysql": "^1.0.14",
|
|
45
45
|
"orator": "^6.0.4",
|
|
@@ -297,6 +297,10 @@ class MeadowSyncEntityInitial extends libFableServiceProviderBase
|
|
|
297
297
|
(pDeleteSyncError) =>
|
|
298
298
|
{
|
|
299
299
|
this.fable.log.info(`Delete sync complete for ${this.EntitySchema.TableName} (${tmpDeletedCount} deleted records processed).`);
|
|
300
|
+
if (this.syncResults)
|
|
301
|
+
{
|
|
302
|
+
this.syncResults.Deleted = tmpDeletedCount;
|
|
303
|
+
}
|
|
300
304
|
return fCallback();
|
|
301
305
|
});
|
|
302
306
|
});
|
|
@@ -599,6 +603,21 @@ class MeadowSyncEntityInitial extends libFableServiceProviderBase
|
|
|
599
603
|
{
|
|
600
604
|
this.fable.log.error(`Error returned URL Partial .. this may not be an error: ${pDownloadError}`);
|
|
601
605
|
}
|
|
606
|
+
|
|
607
|
+
// Store sync results on the entity so callers can inspect the breakdown
|
|
608
|
+
this.syncResults = (
|
|
609
|
+
{
|
|
610
|
+
Created: tmpRecordsCreated,
|
|
611
|
+
Skipped: tmpRecordsSkipped,
|
|
612
|
+
Errors: tmpRecordsErrored,
|
|
613
|
+
Deleted: 0,
|
|
614
|
+
ServerRecordCount: tmpSyncState.Server.RecordCount,
|
|
615
|
+
LocalRecordCount: tmpSyncState.Local.RecordCount,
|
|
616
|
+
ServerMaxID: tmpSyncState.Server.MaxIDEntity,
|
|
617
|
+
LocalMaxID: tmpSyncState.Local.MaxIDEntity,
|
|
618
|
+
EstimatedNew: tmpSyncState.EstimatedRecordCount
|
|
619
|
+
});
|
|
620
|
+
|
|
602
621
|
fStageComplete();
|
|
603
622
|
});
|
|
604
623
|
},
|
|
@@ -46,6 +46,15 @@ class MeadowSyncEntityOngoing extends libFableServiceProviderBase
|
|
|
46
46
|
// pull all records in the range from the server instead of subdividing further.
|
|
47
47
|
this.BisectMinRangeSize = this.options.BisectMinRangeSize || 1000;
|
|
48
48
|
|
|
49
|
+
// Tolerance window in milliseconds for cross-database timestamp precision differences.
|
|
50
|
+
// MySQL DATETIME stores whole seconds, MSSQL DATETIME rounds to ~3.33ms increments,
|
|
51
|
+
// PostgreSQL TIMESTAMP stores microseconds, and SQLite stores as TEXT. When comparing
|
|
52
|
+
// timestamps across systems, the maximum rounding error is 1000ms (MySQL second-level
|
|
53
|
+
// truncation). Default 1000ms covers all supported provider combinations.
|
|
54
|
+
this.DateTimePrecisionMS = (typeof(this.options.DateTimePrecisionMS) === 'number')
|
|
55
|
+
? this.options.DateTimePrecisionMS
|
|
56
|
+
: 1000;
|
|
57
|
+
|
|
49
58
|
this.Meadow = false;
|
|
50
59
|
|
|
51
60
|
this.operation = new libMeadowOperation(this.fable);
|
|
@@ -150,10 +159,31 @@ class MeadowSyncEntityOngoing extends libFableServiceProviderBase
|
|
|
150
159
|
|
|
151
160
|
// ---- REST / Local query helpers ----
|
|
152
161
|
|
|
162
|
+
// Normalize a date value to a UTC dayJS instance.
|
|
163
|
+
//
|
|
164
|
+
// Local dates stored in SQLite are in UTC but formatted without a timezone
|
|
165
|
+
// indicator (e.g. "2022-05-10 22:50:26.000"). When the driver or ORM wraps
|
|
166
|
+
// them in a JavaScript Date object, JS interprets them as local time, shifting
|
|
167
|
+
// the value by the machine's UTC offset. To recover the original naive UTC
|
|
168
|
+
// time, we format through local time (which undoes the offset) then re-parse
|
|
169
|
+
// as UTC. Server dates already carry a "Z" suffix and are parsed correctly.
|
|
170
|
+
_normalizeDateUTC(pDate)
|
|
171
|
+
{
|
|
172
|
+
if (typeof(pDate) === 'string')
|
|
173
|
+
{
|
|
174
|
+
// String dates — strip any trailing Z or timezone so dayJS.utc() treats as UTC
|
|
175
|
+
return this.fable.Dates.dayJS.utc(pDate);
|
|
176
|
+
}
|
|
177
|
+
// Date objects (from SQLite via ORM) — format as local time string to recover
|
|
178
|
+
// the naive stored value, then re-parse as UTC
|
|
179
|
+
let tmpNaiveStr = this.fable.Dates.dayJS(pDate).format('YYYY-MM-DD HH:mm:ss.SSS');
|
|
180
|
+
return this.fable.Dates.dayJS.utc(tmpNaiveStr);
|
|
181
|
+
}
|
|
182
|
+
|
|
153
183
|
// Format a date value for use in Meadow REST filter expressions (FBV).
|
|
154
184
|
_formatDateForFilter(pDate)
|
|
155
185
|
{
|
|
156
|
-
return this.
|
|
186
|
+
return this._normalizeDateUTC(pDate).format('YYYY-MM-DDTHH:mm:ss.SSS');
|
|
157
187
|
}
|
|
158
188
|
|
|
159
189
|
// Get a count from the remote server, optionally filtered.
|
|
@@ -413,11 +443,15 @@ class MeadowSyncEntityOngoing extends libFableServiceProviderBase
|
|
|
413
443
|
() =>
|
|
414
444
|
{
|
|
415
445
|
tmpSyncedCount++;
|
|
416
|
-
|
|
446
|
+
// Use setImmediate to yield the event loop and prevent
|
|
447
|
+
// stack overflow when SQLite callbacks complete synchronously
|
|
448
|
+
return setImmediate(fRecordDone);
|
|
417
449
|
});
|
|
418
450
|
},
|
|
419
451
|
(pUpsertError) =>
|
|
420
452
|
{
|
|
453
|
+
// Increment per-page progress so the UI reflects sync in real-time
|
|
454
|
+
this._incrementProgress(pRecords.length);
|
|
421
455
|
tmpOffset += this.PageSize;
|
|
422
456
|
if (pRecords.length < this.PageSize)
|
|
423
457
|
{
|
|
@@ -425,7 +459,8 @@ class MeadowSyncEntityOngoing extends libFableServiceProviderBase
|
|
|
425
459
|
return fCallback(null, tmpSyncedCount);
|
|
426
460
|
}
|
|
427
461
|
this.fable.log.info(`${this.EntitySchema.TableName}: pulled ${tmpSyncedCount} of ~${tmpRecordCap} records...`);
|
|
428
|
-
|
|
462
|
+
// Use setImmediate to break the recursive call chain across pages
|
|
463
|
+
return setImmediate(fFetchPage);
|
|
429
464
|
});
|
|
430
465
|
});
|
|
431
466
|
};
|
|
@@ -433,6 +468,16 @@ class MeadowSyncEntityOngoing extends libFableServiceProviderBase
|
|
|
433
468
|
fFetchPage();
|
|
434
469
|
}
|
|
435
470
|
|
|
471
|
+
// Increment the FullSync progress tracker by pCount records checked/synced.
|
|
472
|
+
_incrementProgress(pCount)
|
|
473
|
+
{
|
|
474
|
+
let tmpTracker = this.operation.progressTrackers[`FullSync-${this.EntitySchema.TableName}`];
|
|
475
|
+
if (tmpTracker && pCount > 0)
|
|
476
|
+
{
|
|
477
|
+
tmpTracker.CurrentCount = Math.min(tmpTracker.CurrentCount + pCount, tmpTracker.TotalCount);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
436
481
|
// ---- Bisection logic ----
|
|
437
482
|
|
|
438
483
|
// Compare a local ID range against the server. If counts or date boundaries
|
|
@@ -470,6 +515,7 @@ class MeadowSyncEntityOngoing extends libFableServiceProviderBase
|
|
|
470
515
|
if (!this._hasUpdateDate)
|
|
471
516
|
{
|
|
472
517
|
// No UpdateDate column -- counts match, assume in sync
|
|
518
|
+
this._incrementProgress(pServerCount);
|
|
473
519
|
return fCallback();
|
|
474
520
|
}
|
|
475
521
|
|
|
@@ -493,11 +539,12 @@ class MeadowSyncEntityOngoing extends libFableServiceProviderBase
|
|
|
493
539
|
}
|
|
494
540
|
|
|
495
541
|
const tmpServerMaxDate = pServerMaxRecords[0].UpdateDate;
|
|
496
|
-
const tmpMaxDateDiff = Math.abs(this.
|
|
542
|
+
const tmpMaxDateDiff = Math.abs(this._normalizeDateUTC(pLocalMaxDate).diff(this._normalizeDateUTC(tmpServerMaxDate)));
|
|
497
543
|
|
|
498
|
-
if (tmpMaxDateDiff
|
|
544
|
+
if (tmpMaxDateDiff <= this.DateTimePrecisionMS)
|
|
499
545
|
{
|
|
500
546
|
// Max dates match and counts match -- this range is in sync
|
|
547
|
+
this._incrementProgress(pServerCount);
|
|
501
548
|
return fCallback();
|
|
502
549
|
}
|
|
503
550
|
|
|
@@ -562,6 +609,7 @@ class MeadowSyncEntityOngoing extends libFableServiceProviderBase
|
|
|
562
609
|
{
|
|
563
610
|
this.fable.log.info(`${this.EntitySchema.TableName}: synced ${pSyncedCount} records in range ${pMinID}-${pMaxID}`);
|
|
564
611
|
}
|
|
612
|
+
// Per-page progress is now tracked inside _pullServerRecords()
|
|
565
613
|
return fCallback();
|
|
566
614
|
});
|
|
567
615
|
}
|
|
@@ -890,6 +938,13 @@ class MeadowSyncEntityOngoing extends libFableServiceProviderBase
|
|
|
890
938
|
});
|
|
891
939
|
},
|
|
892
940
|
|
|
941
|
+
// Create a progress tracker so callers (e.g. data-cloner UI) can see Total/Synced
|
|
942
|
+
(fStageComplete) =>
|
|
943
|
+
{
|
|
944
|
+
this.operation.createProgressTracker(tmpSyncState.Server.RecordCount, `FullSync-${this.EntitySchema.TableName}`);
|
|
945
|
+
return fStageComplete();
|
|
946
|
+
},
|
|
947
|
+
|
|
893
948
|
// ---- Stage 3: UpdateDate-based fast sync ----
|
|
894
949
|
// If we have UpdateDate, compare server record count up to our local
|
|
895
950
|
// max UpdateDate. If it matches local count, existing records are in
|
|
@@ -926,6 +981,7 @@ class MeadowSyncEntityOngoing extends libFableServiceProviderBase
|
|
|
926
981
|
// Record counts match up to our max UpdateDate -- existing records are in sync.
|
|
927
982
|
this.fable.log.info(`${this.EntitySchema.TableName}: counts match up to local max UpdateDate; existing records appear in sync.`);
|
|
928
983
|
tmpSyncState.ExistingRecordsInSync = true;
|
|
984
|
+
this._incrementProgress(pServerCountBefore);
|
|
929
985
|
}
|
|
930
986
|
else
|
|
931
987
|
{
|
|
@@ -951,6 +1007,7 @@ class MeadowSyncEntityOngoing extends libFableServiceProviderBase
|
|
|
951
1007
|
if (pServerCountAfter < 1)
|
|
952
1008
|
{
|
|
953
1009
|
tmpSyncState.UpdateDateSyncDone = true;
|
|
1010
|
+
// No new records -- nothing additional to count
|
|
954
1011
|
return fStageComplete();
|
|
955
1012
|
}
|
|
956
1013
|
|
|
@@ -965,6 +1022,7 @@ class MeadowSyncEntityOngoing extends libFableServiceProviderBase
|
|
|
965
1022
|
{
|
|
966
1023
|
this.fable.log.info(`${this.EntitySchema.TableName}: pulled ${pSyncedCount} new/modified records via UpdateDate.`);
|
|
967
1024
|
}
|
|
1025
|
+
// Per-page progress is now tracked inside _pullServerRecords()
|
|
968
1026
|
tmpSyncState.UpdateDateSyncDone = true;
|
|
969
1027
|
return fStageComplete();
|
|
970
1028
|
});
|
|
@@ -1047,6 +1105,7 @@ class MeadowSyncEntityOngoing extends libFableServiceProviderBase
|
|
|
1047
1105
|
{
|
|
1048
1106
|
this.fable.log.info(`${this.EntitySchema.TableName}: pulled ${pSyncedCount} new records by ID.`);
|
|
1049
1107
|
}
|
|
1108
|
+
// Per-page progress is now tracked inside _pullServerRecords()
|
|
1050
1109
|
return fStageComplete();
|
|
1051
1110
|
});
|
|
1052
1111
|
},
|
|
@@ -1058,6 +1117,13 @@ class MeadowSyncEntityOngoing extends libFableServiceProviderBase
|
|
|
1058
1117
|
this.fable.log.error(`Error performing ongoing sync ${this.EntitySchema.TableName}: ${pError}`, { Error: pError });
|
|
1059
1118
|
}
|
|
1060
1119
|
|
|
1120
|
+
// Mark progress tracker as complete so the UI shows the correct totals
|
|
1121
|
+
let tmpTracker = this.operation.progressTrackers[`FullSync-${this.EntitySchema.TableName}`];
|
|
1122
|
+
if (tmpTracker)
|
|
1123
|
+
{
|
|
1124
|
+
tmpTracker.CurrentCount = tmpTracker.TotalCount;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1061
1127
|
this.fable.log.info(`${this.EntitySchema.TableName}: ongoing sync complete.`);
|
|
1062
1128
|
|
|
1063
1129
|
if (this.SyncDeletedRecords)
|
|
@@ -65,6 +65,18 @@ class MeadowSync extends libFableServiceProviderBase
|
|
|
65
65
|
this.MaxRecordsPerEntity = parseInt(this.options.MaxRecordsPerEntity, 10) || 0;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
// Tolerance window in milliseconds for cross-database timestamp precision differences.
|
|
69
|
+
// Passed through to Ongoing sync entities for bisection date comparison.
|
|
70
|
+
this.DateTimePrecisionMS = 1000;
|
|
71
|
+
if (this.fable.ProgramConfiguration.hasOwnProperty('DateTimePrecisionMS'))
|
|
72
|
+
{
|
|
73
|
+
this.DateTimePrecisionMS = parseInt(this.fable.ProgramConfiguration.DateTimePrecisionMS, 10) || 1000;
|
|
74
|
+
}
|
|
75
|
+
else if (this.options.hasOwnProperty('DateTimePrecisionMS'))
|
|
76
|
+
{
|
|
77
|
+
this.DateTimePrecisionMS = parseInt(this.options.DateTimePrecisionMS, 10) || 1000;
|
|
78
|
+
}
|
|
79
|
+
|
|
68
80
|
this.MeadowSchema = false;
|
|
69
81
|
this.MeadowSchemaTableList = false;
|
|
70
82
|
|
|
@@ -101,6 +113,7 @@ class MeadowSync extends libFableServiceProviderBase
|
|
|
101
113
|
PageSize: this.options.PageSize || 100,
|
|
102
114
|
SyncDeletedRecords: this.SyncDeletedRecords,
|
|
103
115
|
MaxRecordsPerEntity: this.MaxRecordsPerEntity,
|
|
116
|
+
DateTimePrecisionMS: this.DateTimePrecisionMS,
|
|
104
117
|
};
|
|
105
118
|
|
|
106
119
|
let tmpSyncEntity;
|