retold-data-service 2.0.16 → 2.0.18
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/.claude/launch.json +2 -2
- package/.quackage.json +19 -0
- package/package.json +13 -6
- package/source/services/data-cloner/DataCloner-Command-Sync.js +83 -50
- package/source/services/data-cloner/DataCloner-Command-WebUI.js +27 -10
- package/source/services/data-cloner/Retold-Data-Service-DataCloner.js +281 -4
- package/source/services/data-cloner/pict-app/Pict-Application-DataCloner-Configuration.json +9 -0
- package/source/services/data-cloner/pict-app/Pict-Application-DataCloner.js +102 -0
- package/source/services/data-cloner/pict-app/Pict-DataCloner-Bundle.js +6 -0
- package/source/services/data-cloner/pict-app/providers/Pict-Provider-DataCloner.js +998 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Connection.js +407 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Deploy.js +126 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Export.js +483 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Layout.js +390 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Schema.js +241 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Session.js +268 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Sync.js +575 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-ViewData.js +176 -0
- package/source/services/data-cloner/web/data-cloner.js +7952 -0
- package/source/services/data-cloner/web/data-cloner.js.map +1 -0
- package/source/services/data-cloner/web/data-cloner.min.js +2 -0
- package/source/services/data-cloner/web/data-cloner.min.js.map +1 -0
- package/source/services/data-cloner/web/index.html +17 -0
- package/test/DataCloner-Integration_tests.js +1205 -0
- package/test/DataCloner-Puppeteer_tests.js +502 -0
- package/test/integration-report.json +311 -0
- package/test/run-integration-tests.js +501 -0
- package/source/services/data-cloner/data-cloner-web.html +0 -2706
package/.claude/launch.json
CHANGED
package/.quackage.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"GulpfileConfiguration":
|
|
3
|
+
{
|
|
4
|
+
"EntrypointInputSourceFile": "{~Data:AppData.CWD~}/source/services/data-cloner/pict-app/Pict-DataCloner-Bundle.js",
|
|
5
|
+
"LibraryObjectName": "dataCloner",
|
|
6
|
+
"LibraryOutputFolder": "{~Data:AppData.CWD~}/source/services/data-cloner/web/",
|
|
7
|
+
"LibraryUniminifiedFileName": "data-cloner.{~Data:Record.BuildFileLabel~}js",
|
|
8
|
+
"LibraryMinifiedFileName": "data-cloner.{~Data:Record.BuildFileLabel~}min.js"
|
|
9
|
+
},
|
|
10
|
+
"GulpExecutions":
|
|
11
|
+
[
|
|
12
|
+
{
|
|
13
|
+
"Hash": "default",
|
|
14
|
+
"Name": "Default standard build.",
|
|
15
|
+
"BuildFileLabel": "",
|
|
16
|
+
"BrowsersListRC": "since 2022"
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
package/package.json
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "retold-data-service",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.18",
|
|
4
4
|
"description": "Serve up a whole model!",
|
|
5
5
|
"main": "source/Retold-Data-Service.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"retold-data-service-clone": "bin/retold-data-service-clone.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"start": "node
|
|
10
|
+
"start": "node bin/retold-data-service-clone.js",
|
|
11
11
|
"coverage": "npx quack coverage",
|
|
12
12
|
"test": "npx quack test",
|
|
13
13
|
"build": "npx quack build",
|
|
14
|
+
"prepublishOnly": "npx quack build",
|
|
14
15
|
"build-test-model": "cd test && npx stricture -i model/ddl/BookStore.ddl",
|
|
15
16
|
"docker-dev-build": "docker build ./ -f Dockerfile_LUXURYCode -t retold-data-service-image:local",
|
|
16
17
|
"docker-dev-run": "docker run -it -d --name retold-data-service-dev -p 44444:8080 -p 43306:3306 -p 48086:8086 -v \"$PWD/.config:/home/coder/.config\" -v \"$PWD:/home/coder/retold-data-service\" -u \"$(id -u):$(id -g)\" -e \"DOCKER_USER=$USER\" retold-data-service-image:local",
|
|
@@ -18,9 +19,13 @@
|
|
|
18
19
|
"docker-service-build": "docker build ./ -f Dockerfile_Service -t retold-data-service-server-image:local",
|
|
19
20
|
"docker-service-run-test": "docker run -it --init -d --name retold-data-service -p 8086:8086 -p 43306:3306 -v \"$(pwd):/retold-data-service:z\" -u \"$(id -u):$(id -g)\" retold-data-service-server-image:local",
|
|
20
21
|
"docker-service-run": "docker run -it --init -d --name retold-data-service -p 8086:8086 -p 43306:3306 -v \"$(pwd):/retold-data-service:z\" retold-data-service-server-image:local",
|
|
21
|
-
"docker-service-shell": "docker exec -it retold-data-service /bin/bash"
|
|
22
|
+
"docker-service-shell": "docker exec -it retold-data-service /bin/bash",
|
|
23
|
+
"test:integration": "node test/run-integration-tests.js",
|
|
24
|
+
"test:integration:no-browser": "node test/run-integration-tests.js --skip-puppeteer",
|
|
25
|
+
"test:all": "npx quack test && node test/run-integration-tests.js --skip-puppeteer"
|
|
22
26
|
},
|
|
23
27
|
"mocha": {
|
|
28
|
+
"spec": "test/RetoldDataService_tests.js",
|
|
24
29
|
"diff": true,
|
|
25
30
|
"extension": [
|
|
26
31
|
"js"
|
|
@@ -55,6 +60,7 @@
|
|
|
55
60
|
"homepage": "https://github.com/stevenvelozo/retold-data-service",
|
|
56
61
|
"devDependencies": {
|
|
57
62
|
"meadow-connection-sqlite": "^1.0.18",
|
|
63
|
+
"puppeteer": "^24.38.0",
|
|
58
64
|
"quackage": "^1.0.63",
|
|
59
65
|
"stricture": "^4.0.2",
|
|
60
66
|
"supertest": "^7.2.2"
|
|
@@ -63,16 +69,17 @@
|
|
|
63
69
|
"bibliograph": "^0.1.4",
|
|
64
70
|
"fable": "^3.1.63",
|
|
65
71
|
"fable-serviceproviderbase": "^3.0.19",
|
|
66
|
-
"meadow": "^2.0.
|
|
72
|
+
"meadow": "^2.0.33",
|
|
67
73
|
"meadow-connection-mysql": "^1.0.14",
|
|
68
74
|
"meadow-endpoints": "^4.0.14",
|
|
75
|
+
"meadow-integration": "^1.0.14",
|
|
76
|
+
"meadow-migrationmanager": "^0.0.4",
|
|
69
77
|
"orator": "^6.0.4",
|
|
70
78
|
"orator-http-proxy": "^1.0.5",
|
|
71
79
|
"orator-serviceserver-restify": "^2.0.9",
|
|
72
|
-
"meadow-integration": "^1.0.14",
|
|
73
|
-
"meadow-migrationmanager": "^0.0.4",
|
|
74
80
|
"orator-static-server": "^2.0.4",
|
|
75
81
|
"pict": "^1.0.357",
|
|
82
|
+
"pict-section-histogram": "^1.0.0",
|
|
76
83
|
"pict-sessionmanager": "^1.0.2",
|
|
77
84
|
"stricture": "^4.0.2"
|
|
78
85
|
}
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
* @param {Object} pDataClonerService - The RetoldDataServiceDataCloner instance
|
|
8
8
|
* @param {Object} pOratorServiceServer - The Orator ServiceServer instance
|
|
9
9
|
*/
|
|
10
|
+
const libFableLog = require('fable-log');
|
|
11
|
+
const libPath = require('path');
|
|
12
|
+
|
|
10
13
|
module.exports = (pDataClonerService, pOratorServiceServer) =>
|
|
11
14
|
{
|
|
12
15
|
let tmpFable = pDataClonerService.fable;
|
|
@@ -88,6 +91,33 @@ module.exports = (pDataClonerService, pOratorServiceServer) =>
|
|
|
88
91
|
return fNext();
|
|
89
92
|
}
|
|
90
93
|
|
|
94
|
+
// ---- Enable log-to-file if requested ----
|
|
95
|
+
if (tmpBody.LogToFile)
|
|
96
|
+
{
|
|
97
|
+
// Remove any previous sync log stream
|
|
98
|
+
if (tmpCloneState.SyncLogFileLogger)
|
|
99
|
+
{
|
|
100
|
+
try { tmpCloneState.SyncLogFileLogger.closeWriter(() => {}); }
|
|
101
|
+
catch (pErr) { /* ignore */ }
|
|
102
|
+
tmpCloneState.SyncLogFileLogger = null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let tmpTimestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
106
|
+
let tmpLogPath = libPath.join(process.cwd(), `DataCloner-Run-${tmpTimestamp}.log`);
|
|
107
|
+
let tmpFileLogger = new libFableLog.LogProviderFlatfile(
|
|
108
|
+
{
|
|
109
|
+
path: tmpLogPath,
|
|
110
|
+
showtimestamps: true,
|
|
111
|
+
formattedtimestamps: true,
|
|
112
|
+
outputloglinestoconsole: false
|
|
113
|
+
}, tmpFable.log);
|
|
114
|
+
tmpFileLogger.initialize();
|
|
115
|
+
tmpFable.log.addLogger(tmpFileLogger, 'trace');
|
|
116
|
+
tmpCloneState.SyncLogFileLogger = tmpFileLogger;
|
|
117
|
+
tmpCloneState.SyncLogFilePath = tmpLogPath;
|
|
118
|
+
tmpFable.log.info(`Data Cloner: Log file enabled — writing to ${tmpLogPath}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
91
121
|
// ---- Handle Sync Mode switching ----
|
|
92
122
|
let fStartSync = () =>
|
|
93
123
|
{
|
|
@@ -167,33 +197,35 @@ module.exports = (pDataClonerService, pOratorServiceServer) =>
|
|
|
167
197
|
pOratorServiceServer.get(`${tmpPrefix}/sync/status`,
|
|
168
198
|
(pRequest, pResponse, fNext) =>
|
|
169
199
|
{
|
|
170
|
-
//
|
|
171
|
-
|
|
200
|
+
// Build a read-only snapshot of progress (never mutate SyncProgress during a poll)
|
|
201
|
+
let tmpTablesSnapshot = {};
|
|
202
|
+
let tmpTableNames = Object.keys(tmpCloneState.SyncProgress);
|
|
203
|
+
for (let i = 0; i < tmpTableNames.length; i++)
|
|
172
204
|
{
|
|
173
|
-
let
|
|
174
|
-
|
|
205
|
+
let tmpName = tmpTableNames[i];
|
|
206
|
+
let tmpP = tmpCloneState.SyncProgress[tmpName];
|
|
207
|
+
let tmpSnap = { Status: tmpP.Status, Total: tmpP.Total || 0, Synced: tmpP.Synced || 0, Errors: tmpP.Errors || 0 };
|
|
208
|
+
if (tmpP.ErrorMessage) tmpSnap.ErrorMessage = tmpP.ErrorMessage;
|
|
209
|
+
|
|
210
|
+
if ((tmpP.Status === 'Syncing' || tmpP.Status === 'Pending') && tmpFable.MeadowSync && tmpFable.MeadowSync.MeadowSyncEntities)
|
|
175
211
|
{
|
|
176
|
-
let
|
|
177
|
-
|
|
178
|
-
if (tmpProgress && (tmpProgress.Status === 'Syncing' || tmpProgress.Status === 'Pending'))
|
|
212
|
+
let tmpSyncEntity = tmpFable.MeadowSync.MeadowSyncEntities[tmpName];
|
|
213
|
+
if (tmpSyncEntity && tmpSyncEntity.operation)
|
|
179
214
|
{
|
|
180
|
-
let
|
|
181
|
-
if (
|
|
215
|
+
let tmpTracker = tmpSyncEntity.operation.progressTrackers[`FullSync-${tmpName}`];
|
|
216
|
+
if (tmpTracker)
|
|
182
217
|
{
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
{
|
|
186
|
-
tmpProgress.Total = tmpTracker.TotalCount || 0;
|
|
187
|
-
tmpProgress.Synced = Math.max(tmpTracker.CurrentCount || 0, 0);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
let tmpRESTErrors = tmpCloneState.SyncRESTErrors[tmpEntityName] || 0;
|
|
191
|
-
if (tmpRESTErrors > 0)
|
|
192
|
-
{
|
|
193
|
-
tmpProgress.Errors = tmpRESTErrors;
|
|
218
|
+
tmpSnap.Total = tmpTracker.TotalCount || tmpSnap.Total;
|
|
219
|
+
tmpSnap.Synced = Math.max(tmpTracker.CurrentCount || 0, 0);
|
|
194
220
|
}
|
|
195
221
|
}
|
|
222
|
+
let tmpRESTErrors = tmpCloneState.SyncRESTErrors[tmpName] || 0;
|
|
223
|
+
if (tmpRESTErrors > 0)
|
|
224
|
+
{
|
|
225
|
+
tmpSnap.Errors = tmpRESTErrors;
|
|
226
|
+
}
|
|
196
227
|
}
|
|
228
|
+
tmpTablesSnapshot[tmpName] = tmpSnap;
|
|
197
229
|
}
|
|
198
230
|
|
|
199
231
|
pResponse.send(200,
|
|
@@ -201,7 +233,7 @@ module.exports = (pDataClonerService, pOratorServiceServer) =>
|
|
|
201
233
|
Running: tmpCloneState.SyncRunning,
|
|
202
234
|
Stopping: tmpCloneState.SyncStopping,
|
|
203
235
|
SyncMode: tmpCloneState.SyncMode,
|
|
204
|
-
Tables:
|
|
236
|
+
Tables: tmpTablesSnapshot
|
|
205
237
|
});
|
|
206
238
|
return fNext();
|
|
207
239
|
});
|
|
@@ -287,57 +319,57 @@ module.exports = (pDataClonerService, pOratorServiceServer) =>
|
|
|
287
319
|
SyncMode: tmpCloneState.SyncMode,
|
|
288
320
|
ETA: null,
|
|
289
321
|
PreCountGrandTotal: 0,
|
|
290
|
-
PreCountProgress: tmpPC
|
|
322
|
+
PreCountProgress: tmpPC,
|
|
323
|
+
ThroughputSamples: []
|
|
291
324
|
});
|
|
292
325
|
return fNext();
|
|
293
326
|
}
|
|
294
327
|
|
|
295
|
-
//
|
|
296
|
-
|
|
328
|
+
// Read progress from MeadowSync operation trackers (read-only snapshot — never write back to SyncProgress during a poll)
|
|
329
|
+
let tmpTableNames = Object.keys(tmpCloneState.SyncProgress);
|
|
330
|
+
for (let i = 0; i < tmpTableNames.length; i++)
|
|
297
331
|
{
|
|
298
|
-
let
|
|
299
|
-
|
|
332
|
+
let tmpName = tmpTableNames[i];
|
|
333
|
+
let tmpP = tmpCloneState.SyncProgress[tmpName];
|
|
334
|
+
|
|
335
|
+
// Snapshot live tracker values without mutating SyncProgress
|
|
336
|
+
let tmpLiveTotal = tmpP.Total || 0;
|
|
337
|
+
let tmpLiveSynced = tmpP.Synced || 0;
|
|
338
|
+
let tmpLiveErrors = tmpP.Errors || 0;
|
|
339
|
+
|
|
340
|
+
if (tmpP.Status === 'Syncing' || tmpP.Status === 'Pending')
|
|
300
341
|
{
|
|
301
|
-
|
|
302
|
-
let tmpProgress = tmpCloneState.SyncProgress[tmpEntityName];
|
|
303
|
-
if (tmpProgress && (tmpProgress.Status === 'Syncing' || tmpProgress.Status === 'Pending'))
|
|
342
|
+
if (tmpFable.MeadowSync && tmpFable.MeadowSync.MeadowSyncEntities)
|
|
304
343
|
{
|
|
305
|
-
let tmpSyncEntity = tmpFable.MeadowSync.MeadowSyncEntities[
|
|
344
|
+
let tmpSyncEntity = tmpFable.MeadowSync.MeadowSyncEntities[tmpName];
|
|
306
345
|
if (tmpSyncEntity && tmpSyncEntity.operation)
|
|
307
346
|
{
|
|
308
|
-
let tmpTracker = tmpSyncEntity.operation.progressTrackers[`FullSync-${
|
|
347
|
+
let tmpTracker = tmpSyncEntity.operation.progressTrackers[`FullSync-${tmpName}`];
|
|
309
348
|
if (tmpTracker)
|
|
310
349
|
{
|
|
311
|
-
|
|
312
|
-
|
|
350
|
+
tmpLiveTotal = tmpTracker.TotalCount || tmpLiveTotal;
|
|
351
|
+
tmpLiveSynced = Math.max(tmpTracker.CurrentCount || 0, 0);
|
|
313
352
|
}
|
|
314
353
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
354
|
+
}
|
|
355
|
+
let tmpRESTErrors = tmpCloneState.SyncRESTErrors[tmpName] || 0;
|
|
356
|
+
if (tmpRESTErrors > 0)
|
|
357
|
+
{
|
|
358
|
+
tmpLiveErrors = tmpRESTErrors;
|
|
320
359
|
}
|
|
321
360
|
}
|
|
322
|
-
}
|
|
323
361
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
for (let i = 0; i < tmpTableNames.length; i++)
|
|
327
|
-
{
|
|
328
|
-
let tmpName = tmpTableNames[i];
|
|
329
|
-
let tmpP = tmpCloneState.SyncProgress[tmpName];
|
|
330
|
-
tmpTotalSynced += (tmpP.Synced || 0);
|
|
331
|
-
tmpTotalRecords += (tmpP.Total || 0);
|
|
362
|
+
tmpTotalSynced += tmpLiveSynced;
|
|
363
|
+
tmpTotalRecords += tmpLiveTotal;
|
|
332
364
|
|
|
333
365
|
if (tmpP.Status === 'Syncing')
|
|
334
366
|
{
|
|
335
367
|
tmpActiveEntity = tmpName;
|
|
336
|
-
tmpActiveProgress =
|
|
368
|
+
tmpActiveProgress = { Synced: tmpLiveSynced, Total: tmpLiveTotal };
|
|
337
369
|
}
|
|
338
370
|
else if (tmpP.Status === 'Complete' || tmpP.Status === 'Partial')
|
|
339
371
|
{
|
|
340
|
-
tmpCompleted.push({ Name: tmpName, Synced:
|
|
372
|
+
tmpCompleted.push({ Name: tmpName, Synced: tmpLiveSynced, Total: tmpLiveTotal, Status: tmpP.Status });
|
|
341
373
|
}
|
|
342
374
|
else if (tmpP.Status === 'Error')
|
|
343
375
|
{
|
|
@@ -455,7 +487,8 @@ module.exports = (pDataClonerService, pOratorServiceServer) =>
|
|
|
455
487
|
Elapsed: tmpElapsed,
|
|
456
488
|
SyncMode: tmpCloneState.SyncMode,
|
|
457
489
|
ETA: tmpETA,
|
|
458
|
-
PreCountGrandTotal: tmpCloneState.PreCountGrandTotal || 0
|
|
490
|
+
PreCountGrandTotal: tmpCloneState.PreCountGrandTotal || 0,
|
|
491
|
+
ThroughputSamples: tmpCloneState.ThroughputSamples || []
|
|
459
492
|
});
|
|
460
493
|
return fNext();
|
|
461
494
|
});
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DataCloner Web UI Routes
|
|
3
3
|
*
|
|
4
|
-
* Serves the
|
|
5
|
-
* from
|
|
4
|
+
* Serves the Pict-application-based data cloner web UI.
|
|
5
|
+
* The HTML entry point loads pict.min.js (from node_modules) and the
|
|
6
|
+
* Quackage-built application bundle.
|
|
6
7
|
*
|
|
7
8
|
* @param {Object} pDataClonerService - The RetoldDataServiceDataCloner instance
|
|
8
9
|
* @param {Object} pOratorServiceServer - The Orator ServiceServer instance
|
|
@@ -13,28 +14,44 @@ module.exports = (pDataClonerService, pOratorServiceServer) =>
|
|
|
13
14
|
let libPath = require('path');
|
|
14
15
|
|
|
15
16
|
let tmpPrefix = pDataClonerService.routePrefix;
|
|
16
|
-
let
|
|
17
|
+
let tmpWebDir = libPath.join(__dirname, 'web');
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
// Helper: serve a static file with the given content type
|
|
20
|
+
let fServeFile = (pFilePath, pContentType) =>
|
|
21
|
+
{
|
|
22
|
+
return (pRequest, pResponse, fNext) =>
|
|
20
23
|
{
|
|
21
24
|
try
|
|
22
25
|
{
|
|
23
|
-
let
|
|
24
|
-
pResponse.writeHead(200, { 'Content-Type': '
|
|
25
|
-
pResponse.write(
|
|
26
|
+
let tmpContent = libFs.readFileSync(pFilePath, 'utf8');
|
|
27
|
+
pResponse.writeHead(200, { 'Content-Type': pContentType + '; charset=utf-8' });
|
|
28
|
+
pResponse.write(tmpContent);
|
|
26
29
|
pResponse.end();
|
|
27
30
|
}
|
|
28
31
|
catch (pReadError)
|
|
29
32
|
{
|
|
30
|
-
pResponse.send(500, { Success: false, Error: 'Failed to load
|
|
33
|
+
pResponse.send(500, { Success: false, Error: 'Failed to load ' + libPath.basename(pFilePath) });
|
|
31
34
|
}
|
|
32
35
|
return fNext();
|
|
33
|
-
}
|
|
36
|
+
};
|
|
37
|
+
};
|
|
34
38
|
|
|
39
|
+
// ---- HTML ----
|
|
40
|
+
pOratorServiceServer.get(`${tmpPrefix}/`,
|
|
41
|
+
fServeFile(libPath.join(tmpWebDir, 'index.html'), 'text/html'));
|
|
42
|
+
|
|
43
|
+
// Redirect /clone → /clone/
|
|
35
44
|
pOratorServiceServer.get(`${tmpPrefix}`,
|
|
36
45
|
(pRequest, pResponse, fNext) =>
|
|
37
46
|
{
|
|
38
47
|
pResponse.redirect(`${tmpPrefix}/`, fNext);
|
|
39
48
|
});
|
|
49
|
+
|
|
50
|
+
// ---- Pict library (from node_modules) ----
|
|
51
|
+
pOratorServiceServer.get(`${tmpPrefix}/pict.min.js`,
|
|
52
|
+
fServeFile(require.resolve('pict/dist/pict.min.js'), 'application/javascript'));
|
|
53
|
+
|
|
54
|
+
// ---- Application bundle ----
|
|
55
|
+
pOratorServiceServer.get(`${tmpPrefix}/data-cloner.js`,
|
|
56
|
+
fServeFile(libPath.join(tmpWebDir, 'data-cloner.js'), 'application/javascript'));
|
|
40
57
|
};
|