retold-data-service 2.0.37 → 2.0.39

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": "retold-data-service",
3
- "version": "2.0.37",
3
+ "version": "2.0.39",
4
4
  "description": "Serve up a whole model!",
5
5
  "main": "source/Retold-Data-Service.js",
6
6
  "bin": {
@@ -73,8 +73,8 @@
73
73
  "meadow": "^2.0.33",
74
74
  "meadow-connection-mysql": "^1.0.14",
75
75
  "meadow-endpoints": "^4.0.15",
76
- "meadow-integration": "^1.0.30",
77
- "meadow-migrationmanager": "^0.0.9",
76
+ "meadow-integration": "^1.0.32",
77
+ "meadow-migrationmanager": "^0.0.10",
78
78
  "orator": "^6.0.4",
79
79
  "orator-http-proxy": "^1.0.5",
80
80
  "orator-serviceserver-restify": "^2.0.10",
@@ -1018,12 +1018,19 @@ class RetoldDataServiceDataCloner extends libFableServiceProviderBase
1018
1018
  let tmpTracker = tmpSyncEntity.operation.progressTrackers[`FullSync-${tmpTableName}`];
1019
1019
  if (tmpTracker)
1020
1020
  {
1021
- tmpProgress.Total = tmpTracker.TotalCount || 0;
1021
+ // Clamp both to >= 0: Total can be estimated from
1022
+ // Server.RecordCount - Local.RecordCount, which is
1023
+ // negative when local has extra records (e.g. after
1024
+ // server-side deletes). A negative total in the
1025
+ // progress report is nonsensical.
1026
+ tmpProgress.Total = Math.max(tmpTracker.TotalCount || 0, 0);
1022
1027
  tmpProgress.Synced = Math.max(tmpTracker.CurrentCount || 0, 0);
1023
1028
  }
1024
1029
  }
1025
1030
 
1026
1031
  // Read per-record breakdown from the sync entity
1032
+ let tmpEntityErrors = 0;
1033
+ let tmpEntitySkipped = 0;
1027
1034
  if (tmpSyncEntity && tmpSyncEntity.syncResults)
1028
1035
  {
1029
1036
  let tmpResults = tmpSyncEntity.syncResults;
@@ -1033,12 +1040,12 @@ class RetoldDataServiceDataCloner extends libFableServiceProviderBase
1033
1040
  tmpProgress.Deleted = tmpResults.Deleted || 0;
1034
1041
  tmpProgress.ServerTotal = tmpResults.ServerRecordCount || 0;
1035
1042
  tmpProgress.LocalCountBefore = tmpResults.LocalRecordCount || 0;
1043
+ tmpEntityErrors = tmpResults.Errors || 0;
1044
+ tmpEntitySkipped = tmpResults.Skipped || 0;
1036
1045
  }
1037
1046
 
1038
1047
  let tmpRESTErrors = this._cloneState.SyncRESTErrors[tmpTableName] || 0;
1039
- tmpProgress.Errors = tmpRESTErrors;
1040
-
1041
- let tmpMissing = tmpProgress.Total - tmpProgress.Synced;
1048
+ tmpProgress.Errors = tmpRESTErrors + tmpEntityErrors;
1042
1049
 
1043
1050
  if (pError)
1044
1051
  {
@@ -1048,25 +1055,35 @@ class RetoldDataServiceDataCloner extends libFableServiceProviderBase
1048
1055
  this.logSyncEvent('TableError', `Sync [${tmpTableName}] — error: ${pError}`,
1049
1056
  { Table: tmpTableName, Total: tmpProgress.Total, Synced: tmpProgress.Synced, Error: `${pError}` });
1050
1057
  }
