holosphere 1.1.20 → 2.0.0-alpha1
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/.env.example +36 -0
- package/.eslintrc.json +16 -0
- package/.prettierrc.json +7 -0
- package/LICENSE +162 -38
- package/README.md +483 -367
- package/bin/holosphere-activitypub.js +158 -0
- package/cleanup-test-data.js +204 -0
- package/examples/demo.html +1333 -0
- package/examples/example-bot.js +197 -0
- package/package.json +47 -87
- package/scripts/check-bundle-size.js +54 -0
- package/scripts/check-quest-ids.js +77 -0
- package/scripts/import-holons.js +578 -0
- package/scripts/publish-to-relay.js +101 -0
- package/scripts/read-example.js +186 -0
- package/scripts/relay-diagnostic.js +59 -0
- package/scripts/relay-example.js +179 -0
- package/scripts/resync-to-relay.js +245 -0
- package/scripts/revert-import.js +196 -0
- package/scripts/test-hybrid-mode.js +108 -0
- package/scripts/test-local-storage.js +63 -0
- package/scripts/test-nostr-direct.js +55 -0
- package/scripts/test-read-data.js +45 -0
- package/scripts/test-write-read.js +63 -0
- package/scripts/verify-import.js +95 -0
- package/scripts/verify-relay-data.js +139 -0
- package/src/ai/aggregation.js +319 -0
- package/src/ai/breakdown.js +511 -0
- package/src/ai/classifier.js +217 -0
- package/src/ai/council.js +228 -0
- package/src/ai/embeddings.js +279 -0
- package/src/ai/federation-ai.js +324 -0
- package/src/ai/h3-ai.js +955 -0
- package/src/ai/index.js +112 -0
- package/src/ai/json-ops.js +225 -0
- package/src/ai/llm-service.js +205 -0
- package/src/ai/nl-query.js +223 -0
- package/src/ai/relationships.js +353 -0
- package/src/ai/schema-extractor.js +218 -0
- package/src/ai/spatial.js +293 -0
- package/src/ai/tts.js +194 -0
- package/src/content/social-protocols.js +168 -0
- package/src/core/holosphere.js +273 -0
- package/src/crypto/secp256k1.js +259 -0
- package/src/federation/discovery.js +334 -0
- package/src/federation/hologram.js +1042 -0
- package/src/federation/registry.js +386 -0
- package/src/hierarchical/upcast.js +110 -0
- package/src/index.js +2669 -0
- package/src/schema/validator.js +91 -0
- package/src/spatial/h3-operations.js +110 -0
- package/src/storage/backend-factory.js +125 -0
- package/src/storage/backend-interface.js +142 -0
- package/src/storage/backends/activitypub/server.js +653 -0
- package/src/storage/backends/activitypub-backend.js +272 -0
- package/src/storage/backends/gundb-backend.js +233 -0
- package/src/storage/backends/nostr-backend.js +136 -0
- package/src/storage/filesystem-storage-browser.js +41 -0
- package/src/storage/filesystem-storage.js +138 -0
- package/src/storage/global-tables.js +81 -0
- package/src/storage/gun-async.js +281 -0
- package/src/storage/gun-wrapper.js +221 -0
- package/src/storage/indexeddb-storage.js +122 -0
- package/src/storage/key-storage-simple.js +76 -0
- package/src/storage/key-storage.js +136 -0
- package/src/storage/memory-storage.js +59 -0
- package/src/storage/migration.js +338 -0
- package/src/storage/nostr-async.js +811 -0
- package/src/storage/nostr-client.js +939 -0
- package/src/storage/nostr-wrapper.js +211 -0
- package/src/storage/outbox-queue.js +208 -0
- package/src/storage/persistent-storage.js +109 -0
- package/src/storage/sync-service.js +164 -0
- package/src/subscriptions/manager.js +142 -0
- package/test-ai-real-api.js +202 -0
- package/tests/unit/ai/aggregation.test.js +295 -0
- package/tests/unit/ai/breakdown.test.js +446 -0
- package/tests/unit/ai/classifier.test.js +294 -0
- package/tests/unit/ai/council.test.js +262 -0
- package/tests/unit/ai/embeddings.test.js +384 -0
- package/tests/unit/ai/federation-ai.test.js +344 -0
- package/tests/unit/ai/h3-ai.test.js +458 -0
- package/tests/unit/ai/index.test.js +304 -0
- package/tests/unit/ai/json-ops.test.js +307 -0
- package/tests/unit/ai/llm-service.test.js +390 -0
- package/tests/unit/ai/nl-query.test.js +383 -0
- package/tests/unit/ai/relationships.test.js +311 -0
- package/tests/unit/ai/schema-extractor.test.js +384 -0
- package/tests/unit/ai/spatial.test.js +279 -0
- package/tests/unit/ai/tts.test.js +279 -0
- package/tests/unit/content.test.js +332 -0
- package/tests/unit/contract/core.test.js +88 -0
- package/tests/unit/contract/crypto.test.js +198 -0
- package/tests/unit/contract/data.test.js +223 -0
- package/tests/unit/contract/federation.test.js +181 -0
- package/tests/unit/contract/hierarchical.test.js +113 -0
- package/tests/unit/contract/schema.test.js +114 -0
- package/tests/unit/contract/social.test.js +217 -0
- package/tests/unit/contract/spatial.test.js +110 -0
- package/tests/unit/contract/subscriptions.test.js +128 -0
- package/tests/unit/contract/utils.test.js +159 -0
- package/tests/unit/core.test.js +152 -0
- package/tests/unit/crypto.test.js +328 -0
- package/tests/unit/federation.test.js +234 -0
- package/tests/unit/gun-async.test.js +252 -0
- package/tests/unit/hierarchical.test.js +399 -0
- package/tests/unit/integration/scenario-01-geographic-storage.test.js +74 -0
- package/tests/unit/integration/scenario-02-federation.test.js +76 -0
- package/tests/unit/integration/scenario-03-subscriptions.test.js +102 -0
- package/tests/unit/integration/scenario-04-validation.test.js +129 -0
- package/tests/unit/integration/scenario-05-hierarchy.test.js +125 -0
- package/tests/unit/integration/scenario-06-social.test.js +135 -0
- package/tests/unit/integration/scenario-07-persistence.test.js +130 -0
- package/tests/unit/integration/scenario-08-authorization.test.js +161 -0
- package/tests/unit/integration/scenario-09-cross-dimensional.test.js +139 -0
- package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +357 -0
- package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +410 -0
- package/tests/unit/integration/scenario-12-capability-federated-read.test.js +719 -0
- package/tests/unit/performance/benchmark.test.js +85 -0
- package/tests/unit/schema.test.js +213 -0
- package/tests/unit/spatial.test.js +158 -0
- package/tests/unit/storage.test.js +195 -0
- package/tests/unit/subscriptions.test.js +328 -0
- package/tests/unit/test-data-permanence-debug.js +197 -0
- package/tests/unit/test-data-permanence.js +340 -0
- package/tests/unit/test-key-persistence-fixed.js +148 -0
- package/tests/unit/test-key-persistence.js +172 -0
- package/tests/unit/test-relay-permanence.js +376 -0
- package/tests/unit/test-second-node.js +95 -0
- package/tests/unit/test-simple-write.js +89 -0
- package/vite.config.js +49 -0
- package/vitest.config.js +20 -0
- package/FEDERATION.md +0 -213
- package/compute.js +0 -298
- package/content.js +0 -980
- package/federation.js +0 -1234
- package/global.js +0 -736
- package/hexlib.js +0 -335
- package/hologram.js +0 -183
- package/holosphere-bundle.esm.js +0 -33256
- package/holosphere-bundle.js +0 -33287
- package/holosphere-bundle.min.js +0 -39
- package/holosphere.d.ts +0 -601
- package/holosphere.js +0 -719
- package/node.js +0 -246
- package/schema.js +0 -139
- package/utils.js +0 -302
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration Tool for HoloSphere Storage Backends
|
|
3
|
+
*
|
|
4
|
+
* Enables data migration between different storage backends:
|
|
5
|
+
* - Export data from any backend to a portable format
|
|
6
|
+
* - Import data to any backend from the portable format
|
|
7
|
+
* - Direct migration between backends
|
|
8
|
+
* - Validation of migrated data
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { BackendFactory } from './backend-factory.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Portable data bundle format
|
|
15
|
+
* @typedef {Object} DataBundle
|
|
16
|
+
* @property {string} version - Bundle format version
|
|
17
|
+
* @property {string} exportedAt - ISO timestamp
|
|
18
|
+
* @property {string} sourceType - Source backend type
|
|
19
|
+
* @property {string} sourceKey - Source public key
|
|
20
|
+
* @property {number} recordCount - Number of records
|
|
21
|
+
* @property {Object[]} records - Array of data records
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Migration record format
|
|
26
|
+
* @typedef {Object} MigrationRecord
|
|
27
|
+
* @property {string} path - Storage path
|
|
28
|
+
* @property {Object} data - Record data
|
|
29
|
+
* @property {number} timestamp - Creation timestamp
|
|
30
|
+
* @property {string} [author] - Author public key
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Migration results
|
|
35
|
+
* @typedef {Object} MigrationResults
|
|
36
|
+
* @property {number} success - Successfully migrated records
|
|
37
|
+
* @property {number} failed - Failed records
|
|
38
|
+
* @property {Object[]} errors - Error details
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
export class MigrationTool {
|
|
42
|
+
constructor() {
|
|
43
|
+
this.sourceBackend = null;
|
|
44
|
+
this.targetBackend = null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Configure source backend
|
|
49
|
+
* @param {string} type - Backend type: 'nostr' | 'gundb' | 'activitypub'
|
|
50
|
+
* @param {Object} config - Backend configuration
|
|
51
|
+
* @returns {Promise<void>}
|
|
52
|
+
*/
|
|
53
|
+
async setSource(type, config) {
|
|
54
|
+
this.sourceBackend = await BackendFactory.create(type, config);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Configure target backend
|
|
59
|
+
* @param {string} type - Backend type: 'nostr' | 'gundb' | 'activitypub'
|
|
60
|
+
* @param {Object} config - Backend configuration
|
|
61
|
+
* @returns {Promise<void>}
|
|
62
|
+
*/
|
|
63
|
+
async setTarget(type, config) {
|
|
64
|
+
this.targetBackend = await BackendFactory.create(type, config);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Use an existing backend instance as source
|
|
69
|
+
* @param {StorageBackend} backend - Backend instance
|
|
70
|
+
*/
|
|
71
|
+
useSourceBackend(backend) {
|
|
72
|
+
this.sourceBackend = backend;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Use an existing backend instance as target
|
|
77
|
+
* @param {StorageBackend} backend - Backend instance
|
|
78
|
+
*/
|
|
79
|
+
useTargetBackend(backend) {
|
|
80
|
+
this.targetBackend = backend;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Export data from source to portable format
|
|
85
|
+
* @param {string} [pathPrefix] - Optional path prefix filter
|
|
86
|
+
* @returns {Promise<DataBundle>} Exportable data bundle
|
|
87
|
+
*/
|
|
88
|
+
async export(pathPrefix = '') {
|
|
89
|
+
if (!this.sourceBackend) {
|
|
90
|
+
throw new Error('Source backend not configured. Call setSource() first.');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
console.log(`Exporting data from ${this.sourceBackend.getStatus().type}...`);
|
|
94
|
+
|
|
95
|
+
const records = await this.sourceBackend.exportData(pathPrefix);
|
|
96
|
+
const status = this.sourceBackend.getStatus();
|
|
97
|
+
|
|
98
|
+
const bundle = {
|
|
99
|
+
version: '1.0',
|
|
100
|
+
exportedAt: new Date().toISOString(),
|
|
101
|
+
sourceType: status.type,
|
|
102
|
+
sourceKey: status.publicKey || 'unknown',
|
|
103
|
+
pathPrefix: pathPrefix || '',
|
|
104
|
+
recordCount: records.length,
|
|
105
|
+
records,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
console.log(`Exported ${records.length} records.`);
|
|
109
|
+
return bundle;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Import data to target backend
|
|
114
|
+
* @param {DataBundle} bundle - Data bundle from export()
|
|
115
|
+
* @param {Object} [options] - Import options
|
|
116
|
+
* @param {boolean} [options.overwrite] - Overwrite existing data
|
|
117
|
+
* @param {boolean} [options.dryRun] - Simulate without writing
|
|
118
|
+
* @param {Function} [options.onProgress] - Progress callback (current, total)
|
|
119
|
+
* @returns {Promise<MigrationResults>} Import results
|
|
120
|
+
*/
|
|
121
|
+
async import(bundle, options = {}) {
|
|
122
|
+
if (!this.targetBackend) {
|
|
123
|
+
throw new Error('Target backend not configured. Call setTarget() first.');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!bundle || !bundle.records) {
|
|
127
|
+
throw new Error('Invalid bundle format. Expected { version, records }');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
console.log(`Importing ${bundle.recordCount} records to ${this.targetBackend.getStatus().type}...`);
|
|
131
|
+
|
|
132
|
+
if (options.dryRun) {
|
|
133
|
+
console.log('Dry run mode - no data will be written.');
|
|
134
|
+
return {
|
|
135
|
+
dryRun: true,
|
|
136
|
+
wouldImport: bundle.recordCount,
|
|
137
|
+
records: bundle.records.map(r => r.path),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const results = await this.targetBackend.importData(bundle.records, options);
|
|
142
|
+
|
|
143
|
+
if (options.onProgress) {
|
|
144
|
+
options.onProgress(results.success + results.failed, bundle.recordCount);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log(`Import complete: ${results.success} succeeded, ${results.failed} failed.`);
|
|
148
|
+
return results;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Migrate data directly from source to target
|
|
153
|
+
* @param {string} [pathPrefix] - Optional path prefix filter
|
|
154
|
+
* @param {Object} [options] - Migration options
|
|
155
|
+
* @param {boolean} [options.dryRun] - Simulate without writing
|
|
156
|
+
* @param {Function} [options.onProgress] - Progress callback
|
|
157
|
+
* @returns {Promise<Object>} Migration results
|
|
158
|
+
*/
|
|
159
|
+
async migrate(pathPrefix = '', options = {}) {
|
|
160
|
+
if (!this.sourceBackend || !this.targetBackend) {
|
|
161
|
+
throw new Error('Both source and target backends must be configured.');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const sourceStatus = this.sourceBackend.getStatus();
|
|
165
|
+
const targetStatus = this.targetBackend.getStatus();
|
|
166
|
+
|
|
167
|
+
console.log(`\nMigrating data:`);
|
|
168
|
+
console.log(` From: ${sourceStatus.type} (${sourceStatus.publicKey || 'unknown'})`);
|
|
169
|
+
console.log(` To: ${targetStatus.type}`);
|
|
170
|
+
console.log(` Filter: ${pathPrefix || '(all data)'}\n`);
|
|
171
|
+
|
|
172
|
+
// Export from source
|
|
173
|
+
const bundle = await this.export(pathPrefix);
|
|
174
|
+
|
|
175
|
+
// Import to target
|
|
176
|
+
const importResults = await this.import(bundle, options);
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
sourceType: sourceStatus.type,
|
|
180
|
+
targetType: targetStatus.type,
|
|
181
|
+
pathPrefix,
|
|
182
|
+
exportedCount: bundle.recordCount,
|
|
183
|
+
...importResults,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Validate data integrity after migration
|
|
189
|
+
* @param {string} [pathPrefix] - Optional path prefix filter
|
|
190
|
+
* @returns {Promise<Object>} Validation results
|
|
191
|
+
*/
|
|
192
|
+
async validate(pathPrefix = '') {
|
|
193
|
+
if (!this.sourceBackend || !this.targetBackend) {
|
|
194
|
+
throw new Error('Both source and target backends must be configured.');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
console.log('Validating migration...');
|
|
198
|
+
|
|
199
|
+
const sourceData = await this.sourceBackend.exportData(pathPrefix);
|
|
200
|
+
const targetData = await this.targetBackend.exportData(pathPrefix);
|
|
201
|
+
|
|
202
|
+
const sourceMap = new Map(sourceData.map(r => [r.path, r]));
|
|
203
|
+
const targetMap = new Map(targetData.map(r => [r.path, r]));
|
|
204
|
+
|
|
205
|
+
const missing = [];
|
|
206
|
+
const different = [];
|
|
207
|
+
const extra = [];
|
|
208
|
+
|
|
209
|
+
// Check for missing or different records
|
|
210
|
+
for (const [path, sourceRecord] of sourceMap) {
|
|
211
|
+
const targetRecord = targetMap.get(path);
|
|
212
|
+
|
|
213
|
+
if (!targetRecord) {
|
|
214
|
+
missing.push(path);
|
|
215
|
+
} else {
|
|
216
|
+
// Compare data (ignoring metadata that may differ)
|
|
217
|
+
const sourceDataStr = JSON.stringify(this._normalizeForComparison(sourceRecord.data));
|
|
218
|
+
const targetDataStr = JSON.stringify(this._normalizeForComparison(targetRecord.data));
|
|
219
|
+
|
|
220
|
+
if (sourceDataStr !== targetDataStr) {
|
|
221
|
+
different.push({
|
|
222
|
+
path,
|
|
223
|
+
source: sourceRecord.data,
|
|
224
|
+
target: targetRecord.data,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Check for extra records in target
|
|
231
|
+
for (const path of targetMap.keys()) {
|
|
232
|
+
if (!sourceMap.has(path)) {
|
|
233
|
+
extra.push(path);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const valid = missing.length === 0 && different.length === 0;
|
|
238
|
+
|
|
239
|
+
console.log(`Validation ${valid ? 'PASSED' : 'FAILED'}:`);
|
|
240
|
+
console.log(` Source records: ${sourceData.length}`);
|
|
241
|
+
console.log(` Target records: ${targetData.length}`);
|
|
242
|
+
console.log(` Missing: ${missing.length}`);
|
|
243
|
+
console.log(` Different: ${different.length}`);
|
|
244
|
+
console.log(` Extra: ${extra.length}`);
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
valid,
|
|
248
|
+
sourceCount: sourceData.length,
|
|
249
|
+
targetCount: targetData.length,
|
|
250
|
+
missing,
|
|
251
|
+
different,
|
|
252
|
+
extra,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Normalize data for comparison (remove volatile metadata)
|
|
258
|
+
* @private
|
|
259
|
+
*/
|
|
260
|
+
_normalizeForComparison(data) {
|
|
261
|
+
if (!data || typeof data !== 'object') return data;
|
|
262
|
+
|
|
263
|
+
const normalized = { ...data };
|
|
264
|
+
|
|
265
|
+
// Remove metadata that may legitimately differ
|
|
266
|
+
delete normalized._meta;
|
|
267
|
+
delete normalized._author;
|
|
268
|
+
delete normalized._timestamp;
|
|
269
|
+
|
|
270
|
+
return normalized;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Export bundle to JSON file (for manual transfer)
|
|
275
|
+
* @param {DataBundle} bundle - Data bundle
|
|
276
|
+
* @param {string} filePath - Output file path
|
|
277
|
+
*/
|
|
278
|
+
async exportToFile(bundle, filePath) {
|
|
279
|
+
const fs = await import('fs/promises');
|
|
280
|
+
await fs.writeFile(filePath, JSON.stringify(bundle, null, 2), 'utf-8');
|
|
281
|
+
console.log(`Bundle exported to ${filePath}`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Import bundle from JSON file
|
|
286
|
+
* @param {string} filePath - Input file path
|
|
287
|
+
* @returns {Promise<DataBundle>} Loaded bundle
|
|
288
|
+
*/
|
|
289
|
+
async importFromFile(filePath) {
|
|
290
|
+
const fs = await import('fs/promises');
|
|
291
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
292
|
+
const bundle = JSON.parse(content);
|
|
293
|
+
console.log(`Bundle loaded from ${filePath}: ${bundle.recordCount} records`);
|
|
294
|
+
return bundle;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Close all backend connections
|
|
299
|
+
*/
|
|
300
|
+
close() {
|
|
301
|
+
if (this.sourceBackend) {
|
|
302
|
+
this.sourceBackend.close();
|
|
303
|
+
}
|
|
304
|
+
if (this.targetBackend) {
|
|
305
|
+
this.targetBackend.close();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Quick migration function for common use cases
|
|
312
|
+
* @param {Object} sourceConfig - Source backend config with 'type' field
|
|
313
|
+
* @param {Object} targetConfig - Target backend config with 'type' field
|
|
314
|
+
* @param {string} [pathPrefix] - Optional path filter
|
|
315
|
+
* @param {Object} [options] - Migration options
|
|
316
|
+
* @returns {Promise<Object>} Migration results
|
|
317
|
+
*/
|
|
318
|
+
export async function quickMigrate(sourceConfig, targetConfig, pathPrefix = '', options = {}) {
|
|
319
|
+
const tool = new MigrationTool();
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
await tool.setSource(sourceConfig.type, sourceConfig);
|
|
323
|
+
await tool.setTarget(targetConfig.type, targetConfig);
|
|
324
|
+
|
|
325
|
+
const results = await tool.migrate(pathPrefix, options);
|
|
326
|
+
|
|
327
|
+
if (!options.skipValidation) {
|
|
328
|
+
const validation = await tool.validate(pathPrefix);
|
|
329
|
+
results.validation = validation;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return results;
|
|
333
|
+
} finally {
|
|
334
|
+
tool.close();
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export default MigrationTool;
|