@decaf-ts/core 0.12.0 → 0.13.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/README.md +140 -1
- package/dist/core.cjs +1 -1
- package/dist/core.cjs.map +1 -1
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/lib/esm/index.d.ts +2 -1
- package/lib/esm/index.js +2 -2
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/migrations/Migration.d.ts +1 -1
- package/lib/esm/migrations/MigrationService.d.ts +45 -2
- package/lib/esm/migrations/MigrationService.js +436 -97
- package/lib/esm/migrations/MigrationService.js.map +1 -1
- package/lib/esm/migrations/MigrationTaskBuilder.d.ts +10 -0
- package/lib/esm/migrations/MigrationTaskBuilder.js +21 -0
- package/lib/esm/migrations/MigrationTaskBuilder.js.map +1 -0
- package/lib/esm/migrations/MigrationTasks.js +10 -4
- package/lib/esm/migrations/MigrationTasks.js.map +1 -1
- package/lib/esm/migrations/MigrationVersioning.d.ts +7 -0
- package/lib/esm/migrations/MigrationVersioning.js +2 -0
- package/lib/esm/migrations/MigrationVersioning.js.map +1 -0
- package/lib/esm/migrations/SemverMigrationVersioning.d.ts +10 -0
- package/lib/esm/migrations/SemverMigrationVersioning.js +43 -0
- package/lib/esm/migrations/SemverMigrationVersioning.js.map +1 -0
- package/lib/esm/migrations/StandardMigrationVersioning.d.ts +12 -0
- package/lib/esm/migrations/StandardMigrationVersioning.js +27 -0
- package/lib/esm/migrations/StandardMigrationVersioning.js.map +1 -0
- package/lib/esm/migrations/decorators.d.ts +5 -0
- package/lib/esm/migrations/decorators.js +42 -11
- package/lib/esm/migrations/decorators.js.map +1 -1
- package/lib/esm/migrations/index.d.ts +3 -0
- package/lib/esm/migrations/index.js +3 -0
- package/lib/esm/migrations/index.js.map +1 -1
- package/lib/esm/migrations/types.d.ts +25 -2
- package/lib/esm/persistence/Adapter.d.ts +1 -1
- package/lib/esm/persistence/constants.js +6 -1
- package/lib/esm/persistence/constants.js.map +1 -1
- package/lib/esm/query/Paginator.js +16 -5
- package/lib/esm/query/Paginator.js.map +1 -1
- package/lib/esm/query/constants.d.ts +1 -0
- package/lib/esm/query/constants.js +1 -0
- package/lib/esm/query/constants.js.map +1 -1
- package/lib/esm/query/decorators.js +8 -3
- package/lib/esm/query/decorators.js.map +1 -1
- package/lib/esm/repository/Repository.d.ts +1 -0
- package/lib/esm/repository/Repository.js +51 -0
- package/lib/esm/repository/Repository.js.map +1 -1
- package/lib/esm/services/ModelService.d.ts +1 -0
- package/lib/esm/services/ModelService.js +4 -0
- package/lib/esm/services/ModelService.js.map +1 -1
- package/lib/esm/tasks/TaskService.d.ts +1 -0
- package/lib/esm/tasks/TaskService.js +4 -0
- package/lib/esm/tasks/TaskService.js.map +1 -1
- package/lib/index.cjs +2 -2
- package/lib/index.d.ts +2 -1
- package/lib/index.js.map +1 -1
- package/lib/migrations/Migration.d.ts +1 -1
- package/lib/migrations/MigrationService.cjs +436 -97
- package/lib/migrations/MigrationService.d.ts +45 -2
- package/lib/migrations/MigrationService.js.map +1 -1
- package/lib/migrations/MigrationTaskBuilder.cjs +25 -0
- package/lib/migrations/MigrationTaskBuilder.d.ts +10 -0
- package/lib/migrations/MigrationTaskBuilder.js.map +1 -0
- package/lib/migrations/MigrationTasks.cjs +10 -4
- package/lib/migrations/MigrationTasks.js.map +1 -1
- package/lib/migrations/MigrationVersioning.cjs +3 -0
- package/lib/migrations/MigrationVersioning.d.ts +7 -0
- package/lib/migrations/MigrationVersioning.js.map +1 -0
- package/lib/migrations/SemverMigrationVersioning.cjs +80 -0
- package/lib/migrations/SemverMigrationVersioning.d.ts +10 -0
- package/lib/migrations/SemverMigrationVersioning.js.map +1 -0
- package/lib/migrations/StandardMigrationVersioning.cjs +31 -0
- package/lib/migrations/StandardMigrationVersioning.d.ts +12 -0
- package/lib/migrations/StandardMigrationVersioning.js.map +1 -0
- package/lib/migrations/decorators.cjs +42 -11
- package/lib/migrations/decorators.d.ts +5 -0
- package/lib/migrations/decorators.js.map +1 -1
- package/lib/migrations/index.cjs +3 -0
- package/lib/migrations/index.d.ts +3 -0
- package/lib/migrations/index.js.map +1 -1
- package/lib/migrations/types.d.ts +25 -2
- package/lib/persistence/Adapter.d.ts +1 -1
- package/lib/persistence/constants.cjs +6 -1
- package/lib/persistence/constants.js.map +1 -1
- package/lib/query/Paginator.cjs +16 -5
- package/lib/query/Paginator.js.map +1 -1
- package/lib/query/constants.cjs +1 -0
- package/lib/query/constants.d.ts +1 -0
- package/lib/query/constants.js.map +1 -1
- package/lib/query/decorators.cjs +8 -3
- package/lib/query/decorators.js.map +1 -1
- package/lib/repository/Repository.cjs +51 -0
- package/lib/repository/Repository.d.ts +1 -0
- package/lib/repository/Repository.js.map +1 -1
- package/lib/services/ModelService.cjs +4 -0
- package/lib/services/ModelService.d.ts +1 -0
- package/lib/services/ModelService.js.map +1 -1
- package/lib/tasks/TaskService.cjs +4 -0
- package/lib/tasks/TaskService.d.ts +1 -0
- package/lib/tasks/TaskService.js.map +1 -1
- package/package.json +9 -1
|
@@ -9,12 +9,50 @@ const errors_1 = require("./../persistence/errors.cjs");
|
|
|
9
9
|
const logging_1 = require("@decaf-ts/logging");
|
|
10
10
|
const decoration_1 = require("@decaf-ts/decoration");
|
|
11
11
|
const db_decorators_1 = require("@decaf-ts/db-decorators");
|
|
12
|
+
const MigrationTaskBuilder_1 = require("./MigrationTaskBuilder.cjs");
|
|
13
|
+
const TaskModel_1 = require("./../tasks/models/TaskModel.cjs");
|
|
14
|
+
const StandardMigrationVersioning_1 = require("./StandardMigrationVersioning.cjs");
|
|
15
|
+
const constants_3 = require("./../tasks/constants.cjs");
|
|
12
16
|
class MigrationService extends services_1.ClientBasedService {
|
|
13
17
|
constructor() {
|
|
14
18
|
super();
|
|
19
|
+
this.versioning = new StandardMigrationVersioning_1.StandardMigrationVersioning();
|
|
20
|
+
this.queuedTaskChain = [];
|
|
15
21
|
this.reference = MigrationService.name;
|
|
16
22
|
this.precedence = null;
|
|
17
23
|
}
|
|
24
|
+
static async migrateAdapters(adapters, cfg = {}, ...args) {
|
|
25
|
+
const flavours = cfg.flavours?.length ? new Set(cfg.flavours) : undefined;
|
|
26
|
+
const selected = adapters.filter((adapter) => !flavours || flavours.has(adapter.flavour));
|
|
27
|
+
const migratingAliases = new Set(adapters.map((adapter) => adapter.alias || adapter.flavour).filter(Boolean));
|
|
28
|
+
const taskEngineClient = cfg.taskService?.client;
|
|
29
|
+
const taskServiceAdapterAlias = taskEngineClient?.adapter?.alias || taskEngineClient?.adapter?.flavour;
|
|
30
|
+
if (taskServiceAdapterAlias &&
|
|
31
|
+
migratingAliases.has(taskServiceAdapterAlias)) {
|
|
32
|
+
throw new db_decorators_1.InternalError(`TaskEngine adapter alias "${taskServiceAdapterAlias}" cannot participate in the migration targets`);
|
|
33
|
+
}
|
|
34
|
+
const services = [];
|
|
35
|
+
for (const adapter of selected) {
|
|
36
|
+
const handlers = cfg.handlers?.[adapter.flavour] || {};
|
|
37
|
+
const migrationService = new MigrationService();
|
|
38
|
+
services.push(migrationService);
|
|
39
|
+
await migrationService.boot({
|
|
40
|
+
persistenceFlavour: adapter.flavour,
|
|
41
|
+
targetVersion: cfg.toVersion,
|
|
42
|
+
taskMode: !!cfg.taskMode,
|
|
43
|
+
// In multi-adapter task mode, run flavour-scoped migrations only.
|
|
44
|
+
includeGenericInTaskMode: !(cfg.taskMode && selected.length > 1),
|
|
45
|
+
retrieveLastVersion: handlers.retrieveLastVersion,
|
|
46
|
+
setCurrentVersion: handlers.setCurrentVersion,
|
|
47
|
+
taskService: cfg.taskService,
|
|
48
|
+
});
|
|
49
|
+
if (cfg.taskMode)
|
|
50
|
+
await migrationService.migrateViaTasks(undefined, undefined, ...args);
|
|
51
|
+
else
|
|
52
|
+
await migrationService.migrateNormally(undefined, undefined, ...args);
|
|
53
|
+
}
|
|
54
|
+
return services;
|
|
55
|
+
}
|
|
18
56
|
async initialize(...args) {
|
|
19
57
|
const { log, ctx } = (await this.logCtx(args, constants_2.PersistenceKeys.INITIALIZATION, true)).for(this.initialize);
|
|
20
58
|
try {
|
|
@@ -27,134 +65,435 @@ class MigrationService extends services_1.ClientBasedService {
|
|
|
27
65
|
log.warn(`Persistence service not available. this may indicate poor initialization of the persistence layer (or not)`);
|
|
28
66
|
}
|
|
29
67
|
const cfg = Object.assign({}, args.length ? args[0] : constants_1.DefaultMigrationConfig, constants_1.DefaultMigrationConfig);
|
|
68
|
+
this.versioning = cfg.versioning || new StandardMigrationVersioning_1.StandardMigrationVersioning();
|
|
30
69
|
this.transaction = cfg.persistMigrationSteps;
|
|
31
70
|
return {
|
|
32
71
|
config: cfg,
|
|
33
|
-
client:
|
|
72
|
+
client: (cfg.persistenceFlavour
|
|
73
|
+
? Adapter_1.Adapter.get(cfg.persistenceFlavour)
|
|
74
|
+
: undefined),
|
|
34
75
|
};
|
|
35
76
|
}
|
|
36
77
|
async down(qr, adapter, ...args) {
|
|
37
78
|
const { log } = this.logCtx(args, this.down);
|
|
38
79
|
log.verbose((0, logging_1.style)("Cleaning up after all migrations").green.bold);
|
|
39
80
|
}
|
|
81
|
+
normalizeVersion(raw, precedenceHint) {
|
|
82
|
+
return this.versioning.normalize(raw, precedenceHint);
|
|
83
|
+
}
|
|
84
|
+
extractPrecedenceTokens(migration) {
|
|
85
|
+
const precedence = migration.precedence;
|
|
86
|
+
if (!precedence)
|
|
87
|
+
return [];
|
|
88
|
+
const list = Array.isArray(precedence) ? precedence : [precedence];
|
|
89
|
+
return list
|
|
90
|
+
.map((entry) => {
|
|
91
|
+
if (!entry)
|
|
92
|
+
return undefined;
|
|
93
|
+
if (typeof entry === "string")
|
|
94
|
+
return undefined;
|
|
95
|
+
if (typeof entry === "function")
|
|
96
|
+
return entry.name;
|
|
97
|
+
if (typeof entry.reference === "string")
|
|
98
|
+
return entry.reference;
|
|
99
|
+
if (entry.constructor?.name)
|
|
100
|
+
return entry.constructor.name;
|
|
101
|
+
return undefined;
|
|
102
|
+
})
|
|
103
|
+
.filter((entry) => !!entry);
|
|
104
|
+
}
|
|
105
|
+
referencesMigration(candidate, target) {
|
|
106
|
+
const tokens = this.extractPrecedenceTokens(candidate);
|
|
107
|
+
if (!tokens.length)
|
|
108
|
+
return false;
|
|
109
|
+
return (tokens.includes(target.reference) ||
|
|
110
|
+
tokens.includes(target.migration?.constructor?.name));
|
|
111
|
+
}
|
|
112
|
+
precedenceHint(migration) {
|
|
113
|
+
return typeof migration.precedence === "string"
|
|
114
|
+
? migration.precedence
|
|
115
|
+
: undefined;
|
|
116
|
+
}
|
|
117
|
+
compareByPrecedence(migration1, migration2) {
|
|
118
|
+
const m1 = migration1.migration;
|
|
119
|
+
const m2 = migration2.migration;
|
|
120
|
+
const m1ReferencesM2 = this.referencesMigration(m1, migration2);
|
|
121
|
+
const m2ReferencesM1 = this.referencesMigration(m2, migration1);
|
|
122
|
+
if (m1ReferencesM2 && !m2ReferencesM1)
|
|
123
|
+
return 1;
|
|
124
|
+
if (m2ReferencesM1 && !m1ReferencesM2)
|
|
125
|
+
return -1;
|
|
126
|
+
const p1 = this.extractPrecedenceTokens(m1);
|
|
127
|
+
const p2 = this.extractPrecedenceTokens(m2);
|
|
128
|
+
if (p1.length !== p2.length)
|
|
129
|
+
return p1.length - p2.length;
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
40
132
|
sort(migrations) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (
|
|
47
|
-
return
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
? migration2.precedence
|
|
53
|
-
: [migration2];
|
|
54
|
-
const includes1 = precedences1.every((p) => p === migration2);
|
|
55
|
-
const includes2 = precedences2.every((p) => p === migration1);
|
|
56
|
-
// sort when they reference each other
|
|
57
|
-
if (includes1 && !includes2)
|
|
58
|
-
return 1;
|
|
59
|
-
if (includes2 && !includes1)
|
|
60
|
-
return -1;
|
|
61
|
-
const anyNotIncluded1 = precedences1.find((p) => !precedences2.includes(p));
|
|
62
|
-
const allIncluded1 = precedences1.every((p) => precedences2.includes(p));
|
|
63
|
-
const anyNotIncluded2 = precedences2.find((p) => !precedences1.includes(p));
|
|
64
|
-
const allIncluded2 = precedences2.every((p) => precedences1.includes(p));
|
|
65
|
-
// solve all differences
|
|
66
|
-
if (anyNotIncluded1 && !anyNotIncluded2 && !allIncluded1 && !allIncluded2)
|
|
67
|
-
return 1;
|
|
68
|
-
if (!anyNotIncluded1 && anyNotIncluded2 && !allIncluded1 && !allIncluded2)
|
|
69
|
-
return -1;
|
|
70
|
-
if (!anyNotIncluded1 && !anyNotIncluded2 && allIncluded1 && !allIncluded2)
|
|
71
|
-
return -1;
|
|
72
|
-
if (!anyNotIncluded1 && !anyNotIncluded2 && !allIncluded1 && allIncluded2)
|
|
73
|
-
return 1;
|
|
74
|
-
if (!anyNotIncluded1 && !anyNotIncluded2 && allIncluded1 && !allIncluded2)
|
|
75
|
-
return -1;
|
|
76
|
-
if (!anyNotIncluded1 && !anyNotIncluded2 && !allIncluded1 && allIncluded2)
|
|
77
|
-
return 1;
|
|
78
|
-
if (!anyNotIncluded1 && anyNotIncluded2 && !allIncluded1 && !allIncluded2)
|
|
79
|
-
return 1;
|
|
80
|
-
if (!anyNotIncluded1 &&
|
|
81
|
-
!anyNotIncluded2 &&
|
|
82
|
-
!allIncluded1 &&
|
|
83
|
-
!allIncluded2)
|
|
84
|
-
return -1;
|
|
85
|
-
const size1 = precedences1.length;
|
|
86
|
-
const size2 = precedences2.length;
|
|
87
|
-
const res = size1 - size2;
|
|
88
|
-
if (res === 0) {
|
|
89
|
-
if (migration1.reference === migration2.reference)
|
|
90
|
-
throw new db_decorators_1.InternalError(`Unable to sort migration precedence between ${migration1.reference} and ${migration2.reference}. should not be possible`);
|
|
91
|
-
return migration1.reference.localeCompare(migration2.reference);
|
|
92
|
-
}
|
|
93
|
-
return res;
|
|
133
|
+
const sorted = [...migrations].sort((migration1, migration2) => {
|
|
134
|
+
const semverDelta = this.versioning.compare(migration1.version, migration2.version);
|
|
135
|
+
if (semverDelta !== 0)
|
|
136
|
+
return semverDelta;
|
|
137
|
+
const precedenceDelta = this.compareByPrecedence(migration1, migration2);
|
|
138
|
+
if (precedenceDelta !== 0)
|
|
139
|
+
return precedenceDelta;
|
|
140
|
+
const flavourDelta = migration1.flavour.localeCompare(migration2.flavour);
|
|
141
|
+
if (flavourDelta !== 0)
|
|
142
|
+
return flavourDelta;
|
|
143
|
+
return migration1.reference.localeCompare(migration2.reference);
|
|
94
144
|
});
|
|
145
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
146
|
+
const left = sorted[i];
|
|
147
|
+
const right = sorted[i + 1];
|
|
148
|
+
if (!left || !right)
|
|
149
|
+
continue;
|
|
150
|
+
if (left.version !== right.version || left.flavour !== right.flavour)
|
|
151
|
+
continue;
|
|
152
|
+
if (this.compareByPrecedence(left, right) !== 0)
|
|
153
|
+
continue;
|
|
154
|
+
throw new db_decorators_1.InternalError(`Unable to deterministically sort flavour migrations for version ${left.version} and flavour ${left.flavour}: ${left.reference} vs ${right.reference}`);
|
|
155
|
+
}
|
|
156
|
+
return sorted;
|
|
95
157
|
}
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
158
|
+
resolveMigration(migration) {
|
|
159
|
+
const meta = decoration_1.Metadata.get(migration.constructor, constants_2.PersistenceKeys.MIGRATION);
|
|
160
|
+
const flavour = meta?.flavour || migration.flavour || decoration_1.DefaultFlavour;
|
|
161
|
+
const reference = meta?.reference || migration.reference;
|
|
162
|
+
const precedenceHint = this.precedenceHint(migration);
|
|
163
|
+
return {
|
|
164
|
+
migration,
|
|
165
|
+
flavour,
|
|
166
|
+
reference,
|
|
167
|
+
version: this.normalizeVersion(reference, precedenceHint),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
shouldIncludeMigration(migration, targetFlavour, includeGeneric = true) {
|
|
171
|
+
if (!targetFlavour)
|
|
172
|
+
return true;
|
|
173
|
+
if (migration.flavour === targetFlavour)
|
|
174
|
+
return true;
|
|
175
|
+
return includeGeneric && migration.flavour === decoration_1.DefaultFlavour;
|
|
176
|
+
}
|
|
177
|
+
collectMigrations(targetFlavour, includeGeneric = true) {
|
|
99
178
|
const toBoot = decoration_1.Metadata.migrations();
|
|
100
179
|
const migrations = [];
|
|
101
|
-
for (const [reference,
|
|
180
|
+
for (const [reference, MigrationClass] of toBoot) {
|
|
181
|
+
let migration;
|
|
102
182
|
try {
|
|
103
|
-
|
|
104
|
-
m = new mig();
|
|
105
|
-
log.verbose(`migration ${m.reference} instantiated`, 1);
|
|
183
|
+
migration = new MigrationClass();
|
|
106
184
|
}
|
|
107
185
|
catch (e) {
|
|
108
|
-
throw new db_decorators_1.InternalError(`failed to create migration ${
|
|
186
|
+
throw new db_decorators_1.InternalError(`failed to create migration ${reference}: ${e}`);
|
|
109
187
|
}
|
|
110
|
-
|
|
188
|
+
const resolved = this.resolveMigration(migration);
|
|
189
|
+
if (!this.shouldIncludeMigration(resolved, targetFlavour, includeGeneric))
|
|
190
|
+
continue;
|
|
191
|
+
migrations.push(resolved);
|
|
192
|
+
}
|
|
193
|
+
return migrations;
|
|
194
|
+
}
|
|
195
|
+
buildExecutionPlan(options) {
|
|
196
|
+
const fromVersion = options?.fromVersion
|
|
197
|
+
? this.normalizeVersion(options.fromVersion)
|
|
198
|
+
: undefined;
|
|
199
|
+
const toVersion = options?.toVersion
|
|
200
|
+
? this.normalizeVersion(options.toVersion)
|
|
201
|
+
: undefined;
|
|
202
|
+
const migrations = this.sort(this.collectMigrations(options?.targetFlavour, options?.includeGeneric));
|
|
203
|
+
return migrations.filter((migration) => {
|
|
204
|
+
if (fromVersion && !this.versioning.gt(migration.version, fromVersion))
|
|
205
|
+
return false;
|
|
206
|
+
if (toVersion && !this.versioning.lte(migration.version, toVersion))
|
|
207
|
+
return false;
|
|
208
|
+
return true;
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
async executeMigration(migration, ...args) {
|
|
212
|
+
const m = migration.migration;
|
|
213
|
+
let adapter;
|
|
214
|
+
let qr;
|
|
215
|
+
try {
|
|
216
|
+
adapter = Adapter_1.Adapter.get(migration.flavour);
|
|
217
|
+
if (!adapter)
|
|
218
|
+
throw new db_decorators_1.InternalError(`failed to create migration ${m.reference}. did you call Service.boot() or use the Persistence Service??`);
|
|
219
|
+
qr = adapter.client;
|
|
220
|
+
}
|
|
221
|
+
catch (e) {
|
|
222
|
+
throw new db_decorators_1.InternalError(`Failed to load adapter to migrate ${m.reference}: ${e}`);
|
|
111
223
|
}
|
|
112
|
-
let sortedMigrations;
|
|
113
224
|
try {
|
|
114
|
-
|
|
225
|
+
await m.up(qr, adapter, ...args);
|
|
115
226
|
}
|
|
116
227
|
catch (e) {
|
|
117
|
-
throw new
|
|
228
|
+
throw new errors_1.MigrationError(`failed to initialize migration ${m.reference}: ${e}`);
|
|
118
229
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
230
|
+
try {
|
|
231
|
+
await m.migrate(qr, adapter, ...args);
|
|
232
|
+
}
|
|
233
|
+
catch (e) {
|
|
234
|
+
throw new errors_1.MigrationError(`failed to migrate ${m.reference}: ${e}`);
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
await m.down(qr, adapter, ...args);
|
|
238
|
+
}
|
|
239
|
+
catch (e) {
|
|
240
|
+
throw new errors_1.MigrationError(`failed to conclude migration ${m.reference}: ${e}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
buildMigrationTaskForPlan(plan, ...args) {
|
|
244
|
+
const stepArgs = [...args];
|
|
245
|
+
const builder = new MigrationTaskBuilder_1.MigrationTaskBuilder();
|
|
246
|
+
plan.forEach((migration) => {
|
|
247
|
+
const input = {
|
|
248
|
+
reference: migration.reference,
|
|
249
|
+
args: stepArgs,
|
|
250
|
+
};
|
|
251
|
+
builder.addMigrationStep(input);
|
|
252
|
+
});
|
|
253
|
+
return builder.build();
|
|
254
|
+
}
|
|
255
|
+
buildMigrationTasksForPlan(plan, ...args) {
|
|
256
|
+
const byVersion = new Map();
|
|
257
|
+
for (const migration of plan) {
|
|
258
|
+
const current = byVersion.get(migration.version) || [];
|
|
259
|
+
current.push(migration);
|
|
260
|
+
byVersion.set(migration.version, current);
|
|
261
|
+
}
|
|
262
|
+
return [...byVersion.entries()].map(([version, migrations]) => ({
|
|
263
|
+
version,
|
|
264
|
+
task: this.buildMigrationTaskForPlan(migrations, ...args),
|
|
265
|
+
}));
|
|
266
|
+
}
|
|
267
|
+
createMigrationTask(targetVersion, fromVersion, ...args) {
|
|
268
|
+
const cfg = this.config;
|
|
269
|
+
const taskMode = cfg.taskMode ?? false;
|
|
270
|
+
const includeGeneric = taskMode
|
|
271
|
+
? cfg.includeGenericInTaskMode ?? true
|
|
272
|
+
: true;
|
|
273
|
+
const plan = this.buildExecutionPlan({
|
|
274
|
+
toVersion: targetVersion || cfg.targetVersion,
|
|
275
|
+
fromVersion,
|
|
276
|
+
targetFlavour: cfg.persistenceFlavour,
|
|
277
|
+
includeGeneric,
|
|
278
|
+
});
|
|
279
|
+
return this.buildMigrationTaskForPlan(plan, ...args);
|
|
280
|
+
}
|
|
281
|
+
createMigrationTasks(targetVersion, fromVersion, ...args) {
|
|
282
|
+
const cfg = this.config;
|
|
283
|
+
const taskMode = cfg.taskMode ?? false;
|
|
284
|
+
const includeGeneric = taskMode
|
|
285
|
+
? cfg.includeGenericInTaskMode ?? true
|
|
286
|
+
: true;
|
|
287
|
+
const plan = this.buildExecutionPlan({
|
|
288
|
+
toVersion: targetVersion || cfg.targetVersion,
|
|
289
|
+
fromVersion,
|
|
290
|
+
targetFlavour: cfg.persistenceFlavour,
|
|
291
|
+
includeGeneric,
|
|
292
|
+
});
|
|
293
|
+
return this.buildMigrationTasksForPlan(plan, ...args);
|
|
294
|
+
}
|
|
295
|
+
migrationTaskIdsFromContext(taskKey, args) {
|
|
296
|
+
const last = args[args.length - 1];
|
|
297
|
+
const pending = last?.pending?.() || last?.pending || {};
|
|
298
|
+
const values = pending?.[taskKey];
|
|
299
|
+
if (!Array.isArray(values))
|
|
300
|
+
return [];
|
|
301
|
+
return values.filter((value) => typeof value === "string");
|
|
302
|
+
}
|
|
303
|
+
async migrate(qr, adapter, ...args) {
|
|
304
|
+
void qr;
|
|
305
|
+
void adapter;
|
|
306
|
+
if (this.config.taskMode)
|
|
307
|
+
await this.migrateViaTasks(undefined, undefined, ...args);
|
|
308
|
+
else
|
|
309
|
+
await this.migrateNormally(undefined, undefined, ...args);
|
|
310
|
+
return undefined;
|
|
311
|
+
}
|
|
312
|
+
async migrateNormally(qr, adapter, ...args) {
|
|
313
|
+
const { ctxArgs, log } = (await this.logCtx(args, constants_2.PersistenceKeys.MIGRATION, true)).for(this.migrateNormally);
|
|
314
|
+
void qr;
|
|
315
|
+
void adapter;
|
|
316
|
+
const cfg = this.config;
|
|
317
|
+
const targetFlavour = cfg.persistenceFlavour;
|
|
318
|
+
const includeGeneric = cfg.taskMode
|
|
319
|
+
? cfg.includeGenericInTaskMode ?? true
|
|
320
|
+
: true;
|
|
321
|
+
const scopedAdapter = targetFlavour
|
|
322
|
+
? Adapter_1.Adapter.get(targetFlavour)
|
|
323
|
+
: undefined;
|
|
324
|
+
let currentVersion;
|
|
325
|
+
if (cfg.retrieveLastVersion && scopedAdapter) {
|
|
326
|
+
const retrieved = await cfg.retrieveLastVersion(scopedAdapter, ...ctxArgs);
|
|
327
|
+
if (retrieved)
|
|
328
|
+
currentVersion = this.normalizeVersion(retrieved);
|
|
329
|
+
}
|
|
330
|
+
const plan = this.buildExecutionPlan({
|
|
331
|
+
fromVersion: currentVersion,
|
|
332
|
+
toVersion: cfg.targetVersion,
|
|
333
|
+
targetFlavour,
|
|
334
|
+
includeGeneric,
|
|
335
|
+
});
|
|
336
|
+
log.debug(`sorted migration before execution: ${plan.map((s) => `${s.reference}@${s.version}`)}`);
|
|
337
|
+
for (const migration of plan) {
|
|
338
|
+
await this.executeMigration(migration, ...ctxArgs);
|
|
339
|
+
}
|
|
340
|
+
if (cfg.setCurrentVersion && scopedAdapter) {
|
|
341
|
+
const finalVersion = cfg.targetVersion ||
|
|
342
|
+
plan[plan.length - 1]?.version ||
|
|
343
|
+
currentVersion ||
|
|
344
|
+
undefined;
|
|
345
|
+
if (finalVersion)
|
|
346
|
+
await cfg.setCurrentVersion(this.normalizeVersion(finalVersion), scopedAdapter, ...ctxArgs);
|
|
347
|
+
}
|
|
348
|
+
return undefined;
|
|
349
|
+
}
|
|
350
|
+
async migrateViaTasks(qr, adapter, ...args) {
|
|
351
|
+
const { ctx, ctxArgs, log } = (await this.logCtx(args, constants_2.PersistenceKeys.MIGRATION, true)).for(this.migrateViaTasks);
|
|
352
|
+
void qr;
|
|
353
|
+
void adapter;
|
|
354
|
+
const cfg = this.config;
|
|
355
|
+
const targetFlavour = cfg.persistenceFlavour;
|
|
356
|
+
const includeGeneric = cfg.includeGenericInTaskMode ?? true;
|
|
357
|
+
const scopedAdapter = targetFlavour
|
|
358
|
+
? Adapter_1.Adapter.get(targetFlavour)
|
|
359
|
+
: undefined;
|
|
360
|
+
let currentVersion;
|
|
361
|
+
if (cfg.retrieveLastVersion && scopedAdapter) {
|
|
362
|
+
const retrieved = await cfg.retrieveLastVersion(scopedAdapter, ...ctxArgs);
|
|
363
|
+
if (retrieved)
|
|
364
|
+
currentVersion = this.normalizeVersion(retrieved);
|
|
365
|
+
}
|
|
366
|
+
const plan = this.buildExecutionPlan({
|
|
367
|
+
fromVersion: currentVersion,
|
|
368
|
+
toVersion: cfg.targetVersion,
|
|
369
|
+
targetFlavour,
|
|
370
|
+
includeGeneric,
|
|
371
|
+
});
|
|
372
|
+
log.debug(`sorted migration before execution: ${plan.map((s) => `${s.reference}@${s.version}`)}`);
|
|
373
|
+
this.queuedTaskChain = [];
|
|
374
|
+
if (plan.length) {
|
|
375
|
+
const tasks = this.buildMigrationTasksForPlan(plan, ...ctxArgs);
|
|
376
|
+
if (cfg.taskService) {
|
|
377
|
+
let dependsOnTaskId;
|
|
378
|
+
for (const versionTask of tasks) {
|
|
379
|
+
if (dependsOnTaskId) {
|
|
380
|
+
versionTask.task.dependencies = [
|
|
381
|
+
...(versionTask.task.dependencies || []),
|
|
382
|
+
dependsOnTaskId,
|
|
383
|
+
];
|
|
384
|
+
}
|
|
385
|
+
const created = await cfg.taskService.push(versionTask.task, false, ...ctxArgs);
|
|
386
|
+
dependsOnTaskId = created.id;
|
|
387
|
+
this.queuedTaskChain.push({
|
|
388
|
+
id: created.id,
|
|
389
|
+
version: versionTask.version,
|
|
390
|
+
});
|
|
391
|
+
if (!created?.id) {
|
|
392
|
+
log.warn(`TaskService.push returned missing id for version ${versionTask.version}`);
|
|
393
|
+
}
|
|
394
|
+
ctx.pushPending(constants_2.PersistenceKeys.MIGRATION, created.id);
|
|
395
|
+
}
|
|
148
396
|
}
|
|
149
|
-
|
|
150
|
-
|
|
397
|
+
else {
|
|
398
|
+
for (const migration of plan) {
|
|
399
|
+
await this.executeMigration(migration, ...ctxArgs);
|
|
400
|
+
}
|
|
401
|
+
if (cfg.setCurrentVersion && scopedAdapter) {
|
|
402
|
+
const finalVersion = cfg.targetVersion ||
|
|
403
|
+
plan[plan.length - 1]?.version ||
|
|
404
|
+
currentVersion ||
|
|
405
|
+
undefined;
|
|
406
|
+
if (finalVersion)
|
|
407
|
+
await cfg.setCurrentVersion(this.normalizeVersion(finalVersion), scopedAdapter, ...ctxArgs);
|
|
408
|
+
}
|
|
151
409
|
}
|
|
152
410
|
}
|
|
153
411
|
return undefined;
|
|
154
412
|
}
|
|
413
|
+
async track(taskIds, ...args) {
|
|
414
|
+
const { ctxArgs } = (await this.logCtx(args, constants_2.PersistenceKeys.MIGRATION, true)).for(this.track);
|
|
415
|
+
const cfg = this.config;
|
|
416
|
+
if (!cfg.taskService || !cfg.taskMode)
|
|
417
|
+
return;
|
|
418
|
+
const explicitTaskIds = typeof taskIds === "string"
|
|
419
|
+
? [taskIds]
|
|
420
|
+
: Array.isArray(taskIds)
|
|
421
|
+
? taskIds
|
|
422
|
+
: undefined;
|
|
423
|
+
const queuedIds = this.queuedTaskChain.map((task) => task.id);
|
|
424
|
+
const pendingIds = this.migrationTaskIdsFromContext(constants_2.PersistenceKeys.MIGRATION, ctxArgs);
|
|
425
|
+
const ids = explicitTaskIds?.length
|
|
426
|
+
? explicitTaskIds
|
|
427
|
+
: queuedIds.length
|
|
428
|
+
? queuedIds
|
|
429
|
+
: pendingIds;
|
|
430
|
+
if (!ids.length)
|
|
431
|
+
return;
|
|
432
|
+
const versionByTaskId = this.queuedTaskChain.reduce((acc, task) => {
|
|
433
|
+
acc[task.id] = task.version;
|
|
434
|
+
return acc;
|
|
435
|
+
}, {});
|
|
436
|
+
const scopedAdapter = this.config.persistenceFlavour
|
|
437
|
+
? Adapter_1.Adapter.get(this.config.persistenceFlavour)
|
|
438
|
+
: undefined;
|
|
439
|
+
for (const id of ids) {
|
|
440
|
+
const { tracker } = await cfg.taskService.track(id, ...ctxArgs);
|
|
441
|
+
await tracker.wait();
|
|
442
|
+
if (cfg.setCurrentVersion && scopedAdapter && versionByTaskId[id]) {
|
|
443
|
+
await cfg.setCurrentVersion(this.normalizeVersion(versionByTaskId[id]), scopedAdapter, ...ctxArgs);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
async retry(taskIds, ...args) {
|
|
448
|
+
const { ctxArgs } = (await this.logCtx(args, constants_2.PersistenceKeys.MIGRATION, true)).for(this.retry);
|
|
449
|
+
const cfg = this.config;
|
|
450
|
+
if (!cfg.taskMode || !cfg.taskService) {
|
|
451
|
+
await this.migrateNormally(undefined, undefined, ...ctxArgs);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
const explicitTaskIds = typeof taskIds === "string"
|
|
455
|
+
? [taskIds]
|
|
456
|
+
: Array.isArray(taskIds)
|
|
457
|
+
? taskIds
|
|
458
|
+
: undefined;
|
|
459
|
+
const queuedIds = this.queuedTaskChain.map((task) => task.id);
|
|
460
|
+
const pendingIds = this.migrationTaskIdsFromContext(constants_2.PersistenceKeys.MIGRATION, ctxArgs);
|
|
461
|
+
const ids = explicitTaskIds?.length
|
|
462
|
+
? explicitTaskIds
|
|
463
|
+
: queuedIds.length
|
|
464
|
+
? queuedIds
|
|
465
|
+
: pendingIds;
|
|
466
|
+
if (!ids.length) {
|
|
467
|
+
await this.migrateViaTasks(undefined, undefined, ...ctxArgs);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const taskRepo = cfg.taskService.client.tasks;
|
|
471
|
+
const updateRepo = typeof taskRepo?.override === "function"
|
|
472
|
+
? taskRepo.override({ ignoreHandlers: true })
|
|
473
|
+
: taskRepo;
|
|
474
|
+
for (const id of ids) {
|
|
475
|
+
const tracked = await cfg.taskService.track(id, ...ctxArgs);
|
|
476
|
+
const task = tracked.task;
|
|
477
|
+
if (![constants_3.TaskStatus.FAILED, constants_3.TaskStatus.CANCELED].includes(task.status))
|
|
478
|
+
continue;
|
|
479
|
+
const patched = new TaskModel_1.TaskModel({
|
|
480
|
+
...task,
|
|
481
|
+
status: constants_3.TaskStatus.PENDING,
|
|
482
|
+
error: undefined,
|
|
483
|
+
nextRunAt: undefined,
|
|
484
|
+
scheduledTo: undefined,
|
|
485
|
+
leaseOwner: undefined,
|
|
486
|
+
leaseExpiry: undefined,
|
|
487
|
+
});
|
|
488
|
+
await updateRepo.update(patched, ...ctxArgs);
|
|
489
|
+
}
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
155
492
|
async up(qr, adapter, ...args) {
|
|
156
493
|
const { log } = this.logCtx(args, this.down);
|
|
157
494
|
log.verbose((0, logging_1.style)("Setting up migration process").yellow.bold);
|
|
495
|
+
void qr;
|
|
496
|
+
void adapter;
|
|
158
497
|
}
|
|
159
498
|
}
|
|
160
499
|
exports.MigrationService = MigrationService;
|
|
@@ -1,20 +1,63 @@
|
|
|
1
|
-
import { Migration, MigrationConfig } from "./types";
|
|
1
|
+
import { Migration, MigrationConfig, PersistenceMigrationConfig } from "./types";
|
|
2
2
|
import { ClientBasedService } from "../services/services";
|
|
3
3
|
import { Adapter } from "../persistence/Adapter";
|
|
4
4
|
import { ContextOf } from "../persistence/types";
|
|
5
5
|
import { ContextualArgs, MaybeContextualArg } from "../utils/ContextualLoggedClass";
|
|
6
|
+
import { TaskModel } from "../tasks/models/TaskModel";
|
|
7
|
+
import { MigrationVersioning } from "./MigrationVersioning";
|
|
8
|
+
type ResolvedMigration = {
|
|
9
|
+
migration: Migration<any, any>;
|
|
10
|
+
flavour: string;
|
|
11
|
+
reference: string;
|
|
12
|
+
version: string;
|
|
13
|
+
};
|
|
14
|
+
type VersionTask = {
|
|
15
|
+
version: string;
|
|
16
|
+
task: TaskModel;
|
|
17
|
+
};
|
|
6
18
|
export declare class MigrationService<PERSIST extends boolean, A extends Adapter<any, any, any, any> = any, R = void> extends ClientBasedService<PERSIST extends boolean ? A : void, MigrationConfig<PERSIST>> implements Migration<any, A, R> {
|
|
19
|
+
protected versioning: MigrationVersioning;
|
|
20
|
+
protected queuedTaskChain: Array<{
|
|
21
|
+
id: string;
|
|
22
|
+
version: string;
|
|
23
|
+
}>;
|
|
7
24
|
flavour?: string;
|
|
8
25
|
readonly reference: string;
|
|
9
26
|
readonly precedence: Migration<any, any> | Migration<any, any>[] | null;
|
|
10
27
|
transaction: boolean;
|
|
11
28
|
constructor();
|
|
29
|
+
static migrateAdapters<AD extends Adapter<any, any, any, any> = Adapter<any, any, any, any>>(adapters: AD[], cfg?: PersistenceMigrationConfig<AD>, ...args: MaybeContextualArg<ContextOf<AD>>): Promise<MigrationService<true, AD>[]>;
|
|
12
30
|
initialize(...args: MaybeContextualArg<ContextOf<A>>): Promise<{
|
|
13
31
|
config: MigrationConfig<PERSIST>;
|
|
14
32
|
client: PERSIST extends boolean ? A : void;
|
|
15
33
|
}>;
|
|
16
34
|
down(qr: any, adapter: any, ...args: ContextualArgs<ContextOf<any>>): Promise<void>;
|
|
17
|
-
protected
|
|
35
|
+
protected normalizeVersion(raw: string, precedenceHint?: string): string;
|
|
36
|
+
protected extractPrecedenceTokens(migration: Migration<any, any>): string[];
|
|
37
|
+
protected referencesMigration(candidate: Migration<any, any>, target: ResolvedMigration): boolean;
|
|
38
|
+
protected precedenceHint(migration: Migration<any, any>): string | undefined;
|
|
39
|
+
protected compareByPrecedence(migration1: ResolvedMigration, migration2: ResolvedMigration): number;
|
|
40
|
+
protected sort(migrations: ResolvedMigration[]): ResolvedMigration[];
|
|
41
|
+
protected resolveMigration(migration: Migration<any, any>): ResolvedMigration;
|
|
42
|
+
protected shouldIncludeMigration(migration: ResolvedMigration, targetFlavour?: string, includeGeneric?: boolean): boolean;
|
|
43
|
+
protected collectMigrations(targetFlavour?: string, includeGeneric?: boolean): ResolvedMigration[];
|
|
44
|
+
protected buildExecutionPlan(options?: {
|
|
45
|
+
fromVersion?: string;
|
|
46
|
+
toVersion?: string;
|
|
47
|
+
targetFlavour?: string;
|
|
48
|
+
includeGeneric?: boolean;
|
|
49
|
+
}): ResolvedMigration[];
|
|
50
|
+
protected executeMigration(migration: ResolvedMigration, ...args: ContextualArgs<ContextOf<any>>): Promise<void>;
|
|
51
|
+
protected buildMigrationTaskForPlan(plan: ResolvedMigration[], ...args: ContextualArgs<ContextOf<any>>): TaskModel;
|
|
52
|
+
protected buildMigrationTasksForPlan(plan: ResolvedMigration[], ...args: ContextualArgs<ContextOf<any>>): VersionTask[];
|
|
53
|
+
createMigrationTask(targetVersion?: string, fromVersion?: string, ...args: ContextualArgs<ContextOf<any>>): TaskModel;
|
|
54
|
+
createMigrationTasks(targetVersion?: string, fromVersion?: string, ...args: ContextualArgs<ContextOf<any>>): VersionTask[];
|
|
55
|
+
protected migrationTaskIdsFromContext(taskKey: string, args: ContextualArgs<ContextOf<any>>): string[];
|
|
18
56
|
migrate(qr?: any, adapter?: any, ...args: MaybeContextualArg<ContextOf<any>>): Promise<R>;
|
|
57
|
+
migrateNormally(qr?: any, adapter?: any, ...args: MaybeContextualArg<ContextOf<any>>): Promise<R>;
|
|
58
|
+
migrateViaTasks(qr?: any, adapter?: any, ...args: MaybeContextualArg<ContextOf<any>>): Promise<R>;
|
|
59
|
+
track(taskIds?: string[] | string, ...args: MaybeContextualArg<ContextOf<any>>): Promise<void>;
|
|
60
|
+
retry(taskIds?: string[] | string, ...args: MaybeContextualArg<ContextOf<any>>): Promise<void>;
|
|
19
61
|
up(qr: any, adapter: any, ...args: ContextualArgs<ContextOf<any>>): Promise<void>;
|
|
20
62
|
}
|
|
63
|
+
export {};
|