1051
- else if (tmpRESTErrors > 0)
1052
- {
1053
- tmpProgress.Status = 'Error';
1054
- tmpProgress.ErrorMessage = `${tmpRESTErrors} REST error(s) during sync`;
1055
- this.fable.log.warn(`Data Cloner: Sync [${tmpTableName}] — completed with ${tmpRESTErrors} REST error(s). ${tmpProgress.Synced}/${tmpProgress.Total} records synced.`);
1056
- this.logSyncEvent('TableError', `Sync [${tmpTableName}] — ${tmpRESTErrors} REST error(s).`,
1057
- { Table: tmpTableName, Total: tmpProgress.Total, Synced: tmpProgress.Synced, RESTErrors: tmpRESTErrors });
1058
- }
1059
- else if (tmpProgress.Total > 0 && tmpMissing > 0)
1058
+ else if (tmpRESTErrors > 0 || tmpEntityErrors > 0)
1060
1059
  {
1061
- tmpProgress.Status = 'Partial';
1062
- tmpProgress.Skipped = tmpMissing;
1063
- this.fable.log.warn(`Data Cloner: Sync [${tmpTableName}] — partial. ${tmpProgress.Synced}/${tmpProgress.Total} records synced, ${tmpMissing} skipped (GUID conflicts or other errors).`);
1064
- this.logSyncEvent('TablePartial', `Sync [${tmpTableName}] partial. ${tmpMissing} skipped.`,
1065
- { Table: tmpTableName, Total: tmpProgress.Total, Synced: tmpProgress.Synced, Skipped: tmpMissing });
1060
+ // Only treat the sync as Partial/Error when the sync
1061
+ // entity actually reported errors (REST fetch errors or
1062
+ // per-record commit errors). A count-mismatch between
1063
+ // estimated Total and actual Synced is not itself an
1064
+ // error pre-count estimates can be stale (records
1065
+ // were updated rather than added, local already has
1066
+ // records past the server's max ID, etc.), and the
1067
+ // sync code correctly downloads nothing in those
1068
+ // cases. Reporting "N skipped" for pure estimate
1069
+ // drift is noisy and misleading.
1070
+ let tmpPartialMessage = (tmpRESTErrors > 0 && tmpEntityErrors > 0)
1071
+ ? `${tmpRESTErrors} REST error(s) and ${tmpEntityErrors} record error(s) during sync`
1072
+ : (tmpRESTErrors > 0)
1073
+ ? `${tmpRESTErrors} REST error(s) during sync`
1074
+ : `${tmpEntityErrors} record error(s) during sync`;
1075
+ tmpProgress.Status = (tmpRESTErrors > 0) ? 'Error' : 'Partial';
1076
+ tmpProgress.ErrorMessage = tmpPartialMessage;
1077
+ tmpProgress.Skipped = tmpEntitySkipped;
1078
+ this.fable.log.warn(`Data Cloner: Sync [${tmpTableName}] — ${tmpPartialMessage}. ${tmpProgress.Synced}/${tmpProgress.Total} records synced.`);
1079
+ this.logSyncEvent(tmpProgress.Status === 'Error' ? 'TableError' : 'TablePartial',
1080
+ `Sync [${tmpTableName}] — ${tmpPartialMessage}.`,
1081
+ { Table: tmpTableName, Total: tmpProgress.Total, Synced: tmpProgress.Synced, RESTErrors: tmpRESTErrors, RecordErrors: tmpEntityErrors });
1066
1082
  }
1067
1083
  else
1068
1084
  {
1069
1085
  tmpProgress.Status = 'Complete';
1086
+ tmpProgress.Skipped = tmpEntitySkipped;
1070
1087
  this.fable.log.info(`Data Cloner: Sync [${tmpTableName}] — complete. ${tmpProgress.Synced}/${tmpProgress.Total} records synced.`);
1071
1088
  this.logSyncEvent('TableComplete', `Sync [${tmpTableName}] — complete. ${tmpProgress.Synced}/${tmpProgress.Total} records.`,
1072
1089
  { Table: tmpTableName, Total: tmpProgress.Total, Synced: tmpProgress.Synced });
@@ -295,6 +295,12 @@ class DataClonerProvider extends libPictProvider
295
295
  {
296
296
  document.getElementById('solrSecure').checked = tmpSolrSecure === 'true';
297
297
  }
298
+ let tmpMssqlLegacyPagination = localStorage.getItem('dataCloner_mssqlLegacyPagination');
299
+ if (tmpMssqlLegacyPagination !== null)
300
+ {
301
+ let tmpEl = document.getElementById('mssqlLegacyPagination');
302
+ if (tmpEl) tmpEl.checked = tmpMssqlLegacyPagination === 'true';
303
+ }
298
304
  // Restore advanced ID pagination checkbox
299
305
  let tmpAdvancedIDPagination = localStorage.getItem('dataCloner_syncAdvancedIDPagination');
300
306
  if (tmpAdvancedIDPagination !== null)
@@ -351,6 +357,16 @@ class DataClonerProvider extends libPictProvider
351
357
  });
352
358
  }
353
359
 
