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.13",
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.30",
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.fable.Dates.dayJS.utc(pDate).format('YYYY-MM-DDTHH:mm:ss.SSS');
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
- return fRecordDone();
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
- return fFetchPage();
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.fable.Dates.dayJS.utc(pLocalMaxDate).diff(this.fable.Dates.dayJS.utc(tmpServerMaxDate)));
542
+ const tmpMaxDateDiff = Math.abs(this._normalizeDateUTC(pLocalMaxDate).diff(this._normalizeDateUTC(tmpServerMaxDate)));
497
543
 
498
- if (tmpMaxDateDiff < 5)
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;