@decaf-ts/core 0.12.1 → 0.13.1

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.
Files changed (100) hide show
  1. package/README.md +140 -1
  2. package/dist/core.cjs +1 -1
  3. package/dist/core.cjs.map +1 -1
  4. package/dist/core.js +1 -1
  5. package/dist/core.js.map +1 -1
  6. package/lib/esm/index.d.ts +2 -1
  7. package/lib/esm/index.js +2 -2
  8. package/lib/esm/index.js.map +1 -1
  9. package/lib/esm/migrations/Migration.d.ts +1 -1
  10. package/lib/esm/migrations/MigrationService.d.ts +45 -2
  11. package/lib/esm/migrations/MigrationService.js +436 -97
  12. package/lib/esm/migrations/MigrationService.js.map +1 -1
  13. package/lib/esm/migrations/MigrationTaskBuilder.d.ts +10 -0
  14. package/lib/esm/migrations/MigrationTaskBuilder.js +21 -0
  15. package/lib/esm/migrations/MigrationTaskBuilder.js.map +1 -0
  16. package/lib/esm/migrations/MigrationTasks.js +10 -4
  17. package/lib/esm/migrations/MigrationTasks.js.map +1 -1
  18. package/lib/esm/migrations/MigrationVersioning.d.ts +7 -0
  19. package/lib/esm/migrations/MigrationVersioning.js +2 -0
  20. package/lib/esm/migrations/MigrationVersioning.js.map +1 -0
  21. package/lib/esm/migrations/SemverMigrationVersioning.d.ts +10 -0
  22. package/lib/esm/migrations/SemverMigrationVersioning.js +43 -0
  23. package/lib/esm/migrations/SemverMigrationVersioning.js.map +1 -0
  24. package/lib/esm/migrations/StandardMigrationVersioning.d.ts +12 -0
  25. package/lib/esm/migrations/StandardMigrationVersioning.js +27 -0
  26. package/lib/esm/migrations/StandardMigrationVersioning.js.map +1 -0
  27. package/lib/esm/migrations/decorators.d.ts +5 -0
  28. package/lib/esm/migrations/decorators.js +42 -11
  29. package/lib/esm/migrations/decorators.js.map +1 -1
  30. package/lib/esm/migrations/index.d.ts +3 -0
  31. package/lib/esm/migrations/index.js +3 -0
  32. package/lib/esm/migrations/index.js.map +1 -1
  33. package/lib/esm/migrations/types.d.ts +25 -2
  34. package/lib/esm/persistence/Adapter.d.ts +1 -1
  35. package/lib/esm/persistence/constants.js +6 -1
  36. package/lib/esm/persistence/constants.js.map +1 -1
  37. package/lib/esm/query/Paginator.js +16 -5
  38. package/lib/esm/query/Paginator.js.map +1 -1
  39. package/lib/esm/query/constants.d.ts +1 -0
  40. package/lib/esm/query/constants.js +1 -0
  41. package/lib/esm/query/constants.js.map +1 -1
  42. package/lib/esm/query/decorators.js +1 -1
  43. package/lib/esm/query/decorators.js.map +1 -1
  44. package/lib/esm/repository/Repository.d.ts +1 -0
  45. package/lib/esm/repository/Repository.js +51 -0
  46. package/lib/esm/repository/Repository.js.map +1 -1
  47. package/lib/esm/services/ModelService.d.ts +1 -0
  48. package/lib/esm/services/ModelService.js +4 -0
  49. package/lib/esm/services/ModelService.js.map +1 -1
  50. package/lib/esm/tasks/TaskService.d.ts +1 -0
  51. package/lib/esm/tasks/TaskService.js +4 -0
  52. package/lib/esm/tasks/TaskService.js.map +1 -1
  53. package/lib/index.cjs +2 -2
  54. package/lib/index.d.ts +2 -1
  55. package/lib/index.js.map +1 -1
  56. package/lib/migrations/Migration.d.ts +1 -1
  57. package/lib/migrations/MigrationService.cjs +436 -97
  58. package/lib/migrations/MigrationService.d.ts +45 -2
  59. package/lib/migrations/MigrationService.js.map +1 -1
  60. package/lib/migrations/MigrationTaskBuilder.cjs +25 -0
  61. package/lib/migrations/MigrationTaskBuilder.d.ts +10 -0
  62. package/lib/migrations/MigrationTaskBuilder.js.map +1 -0
  63. package/lib/migrations/MigrationTasks.cjs +10 -4
  64. package/lib/migrations/MigrationTasks.js.map +1 -1
  65. package/lib/migrations/MigrationVersioning.cjs +3 -0
  66. package/lib/migrations/MigrationVersioning.d.ts +7 -0
  67. package/lib/migrations/MigrationVersioning.js.map +1 -0
  68. package/lib/migrations/SemverMigrationVersioning.cjs +80 -0
  69. package/lib/migrations/SemverMigrationVersioning.d.ts +10 -0
  70. package/lib/migrations/SemverMigrationVersioning.js.map +1 -0
  71. package/lib/migrations/StandardMigrationVersioning.cjs +31 -0
  72. package/lib/migrations/StandardMigrationVersioning.d.ts +12 -0
  73. package/lib/migrations/StandardMigrationVersioning.js.map +1 -0
  74. package/lib/migrations/decorators.cjs +42 -11
  75. package/lib/migrations/decorators.d.ts +5 -0
  76. package/lib/migrations/decorators.js.map +1 -1
  77. package/lib/migrations/index.cjs +3 -0
  78. package/lib/migrations/index.d.ts +3 -0
  79. package/lib/migrations/index.js.map +1 -1
  80. package/lib/migrations/types.d.ts +25 -2
  81. package/lib/persistence/Adapter.d.ts +1 -1
  82. package/lib/persistence/constants.cjs +6 -1
  83. package/lib/persistence/constants.js.map +1 -1
  84. package/lib/query/Paginator.cjs +16 -5
  85. package/lib/query/Paginator.js.map +1 -1
  86. package/lib/query/constants.cjs +1 -0
  87. package/lib/query/constants.d.ts +1 -0
  88. package/lib/query/constants.js.map +1 -1
  89. package/lib/query/decorators.cjs +1 -1
  90. package/lib/query/decorators.js.map +1 -1
  91. package/lib/repository/Repository.cjs +51 -0
  92. package/lib/repository/Repository.d.ts +1 -0
  93. package/lib/repository/Repository.js.map +1 -1
  94. package/lib/services/ModelService.cjs +4 -0
  95. package/lib/services/ModelService.d.ts +1 -0
  96. package/lib/services/ModelService.js.map +1 -1
  97. package/lib/tasks/TaskService.cjs +4 -0
  98. package/lib/tasks/TaskService.d.ts +1 -0
  99. package/lib/tasks/TaskService.js.map +1 -1
  100. 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: Adapter_1.Adapter.get(cfg.persistenceFlavour),
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
- return migrations.sort((migration1, migration2) => {
42
- if (!migration1.precedence && !migration2.precedence)
43
- throw new db_decorators_1.InternalError(`Only one migration can have a null precedence: ${migration1.reference} vs ${migration2.reference}`);
44
- if (!migration1.precedence)
45
- return 1;
46
- if (!migration2.precedence)
47
- return -1;
48
- const precedences1 = Array.isArray(migration1.precedence)
49
- ? migration1.precedence
50
- : [migration1];
51
- const precedences2 = Array.isArray(migration2.precedence)
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
- async migrate(qr, adapter, ...args) {
97
- const { ctxArgs, log } = (await this.logCtx(args, constants_2.PersistenceKeys.MIGRATION, true)).for(this.migrate);
98
- let m;
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, mig] of toBoot) {
180
+ for (const [reference, MigrationClass] of toBoot) {
181
+ let migration;
102
182
  try {
103
- log.silly(`loading migration ${reference}...`);
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 ${mig.name}: ${e}`);
186
+ throw new db_decorators_1.InternalError(`failed to create migration ${reference}: ${e}`);
109
187
  }
110
- migrations.push(m);
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
- sortedMigrations = this.sort(migrations);
225
+ await m.up(qr, adapter, ...args);
115
226
  }
116
227
  catch (e) {
117
- throw new db_decorators_1.InternalError(`Failed to sort migrations: ${e}`);
228
+ throw new errors_1.MigrationError(`failed to initialize migration ${m.reference}: ${e}`);
118
229
  }
119
- log.debug(`sorted migration before execution: ${sortedMigrations.map((s) => s.reference)}`);
120
- for (const m of sortedMigrations) {
121
- let adapter;
122
- let qr;
123
- try {
124
- const meta = decoration_1.Metadata.get(m.constructor, constants_2.PersistenceKeys.MIGRATION);
125
- const flavour = meta?.flavour || m.flavour;
126
- adapter = Adapter_1.Adapter.get(flavour);
127
- if (!adapter)
128
- throw new db_decorators_1.InternalError(`failed to create migration ${m.reference}. did you call Service.boot() or use the Persistence Service??`);
129
- qr = adapter.client;
130
- }
131
- catch (e) {
132
- throw new db_decorators_1.InternalError(`Failed to load adapter to migrate ${m.reference}: ${e}`);
133
- }
134
- try {
135
- await m.up(qr, adapter, ...ctxArgs);
136
- }
137
- catch (e) {
138
- throw new errors_1.MigrationError(`failed to initialize migration ${m.reference}: ${e}`);
139
- }
140
- try {
141
- await m.migrate(qr, adapter, ...ctxArgs);
142
- }
143
- catch (e) {
144
- throw new errors_1.MigrationError(`failed to migrate ${m.reference}: ${e}`);
145
- }
146
- try {
147
- await m.down(qr, adapter, ...ctxArgs);
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
- catch (e) {
150
- throw new errors_1.MigrationError(`failed to conclude migration ${m.reference}: ${e}`);
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 sort(migrations: Migration<any, any>[]): Migration<any, any, void>[];
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 {};