360
+ // Persist MSSQL legacy pagination checkbox
361
+ let tmpMssqlLegacyPaginationEl = document.getElementById('mssqlLegacyPagination');
362
+ if (tmpMssqlLegacyPaginationEl)
363
+ {
364
+ tmpMssqlLegacyPaginationEl.addEventListener('change', function()
365
+ {
366
+ localStorage.setItem('dataCloner_mssqlLegacyPagination', this.checked);
367
+ });
368
+ }
369
+
354
370
  // Persist advanced ID pagination checkbox
355
371
  let tmpAdvancedIDPaginationEl = document.getElementById('syncAdvancedIDPagination');
356
372
  if (tmpAdvancedIDPaginationEl)
@@ -48,6 +48,11 @@ class DataClonerConnectionView extends libPictView
48
48
  tmpConfig.password = document.getElementById('mssqlPassword').value;
49
49
  tmpConfig.database = document.getElementById('mssqlDatabase').value.trim();
50
50
  tmpConfig.connectionLimit = parseInt(document.getElementById('mssqlConnectionLimit').value, 10) || 20;
51
+ // Use ROW_NUMBER() pagination instead of OFFSET/FETCH for
52
+ // SQL Server 2008 R2 / 2012 or databases whose compatibility
53
+ // level is < 110 (the parser rejects OFFSET/FETCH syntax
54
+ // otherwise).
55
+ tmpConfig.LegacyPagination = document.getElementById('mssqlLegacyPagination').checked;
51
56
  }
52
57
  else if (tmpProvider === 'PostgreSQL')
53
58
  {
@@ -291,6 +296,10 @@ module.exports.default_configuration =
291
296
  </div>
292
297
  <div></div>
293
298
  </div>
299
+ <div style="margin-top:8px">
300
+ <input type="checkbox" id="mssqlLegacyPagination">
301
+ <label for="mssqlLegacyPagination" title="Enable for SQL Server 2008 R2 / 2012 or databases at compatibility_level &lt; 110. Uses ROW_NUMBER() pagination instead of OFFSET/FETCH.">Legacy pagination (SQL Server &lt; 2012 / compat level &lt; 110)</label>
302
+ </div>
294
303
  </div>
295
304
 
296
305
  <!-- PostgreSQL Config -->
@@ -37,6 +37,10 @@ class DataClonerExportView extends libPictView
37
37
  tmpDbConfig.password = document.getElementById('mssqlPassword').value;
38
38
  tmpDbConfig.database = document.getElementById('mssqlDatabase').value.trim();
39
39
  tmpDbConfig.connectionLimit = parseInt(document.getElementById('mssqlConnectionLimit').value, 10) || 20;
40
+ if (document.getElementById('mssqlLegacyPagination').checked)
41
+ {
42
+ tmpDbConfig.LegacyPagination = true;
43
+ }
40
44
  }
41
45
  else if (tmpProvider === 'PostgreSQL')
42
46
  {
@@ -156,6 +160,10 @@ class DataClonerExportView extends libPictView
156
160
  tmpConfig.Destination.MSSQL.password = document.getElementById('mssqlPassword').value || '';
157
161
  tmpConfig.Destination.MSSQL.database = document.getElementById('mssqlDatabase').value.trim() || 'meadow';
158
162
  tmpConfig.Destination.MSSQL.ConnectionPoolLimit = parseInt(document.getElementById('mssqlConnectionLimit').value, 10) || 20;
163
+ if (document.getElementById('mssqlLegacyPagination').checked)
164
+ {
165
+ tmpConfig.Destination.MSSQL.LegacyPagination = true;
166
+ }
159
167
  }
160
168
  else
161
169
  {
@@ -5072,6 +5072,11 @@
5072
5072
  if (tmpSolrSecure !== null) {
5073
5073
  document.getElementById('solrSecure').checked = tmpSolrSecure === 'true';
5074
5074
  }
5075
+ let tmpMssqlLegacyPagination = localStorage.getItem('dataCloner_mssqlLegacyPagination');
5076
+ if (tmpMssqlLegacyPagination !== null) {
5077
+ let tmpEl = document.getElementById('mssqlLegacyPagination');
5078
+ if (tmpEl) tmpEl.checked = tmpMssqlLegacyPagination === 'true';
5079
+ }
5075
5080
  // Restore advanced ID pagination checkbox
5076
5081
  let tmpAdvancedIDPagination = localStorage.getItem('dataCloner_syncAdvancedIDPagination');
5077
5082
  if (tmpAdvancedIDPagination !== null) {
@@ -5119,6 +5124,14 @@
5119
5124
  });
