@affectively/aeon 1.0.0
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/LICENSE +21 -0
- package/README.md +332 -0
- package/dist/core/index.cjs +4 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +146 -0
- package/dist/core/index.d.ts +146 -0
- package/dist/core/index.js +3 -0
- package/dist/core/index.js.map +1 -0
- package/dist/distributed/index.cjs +1874 -0
- package/dist/distributed/index.cjs.map +1 -0
- package/dist/distributed/index.d.cts +2 -0
- package/dist/distributed/index.d.ts +2 -0
- package/dist/distributed/index.js +1869 -0
- package/dist/distributed/index.js.map +1 -0
- package/dist/index-C_4CMV5c.d.cts +1207 -0
- package/dist/index-C_4CMV5c.d.ts +1207 -0
- package/dist/index.cjs +4671 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +812 -0
- package/dist/index.d.ts +812 -0
- package/dist/index.js +4632 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/index.cjs +64 -0
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.d.cts +38 -0
- package/dist/utils/index.d.ts +38 -0
- package/dist/utils/index.js +57 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/versioning/index.cjs +871 -0
- package/dist/versioning/index.cjs.map +1 -0
- package/dist/versioning/index.d.cts +472 -0
- package/dist/versioning/index.d.ts +472 -0
- package/dist/versioning/index.js +866 -0
- package/dist/versioning/index.js.map +1 -0
- package/package.json +142 -0
|
@@ -0,0 +1,866 @@
|
|
|
1
|
+
// src/utils/logger.ts
|
|
2
|
+
var consoleLogger = {
|
|
3
|
+
debug: (...args) => {
|
|
4
|
+
console.debug("[AEON:DEBUG]", ...args);
|
|
5
|
+
},
|
|
6
|
+
info: (...args) => {
|
|
7
|
+
console.info("[AEON:INFO]", ...args);
|
|
8
|
+
},
|
|
9
|
+
warn: (...args) => {
|
|
10
|
+
console.warn("[AEON:WARN]", ...args);
|
|
11
|
+
},
|
|
12
|
+
error: (...args) => {
|
|
13
|
+
console.error("[AEON:ERROR]", ...args);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var currentLogger = consoleLogger;
|
|
17
|
+
function getLogger() {
|
|
18
|
+
return currentLogger;
|
|
19
|
+
}
|
|
20
|
+
var logger = {
|
|
21
|
+
debug: (...args) => getLogger().debug(...args),
|
|
22
|
+
info: (...args) => getLogger().info(...args),
|
|
23
|
+
warn: (...args) => getLogger().warn(...args),
|
|
24
|
+
error: (...args) => getLogger().error(...args)
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// src/versioning/SchemaVersionManager.ts
|
|
28
|
+
var SchemaVersionManager = class {
|
|
29
|
+
versions = /* @__PURE__ */ new Map();
|
|
30
|
+
versionHistory = [];
|
|
31
|
+
compatibilityMatrix = /* @__PURE__ */ new Map();
|
|
32
|
+
currentVersion = null;
|
|
33
|
+
constructor() {
|
|
34
|
+
this.initializeDefaultVersions();
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Initialize default versions
|
|
38
|
+
*/
|
|
39
|
+
initializeDefaultVersions() {
|
|
40
|
+
const v1_0_0 = {
|
|
41
|
+
major: 1,
|
|
42
|
+
minor: 0,
|
|
43
|
+
patch: 0,
|
|
44
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
45
|
+
description: "Initial schema version",
|
|
46
|
+
breaking: false
|
|
47
|
+
};
|
|
48
|
+
this.registerVersion(v1_0_0);
|
|
49
|
+
this.currentVersion = v1_0_0;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Register a new schema version
|
|
53
|
+
*/
|
|
54
|
+
registerVersion(version) {
|
|
55
|
+
const versionString = this.versionToString(version);
|
|
56
|
+
this.versions.set(versionString, version);
|
|
57
|
+
this.versionHistory.push(version);
|
|
58
|
+
logger.debug("[SchemaVersionManager] Version registered", {
|
|
59
|
+
version: versionString,
|
|
60
|
+
breaking: version.breaking,
|
|
61
|
+
description: version.description
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get current version
|
|
66
|
+
*/
|
|
67
|
+
getCurrentVersion() {
|
|
68
|
+
if (!this.currentVersion) {
|
|
69
|
+
throw new Error("No current version set");
|
|
70
|
+
}
|
|
71
|
+
return this.currentVersion;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Set current version
|
|
75
|
+
*/
|
|
76
|
+
setCurrentVersion(version) {
|
|
77
|
+
if (!this.versions.has(this.versionToString(version))) {
|
|
78
|
+
throw new Error(`Version ${this.versionToString(version)} not registered`);
|
|
79
|
+
}
|
|
80
|
+
this.currentVersion = version;
|
|
81
|
+
logger.debug("[SchemaVersionManager] Current version set", {
|
|
82
|
+
version: this.versionToString(version)
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get version history
|
|
87
|
+
*/
|
|
88
|
+
getVersionHistory() {
|
|
89
|
+
return [...this.versionHistory];
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Check if version exists
|
|
93
|
+
*/
|
|
94
|
+
hasVersion(version) {
|
|
95
|
+
return this.versions.has(this.versionToString(version));
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get version by string (e.g., "1.2.3")
|
|
99
|
+
*/
|
|
100
|
+
getVersion(versionString) {
|
|
101
|
+
return this.versions.get(versionString);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Register compatibility rule
|
|
105
|
+
*/
|
|
106
|
+
registerCompatibility(rule) {
|
|
107
|
+
if (!this.compatibilityMatrix.has(rule.from)) {
|
|
108
|
+
this.compatibilityMatrix.set(rule.from, []);
|
|
109
|
+
}
|
|
110
|
+
const rules = this.compatibilityMatrix.get(rule.from);
|
|
111
|
+
if (rules) {
|
|
112
|
+
rules.push(rule);
|
|
113
|
+
}
|
|
114
|
+
logger.debug("[SchemaVersionManager] Compatibility rule registered", {
|
|
115
|
+
from: rule.from,
|
|
116
|
+
to: rule.to,
|
|
117
|
+
compatible: rule.compatible,
|
|
118
|
+
requiresMigration: rule.requiresMigration
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Check if migration path exists
|
|
123
|
+
*/
|
|
124
|
+
canMigrate(fromVersion, toVersion) {
|
|
125
|
+
const fromStr = typeof fromVersion === "string" ? fromVersion : this.versionToString(fromVersion);
|
|
126
|
+
const toStr = typeof toVersion === "string" ? toVersion : this.versionToString(toVersion);
|
|
127
|
+
const rules = this.compatibilityMatrix.get(fromStr) || [];
|
|
128
|
+
return rules.some((r) => r.to === toStr && r.requiresMigration);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Get migration path
|
|
132
|
+
*/
|
|
133
|
+
getMigrationPath(fromVersion, toVersion) {
|
|
134
|
+
const path = [];
|
|
135
|
+
let current = fromVersion;
|
|
136
|
+
const maxSteps = 100;
|
|
137
|
+
let steps = 0;
|
|
138
|
+
while (this.compareVersions(current, toVersion) !== 0 && steps < maxSteps) {
|
|
139
|
+
const fromStr = this.versionToString(current);
|
|
140
|
+
const rules = this.compatibilityMatrix.get(fromStr) || [];
|
|
141
|
+
let found = false;
|
|
142
|
+
for (const rule of rules) {
|
|
143
|
+
const nextVersion = this.getVersion(rule.to);
|
|
144
|
+
if (nextVersion) {
|
|
145
|
+
if (this.compareVersions(nextVersion, toVersion) <= 0 || this.compareVersions(current, nextVersion) < this.compareVersions(current, toVersion)) {
|
|
146
|
+
current = nextVersion;
|
|
147
|
+
path.push(current);
|
|
148
|
+
found = true;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (!found) {
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
steps++;
|
|
157
|
+
}
|
|
158
|
+
return path;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Compare two versions
|
|
162
|
+
* Returns: -1 if v1 < v2, 0 if equal, 1 if v1 > v2
|
|
163
|
+
*/
|
|
164
|
+
compareVersions(v1, v2) {
|
|
165
|
+
const ver1 = typeof v1 === "string" ? this.parseVersion(v1) : v1;
|
|
166
|
+
const ver2 = typeof v2 === "string" ? this.parseVersion(v2) : v2;
|
|
167
|
+
if (ver1.major !== ver2.major) {
|
|
168
|
+
return ver1.major < ver2.major ? -1 : 1;
|
|
169
|
+
}
|
|
170
|
+
if (ver1.minor !== ver2.minor) {
|
|
171
|
+
return ver1.minor < ver2.minor ? -1 : 1;
|
|
172
|
+
}
|
|
173
|
+
if (ver1.patch !== ver2.patch) {
|
|
174
|
+
return ver1.patch < ver2.patch ? -1 : 1;
|
|
175
|
+
}
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Parse version string to SchemaVersion
|
|
180
|
+
*/
|
|
181
|
+
parseVersion(versionString) {
|
|
182
|
+
const parts = versionString.split(".").map(Number);
|
|
183
|
+
return {
|
|
184
|
+
major: parts[0] || 0,
|
|
185
|
+
minor: parts[1] || 0,
|
|
186
|
+
patch: parts[2] || 0,
|
|
187
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
188
|
+
description: "",
|
|
189
|
+
breaking: false
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Create new version
|
|
194
|
+
*/
|
|
195
|
+
createVersion(major, minor, patch, description, breaking = false) {
|
|
196
|
+
return {
|
|
197
|
+
major,
|
|
198
|
+
minor,
|
|
199
|
+
patch,
|
|
200
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
201
|
+
description,
|
|
202
|
+
breaking
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Convert version to string
|
|
207
|
+
*/
|
|
208
|
+
versionToString(version) {
|
|
209
|
+
return `${version.major}.${version.minor}.${version.patch}`;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Get version metadata
|
|
213
|
+
*/
|
|
214
|
+
getVersionMetadata(version) {
|
|
215
|
+
const history = this.versionHistory;
|
|
216
|
+
const currentIndex = history.findIndex(
|
|
217
|
+
(v) => this.versionToString(v) === this.versionToString(version)
|
|
218
|
+
);
|
|
219
|
+
return {
|
|
220
|
+
version,
|
|
221
|
+
previousVersion: currentIndex > 0 ? history[currentIndex - 1] : void 0,
|
|
222
|
+
changes: [version.description],
|
|
223
|
+
migrationsRequired: this.canMigrate(
|
|
224
|
+
this.currentVersion || version,
|
|
225
|
+
version
|
|
226
|
+
) ? [this.versionToString(version)] : [],
|
|
227
|
+
rollbackPossible: currentIndex > 0
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Get all registered versions
|
|
232
|
+
*/
|
|
233
|
+
getAllVersions() {
|
|
234
|
+
return Array.from(this.versions.values()).sort(
|
|
235
|
+
(a, b) => this.compareVersions(a, b)
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Clear all versions (for testing)
|
|
240
|
+
*/
|
|
241
|
+
clear() {
|
|
242
|
+
this.versions.clear();
|
|
243
|
+
this.versionHistory = [];
|
|
244
|
+
this.compatibilityMatrix.clear();
|
|
245
|
+
this.currentVersion = null;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// src/versioning/MigrationEngine.ts
|
|
250
|
+
var MigrationEngine = class {
|
|
251
|
+
migrations = /* @__PURE__ */ new Map();
|
|
252
|
+
executedMigrations = [];
|
|
253
|
+
state = {
|
|
254
|
+
currentVersion: "1.0.0",
|
|
255
|
+
appliedMigrations: [],
|
|
256
|
+
failedMigrations: [],
|
|
257
|
+
lastMigrationTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
258
|
+
totalMigrationsRun: 0
|
|
259
|
+
};
|
|
260
|
+
/**
|
|
261
|
+
* Register a migration
|
|
262
|
+
*/
|
|
263
|
+
registerMigration(migration) {
|
|
264
|
+
this.migrations.set(migration.id, migration);
|
|
265
|
+
logger.debug("[MigrationEngine] Migration registered", {
|
|
266
|
+
id: migration.id,
|
|
267
|
+
version: migration.version,
|
|
268
|
+
name: migration.name
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Execute a migration
|
|
273
|
+
*/
|
|
274
|
+
async executeMigration(migrationId, data) {
|
|
275
|
+
const migration = this.migrations.get(migrationId);
|
|
276
|
+
if (!migration) {
|
|
277
|
+
throw new Error(`Migration ${migrationId} not found`);
|
|
278
|
+
}
|
|
279
|
+
const startTime = Date.now();
|
|
280
|
+
const result = {
|
|
281
|
+
migrationId,
|
|
282
|
+
success: false,
|
|
283
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
284
|
+
duration: 0,
|
|
285
|
+
itemsAffected: 0,
|
|
286
|
+
errors: []
|
|
287
|
+
};
|
|
288
|
+
try {
|
|
289
|
+
logger.debug("[MigrationEngine] Executing migration", {
|
|
290
|
+
id: migrationId,
|
|
291
|
+
version: migration.version
|
|
292
|
+
});
|
|
293
|
+
migration.up(data);
|
|
294
|
+
result.success = true;
|
|
295
|
+
result.itemsAffected = Array.isArray(data) ? data.length : 1;
|
|
296
|
+
result.duration = Date.now() - startTime;
|
|
297
|
+
this.state.appliedMigrations.push(migrationId);
|
|
298
|
+
this.state.currentVersion = migration.version;
|
|
299
|
+
this.state.totalMigrationsRun++;
|
|
300
|
+
this.state.lastMigrationTime = result.timestamp;
|
|
301
|
+
this.executedMigrations.push(result);
|
|
302
|
+
logger.debug("[MigrationEngine] Migration executed successfully", {
|
|
303
|
+
id: migrationId,
|
|
304
|
+
duration: result.duration,
|
|
305
|
+
itemsAffected: result.itemsAffected
|
|
306
|
+
});
|
|
307
|
+
return result;
|
|
308
|
+
} catch (error) {
|
|
309
|
+
result.errors = [error instanceof Error ? error.message : String(error)];
|
|
310
|
+
this.state.failedMigrations.push(migrationId);
|
|
311
|
+
this.executedMigrations.push(result);
|
|
312
|
+
logger.error("[MigrationEngine] Migration failed", {
|
|
313
|
+
id: migrationId,
|
|
314
|
+
error: result.errors[0]
|
|
315
|
+
});
|
|
316
|
+
throw new Error(`Migration ${migrationId} failed: ${result.errors[0]}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Rollback a migration
|
|
321
|
+
*/
|
|
322
|
+
async rollbackMigration(migrationId, data) {
|
|
323
|
+
const migration = this.migrations.get(migrationId);
|
|
324
|
+
if (!migration) {
|
|
325
|
+
throw new Error(`Migration ${migrationId} not found`);
|
|
326
|
+
}
|
|
327
|
+
if (!migration.down) {
|
|
328
|
+
throw new Error(`Migration ${migrationId} does not support rollback`);
|
|
329
|
+
}
|
|
330
|
+
const startTime = Date.now();
|
|
331
|
+
const result = {
|
|
332
|
+
migrationId,
|
|
333
|
+
success: false,
|
|
334
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
335
|
+
duration: 0,
|
|
336
|
+
itemsAffected: 0,
|
|
337
|
+
errors: []
|
|
338
|
+
};
|
|
339
|
+
try {
|
|
340
|
+
logger.debug("[MigrationEngine] Rolling back migration", {
|
|
341
|
+
id: migrationId,
|
|
342
|
+
version: migration.version
|
|
343
|
+
});
|
|
344
|
+
migration.down(data);
|
|
345
|
+
result.success = true;
|
|
346
|
+
result.itemsAffected = Array.isArray(data) ? data.length : 1;
|
|
347
|
+
result.duration = Date.now() - startTime;
|
|
348
|
+
this.state.appliedMigrations = this.state.appliedMigrations.filter(
|
|
349
|
+
(id) => id !== migrationId
|
|
350
|
+
);
|
|
351
|
+
this.executedMigrations.push(result);
|
|
352
|
+
logger.debug("[MigrationEngine] Migration rolled back", {
|
|
353
|
+
id: migrationId,
|
|
354
|
+
duration: result.duration
|
|
355
|
+
});
|
|
356
|
+
return result;
|
|
357
|
+
} catch (error) {
|
|
358
|
+
result.errors = [error instanceof Error ? error.message : String(error)];
|
|
359
|
+
this.executedMigrations.push(result);
|
|
360
|
+
logger.error("[MigrationEngine] Rollback failed", {
|
|
361
|
+
id: migrationId,
|
|
362
|
+
error: result.errors[0]
|
|
363
|
+
});
|
|
364
|
+
throw new Error(`Rollback for ${migrationId} failed: ${result.errors[0]}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Get migration state
|
|
369
|
+
*/
|
|
370
|
+
getState() {
|
|
371
|
+
return { ...this.state };
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Get migration execution history
|
|
375
|
+
*/
|
|
376
|
+
getExecutionHistory() {
|
|
377
|
+
return [...this.executedMigrations];
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Get migration by ID
|
|
381
|
+
*/
|
|
382
|
+
getMigration(migrationId) {
|
|
383
|
+
return this.migrations.get(migrationId);
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Get all registered migrations
|
|
387
|
+
*/
|
|
388
|
+
getAllMigrations() {
|
|
389
|
+
return Array.from(this.migrations.values());
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Get applied migrations
|
|
393
|
+
*/
|
|
394
|
+
getAppliedMigrations() {
|
|
395
|
+
return [...this.state.appliedMigrations];
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Get failed migrations
|
|
399
|
+
*/
|
|
400
|
+
getFailedMigrations() {
|
|
401
|
+
return [...this.state.failedMigrations];
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Get pending migrations
|
|
405
|
+
*/
|
|
406
|
+
getPendingMigrations() {
|
|
407
|
+
return this.getAllMigrations().filter(
|
|
408
|
+
(m) => !this.state.appliedMigrations.includes(m.id)
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Get migration statistics
|
|
413
|
+
*/
|
|
414
|
+
getStatistics() {
|
|
415
|
+
const successful = this.executedMigrations.filter((m) => m.success).length;
|
|
416
|
+
const failed = this.executedMigrations.filter((m) => !m.success).length;
|
|
417
|
+
const totalDuration = this.executedMigrations.reduce((sum, m) => sum + m.duration, 0);
|
|
418
|
+
const totalAffected = this.executedMigrations.reduce((sum, m) => sum + m.itemsAffected, 0);
|
|
419
|
+
return {
|
|
420
|
+
totalExecuted: this.executedMigrations.length,
|
|
421
|
+
successful,
|
|
422
|
+
failed,
|
|
423
|
+
successRate: this.executedMigrations.length > 0 ? successful / this.executedMigrations.length * 100 : 0,
|
|
424
|
+
totalDurationMs: totalDuration,
|
|
425
|
+
averageDurationMs: this.executedMigrations.length > 0 ? totalDuration / this.executedMigrations.length : 0,
|
|
426
|
+
totalAffected
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Clear history (for testing)
|
|
431
|
+
*/
|
|
432
|
+
clear() {
|
|
433
|
+
this.migrations.clear();
|
|
434
|
+
this.executedMigrations = [];
|
|
435
|
+
this.state = {
|
|
436
|
+
currentVersion: "1.0.0",
|
|
437
|
+
appliedMigrations: [],
|
|
438
|
+
failedMigrations: [],
|
|
439
|
+
lastMigrationTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
440
|
+
totalMigrationsRun: 0
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
// src/versioning/DataTransformer.ts
|
|
446
|
+
var DataTransformer = class {
|
|
447
|
+
rules = /* @__PURE__ */ new Map();
|
|
448
|
+
transformationHistory = [];
|
|
449
|
+
/**
|
|
450
|
+
* Register a transformation rule
|
|
451
|
+
*/
|
|
452
|
+
registerRule(rule) {
|
|
453
|
+
this.rules.set(rule.field, rule);
|
|
454
|
+
logger.debug("[DataTransformer] Rule registered", {
|
|
455
|
+
field: rule.field,
|
|
456
|
+
required: rule.required,
|
|
457
|
+
hasDefault: rule.defaultValue !== void 0
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Transform a single field value
|
|
462
|
+
*/
|
|
463
|
+
transformField(field, value) {
|
|
464
|
+
const rule = this.rules.get(field);
|
|
465
|
+
if (!rule) {
|
|
466
|
+
return value;
|
|
467
|
+
}
|
|
468
|
+
try {
|
|
469
|
+
return rule.transformer(value);
|
|
470
|
+
} catch (error) {
|
|
471
|
+
if (rule.required) {
|
|
472
|
+
throw new Error(`Failed to transform required field ${field}: ${error instanceof Error ? error.message : String(error)}`);
|
|
473
|
+
}
|
|
474
|
+
return rule.defaultValue !== void 0 ? rule.defaultValue : value;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Transform a single object
|
|
479
|
+
*/
|
|
480
|
+
transformObject(data) {
|
|
481
|
+
const transformed = {};
|
|
482
|
+
for (const [key, value] of Object.entries(data)) {
|
|
483
|
+
try {
|
|
484
|
+
transformed[key] = this.transformField(key, value);
|
|
485
|
+
} catch (error) {
|
|
486
|
+
logger.warn("[DataTransformer] Field transformation failed", {
|
|
487
|
+
field: key,
|
|
488
|
+
error: error instanceof Error ? error.message : String(error)
|
|
489
|
+
});
|
|
490
|
+
const rule = this.rules.get(key);
|
|
491
|
+
if (!rule || !rule.required) {
|
|
492
|
+
transformed[key] = value;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return transformed;
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Transform a collection of items
|
|
500
|
+
*/
|
|
501
|
+
transformCollection(items) {
|
|
502
|
+
const startTime = Date.now();
|
|
503
|
+
const result = {
|
|
504
|
+
success: true,
|
|
505
|
+
itemsTransformed: 0,
|
|
506
|
+
itemsFailed: 0,
|
|
507
|
+
errors: [],
|
|
508
|
+
warnings: [],
|
|
509
|
+
duration: 0
|
|
510
|
+
};
|
|
511
|
+
for (let i = 0; i < items.length; i++) {
|
|
512
|
+
const item = items[i];
|
|
513
|
+
try {
|
|
514
|
+
if (typeof item === "object" && item !== null && !Array.isArray(item)) {
|
|
515
|
+
this.transformObject(item);
|
|
516
|
+
result.itemsTransformed++;
|
|
517
|
+
} else {
|
|
518
|
+
result.warnings.push(`Item ${i} is not a transformable object`);
|
|
519
|
+
}
|
|
520
|
+
} catch (error) {
|
|
521
|
+
result.errors.push({
|
|
522
|
+
item,
|
|
523
|
+
error: error instanceof Error ? error.message : String(error)
|
|
524
|
+
});
|
|
525
|
+
result.itemsFailed++;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
result.duration = Date.now() - startTime;
|
|
529
|
+
result.success = result.itemsFailed === 0;
|
|
530
|
+
this.transformationHistory.push(result);
|
|
531
|
+
logger.debug("[DataTransformer] Collection transformed", {
|
|
532
|
+
total: items.length,
|
|
533
|
+
transformed: result.itemsTransformed,
|
|
534
|
+
failed: result.itemsFailed,
|
|
535
|
+
duration: result.duration
|
|
536
|
+
});
|
|
537
|
+
return result;
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Validate transformed data
|
|
541
|
+
*/
|
|
542
|
+
validateTransformation(original, transformed) {
|
|
543
|
+
const issues = [];
|
|
544
|
+
if (original.length !== transformed.length) {
|
|
545
|
+
issues.push(`Item count mismatch: ${original.length} -> ${transformed.length}`);
|
|
546
|
+
}
|
|
547
|
+
for (let i = 0; i < Math.min(original.length, transformed.length); i++) {
|
|
548
|
+
const orig = original[i];
|
|
549
|
+
const trans = transformed[i];
|
|
550
|
+
if (!this.validateItem(orig, trans)) {
|
|
551
|
+
issues.push(`Item ${i} validation failed`);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return {
|
|
555
|
+
valid: issues.length === 0,
|
|
556
|
+
issues
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Validate a single item transformation
|
|
561
|
+
*/
|
|
562
|
+
validateItem(original, transformed) {
|
|
563
|
+
if (original === null || original === void 0) {
|
|
564
|
+
return true;
|
|
565
|
+
}
|
|
566
|
+
if (typeof original === "object" && typeof transformed !== "object") {
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Get transformation history
|
|
573
|
+
*/
|
|
574
|
+
getTransformationHistory() {
|
|
575
|
+
return [...this.transformationHistory];
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Get transformation statistics
|
|
579
|
+
*/
|
|
580
|
+
getStatistics() {
|
|
581
|
+
const totalTransformed = this.transformationHistory.reduce(
|
|
582
|
+
(sum, r) => sum + r.itemsTransformed,
|
|
583
|
+
0
|
|
584
|
+
);
|
|
585
|
+
const totalFailed = this.transformationHistory.reduce(
|
|
586
|
+
(sum, r) => sum + r.itemsFailed,
|
|
587
|
+
0
|
|
588
|
+
);
|
|
589
|
+
const totalDuration = this.transformationHistory.reduce(
|
|
590
|
+
(sum, r) => sum + r.duration,
|
|
591
|
+
0
|
|
592
|
+
);
|
|
593
|
+
return {
|
|
594
|
+
totalBatches: this.transformationHistory.length,
|
|
595
|
+
totalTransformed,
|
|
596
|
+
totalFailed,
|
|
597
|
+
successRate: totalTransformed + totalFailed > 0 ? totalTransformed / (totalTransformed + totalFailed) * 100 : 0,
|
|
598
|
+
totalDurationMs: totalDuration,
|
|
599
|
+
averageBatchDurationMs: this.transformationHistory.length > 0 ? totalDuration / this.transformationHistory.length : 0
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Get registered rules
|
|
604
|
+
*/
|
|
605
|
+
getRules() {
|
|
606
|
+
return Array.from(this.rules.values());
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Get rule for field
|
|
610
|
+
*/
|
|
611
|
+
getRule(field) {
|
|
612
|
+
return this.rules.get(field);
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Clear all rules (for testing)
|
|
616
|
+
*/
|
|
617
|
+
clearRules() {
|
|
618
|
+
this.rules.clear();
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Clear history (for testing)
|
|
622
|
+
*/
|
|
623
|
+
clearHistory() {
|
|
624
|
+
this.transformationHistory = [];
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Clear all state (for testing)
|
|
628
|
+
*/
|
|
629
|
+
clear() {
|
|
630
|
+
this.clearRules();
|
|
631
|
+
this.clearHistory();
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
// src/versioning/MigrationTracker.ts
|
|
636
|
+
var MigrationTracker = class {
|
|
637
|
+
migrations = [];
|
|
638
|
+
snapshots = /* @__PURE__ */ new Map();
|
|
639
|
+
/**
|
|
640
|
+
* Track a new migration
|
|
641
|
+
*/
|
|
642
|
+
recordMigration(record) {
|
|
643
|
+
this.migrations.push({ ...record });
|
|
644
|
+
logger.debug("[MigrationTracker] Migration recorded", {
|
|
645
|
+
id: record.id,
|
|
646
|
+
migrationId: record.migrationId,
|
|
647
|
+
version: record.version,
|
|
648
|
+
status: record.status
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Track migration with snapshot
|
|
653
|
+
*/
|
|
654
|
+
trackMigration(migrationId, version, beforeHash, afterHash, itemCount, duration, itemsAffected, appliedBy = "system") {
|
|
655
|
+
const record = {
|
|
656
|
+
id: `${migrationId}-${Date.now()}`,
|
|
657
|
+
migrationId,
|
|
658
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
659
|
+
version,
|
|
660
|
+
direction: "up",
|
|
661
|
+
status: "applied",
|
|
662
|
+
duration,
|
|
663
|
+
itemsAffected,
|
|
664
|
+
dataSnapshot: {
|
|
665
|
+
beforeHash,
|
|
666
|
+
afterHash,
|
|
667
|
+
itemCount
|
|
668
|
+
},
|
|
669
|
+
appliedBy
|
|
670
|
+
};
|
|
671
|
+
this.recordMigration(record);
|
|
672
|
+
this.snapshots.set(record.id, {
|
|
673
|
+
beforeHash,
|
|
674
|
+
afterHash,
|
|
675
|
+
itemCount
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Get all migration records
|
|
680
|
+
*/
|
|
681
|
+
getMigrations() {
|
|
682
|
+
return this.migrations.map((m) => ({ ...m }));
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Get migrations for a specific version
|
|
686
|
+
*/
|
|
687
|
+
getMigrationsForVersion(version) {
|
|
688
|
+
return this.migrations.filter((m) => m.version === version);
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Get migration by ID
|
|
692
|
+
*/
|
|
693
|
+
getMigration(id) {
|
|
694
|
+
return this.migrations.find((m) => m.id === id);
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Check if can rollback
|
|
698
|
+
*/
|
|
699
|
+
canRollback(fromVersion, toVersion) {
|
|
700
|
+
const fromIndex = this.migrations.findIndex((m) => m.version === fromVersion);
|
|
701
|
+
const toIndex = this.migrations.findIndex((m) => m.version === toVersion);
|
|
702
|
+
if (fromIndex === -1 || toIndex === -1) {
|
|
703
|
+
return false;
|
|
704
|
+
}
|
|
705
|
+
if (toIndex >= fromIndex) {
|
|
706
|
+
return false;
|
|
707
|
+
}
|
|
708
|
+
for (let i = fromIndex; i > toIndex; i--) {
|
|
709
|
+
if (!this.migrations[i]?.dataSnapshot) {
|
|
710
|
+
return false;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
return true;
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Get rollback path
|
|
717
|
+
*/
|
|
718
|
+
getRollbackPath(fromVersion, toVersion) {
|
|
719
|
+
const canRollback = this.canRollback(fromVersion, toVersion);
|
|
720
|
+
const path = [];
|
|
721
|
+
const affectedVersions = [];
|
|
722
|
+
let estimatedDuration = 0;
|
|
723
|
+
if (canRollback) {
|
|
724
|
+
const fromIndex = this.migrations.findIndex((m) => m.version === fromVersion);
|
|
725
|
+
const toIndex = this.migrations.findIndex((m) => m.version === toVersion);
|
|
726
|
+
for (let i = fromIndex; i > toIndex; i--) {
|
|
727
|
+
const migration = this.migrations[i];
|
|
728
|
+
if (migration) {
|
|
729
|
+
path.push(migration.migrationId);
|
|
730
|
+
affectedVersions.push(migration.version);
|
|
731
|
+
estimatedDuration += migration.duration;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return {
|
|
736
|
+
path,
|
|
737
|
+
canRollback,
|
|
738
|
+
affectedVersions,
|
|
739
|
+
estimatedDuration
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Get applied migrations
|
|
744
|
+
*/
|
|
745
|
+
getAppliedMigrations() {
|
|
746
|
+
return this.migrations.filter((m) => m.status === "applied");
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Get failed migrations
|
|
750
|
+
*/
|
|
751
|
+
getFailedMigrations() {
|
|
752
|
+
return this.migrations.filter((m) => m.status === "failed");
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Get pending migrations
|
|
756
|
+
*/
|
|
757
|
+
getPendingMigrations() {
|
|
758
|
+
return this.migrations.filter((m) => m.status === "pending");
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Get latest migration
|
|
762
|
+
*/
|
|
763
|
+
getLatestMigration() {
|
|
764
|
+
return this.migrations[this.migrations.length - 1];
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Get migration timeline
|
|
768
|
+
*/
|
|
769
|
+
getTimeline() {
|
|
770
|
+
return this.migrations.map((m) => ({
|
|
771
|
+
timestamp: m.timestamp,
|
|
772
|
+
version: m.version,
|
|
773
|
+
status: m.status
|
|
774
|
+
}));
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Get migration statistics
|
|
778
|
+
*/
|
|
779
|
+
getStatistics() {
|
|
780
|
+
const applied = this.migrations.filter((m) => m.status === "applied").length;
|
|
781
|
+
const failed = this.migrations.filter((m) => m.status === "failed").length;
|
|
782
|
+
const pending = this.migrations.filter((m) => m.status === "pending").length;
|
|
783
|
+
const rolledBack = this.migrations.filter((m) => m.status === "rolled-back").length;
|
|
784
|
+
const totalDuration = this.migrations.reduce((sum, m) => sum + m.duration, 0);
|
|
785
|
+
const totalAffected = this.migrations.reduce((sum, m) => sum + m.itemsAffected, 0);
|
|
786
|
+
return {
|
|
787
|
+
total: this.migrations.length,
|
|
788
|
+
applied,
|
|
789
|
+
failed,
|
|
790
|
+
pending,
|
|
791
|
+
rolledBack,
|
|
792
|
+
successRate: this.migrations.length > 0 ? applied / this.migrations.length * 100 : 0,
|
|
793
|
+
totalDurationMs: totalDuration,
|
|
794
|
+
averageDurationMs: this.migrations.length > 0 ? totalDuration / this.migrations.length : 0,
|
|
795
|
+
totalItemsAffected: totalAffected
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Get audit trail
|
|
800
|
+
*/
|
|
801
|
+
getAuditTrail(migrationId) {
|
|
802
|
+
const filtered = migrationId ? this.migrations.filter((m) => m.migrationId === migrationId) : this.migrations;
|
|
803
|
+
return filtered.map((m) => ({
|
|
804
|
+
id: m.id,
|
|
805
|
+
timestamp: m.timestamp,
|
|
806
|
+
migrationId: m.migrationId,
|
|
807
|
+
version: m.version,
|
|
808
|
+
status: m.status,
|
|
809
|
+
appliedBy: m.appliedBy,
|
|
810
|
+
duration: m.duration,
|
|
811
|
+
itemsAffected: m.itemsAffected,
|
|
812
|
+
error: m.errorMessage
|
|
813
|
+
}));
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Get data snapshot for recovery
|
|
817
|
+
*/
|
|
818
|
+
getSnapshot(recordId) {
|
|
819
|
+
return this.snapshots.get(recordId);
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Update migration status
|
|
823
|
+
*/
|
|
824
|
+
updateMigrationStatus(recordId, status, error) {
|
|
825
|
+
const migration = this.migrations.find((m) => m.id === recordId);
|
|
826
|
+
if (migration) {
|
|
827
|
+
migration.status = status;
|
|
828
|
+
if (error) {
|
|
829
|
+
migration.errorMessage = error;
|
|
830
|
+
}
|
|
831
|
+
logger.debug("[MigrationTracker] Migration status updated", {
|
|
832
|
+
recordId,
|
|
833
|
+
status,
|
|
834
|
+
hasError: !!error
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Clear history (for testing)
|
|
840
|
+
*/
|
|
841
|
+
clear() {
|
|
842
|
+
this.migrations = [];
|
|
843
|
+
this.snapshots.clear();
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Get total migrations tracked
|
|
847
|
+
*/
|
|
848
|
+
getTotalMigrations() {
|
|
849
|
+
return this.migrations.length;
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Find migrations by time range
|
|
853
|
+
*/
|
|
854
|
+
getMigrationsByTimeRange(startTime, endTime) {
|
|
855
|
+
const start = new Date(startTime).getTime();
|
|
856
|
+
const end = new Date(endTime).getTime();
|
|
857
|
+
return this.migrations.filter((m) => {
|
|
858
|
+
const time = new Date(m.timestamp).getTime();
|
|
859
|
+
return time >= start && time <= end;
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
export { DataTransformer, MigrationEngine, MigrationTracker, SchemaVersionManager };
|
|
865
|
+
//# sourceMappingURL=index.js.map
|
|
866
|
+
//# sourceMappingURL=index.js.map
|