5120
5125
  }
5121
5126
 
5127
+ // Persist MSSQL legacy pagination checkbox
5128
+ let tmpMssqlLegacyPaginationEl = document.getElementById('mssqlLegacyPagination');
5129
+ if (tmpMssqlLegacyPaginationEl) {
5130
+ tmpMssqlLegacyPaginationEl.addEventListener('change', function () {
5131
+ localStorage.setItem('dataCloner_mssqlLegacyPagination', this.checked);
5132
+ });
5133
+ }
5134
+
5122
5135
  // Persist advanced ID pagination checkbox
5123
5136
  let tmpAdvancedIDPaginationEl = document.getElementById('syncAdvancedIDPagination');
5124
5137
  if (tmpAdvancedIDPaginationEl) {
@@ -5851,6 +5864,11 @@
5851
5864
  tmpConfig.password = document.getElementById('mssqlPassword').value;
5852
5865
  tmpConfig.database = document.getElementById('mssqlDatabase').value.trim();
5853
5866
  tmpConfig.connectionLimit = parseInt(document.getElementById('mssqlConnectionLimit').value, 10) || 20;
5867
+ // Use ROW_NUMBER() pagination instead of OFFSET/FETCH for
5868
+ // SQL Server 2008 R2 / 2012 or databases whose compatibility
5869
+ // level is < 110 (the parser rejects OFFSET/FETCH syntax
5870
+ // otherwise).
5871
+ tmpConfig.LegacyPagination = document.getElementById('mssqlLegacyPagination').checked;
5854
5872
  } else if (tmpProvider === 'PostgreSQL') {
5855
5873
  tmpConfig.host = document.getElementById('postgresqlHost').value.trim() || '127.0.0.1';
5856
5874
  tmpConfig.port = parseInt(document.getElementById('postgresqlPort').value, 10) || 5432;
@@ -6044,6 +6062,10 @@
6044
6062
  </div>
6045
6063
  <div></div>
6046
6064
  </div>
6065
+ <div style="margin-top:8px">
6066
+ <input type="checkbox" id="mssqlLegacyPagination">
6067
+ <label for="mssqlLegacyPagination" title="Enable for SQL Server 2008 R2 / 2012 or databases at compatibility_level &lt; 110. Uses ROW_NUMBER() pagination instead of OFFSET/FETCH.">Legacy pagination (SQL Server &lt; 2012 / compat level &lt; 110)</label>
6068
+ </div>
6047
6069
  </div>
6048
6070
 
6049
6071
  <!-- PostgreSQL Config -->
@@ -6362,6 +6384,9 @@
6362
6384
  tmpDbConfig.password = document.getElementById('mssqlPassword').value;
6363
6385
  tmpDbConfig.database = document.getElementById('mssqlDatabase').value.trim();
6364
6386
  tmpDbConfig.connectionLimit = parseInt(document.getElementById('mssqlConnectionLimit').value, 10) || 20;
6387
+ if (document.getElementById('mssqlLegacyPagination').checked) {
6388
+ tmpDbConfig.LegacyPagination = true;
6389
+ }
6365
6390
  } else if (tmpProvider === 'PostgreSQL') {
6366
6391
  tmpDbConfig.host = document.getElementById('postgresqlHost').value.trim() || '127.0.0.1';
6367
6392
  tmpDbConfig.port = parseInt(document.getElementById('postgresqlPort').value, 10) || 5432;
@@ -6465,6 +6490,9 @@
6465
6490
  tmpConfig.Destination.MSSQL.password = document.getElementById('mssqlPassword').value || '';
6466
6491
  tmpConfig.Destination.MSSQL.database = document.getElementById('mssqlDatabase').value.trim() || 'meadow';
6467
6492
  tmpConfig.Destination.MSSQL.ConnectionPoolLimit = parseInt(document.getElementById('mssqlConnectionLimit').value, 10) || 20;
6493
+ if (document.getElementById('mssqlLegacyPagination').checked) {
6494
+ tmpConfig.Destination.MSSQL.LegacyPagination = true;
6495
+ }
6468
6496
  } else {
6469
6497
  // Default to MySQL placeholder for unsupported providers
6470
6498
  tmpConfig.Destination.Provider = 'MySQL';