@objectstack/core 17.0.0-rc.1 → 17.0.0-rc.3
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/CHANGELOG.md +255 -0
- package/dist/index.cjs +453 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +234 -3
- package/dist/index.d.ts +234 -3
- package/dist/index.js +446 -16
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -40,6 +40,8 @@ __export(index_exports, {
|
|
|
40
40
|
DependencyResolver: () => DependencyResolver,
|
|
41
41
|
HotReloadManager: () => HotReloadManager,
|
|
42
42
|
LiteKernel: () => LiteKernel,
|
|
43
|
+
MigrationJournalRefusal: () => MigrationJournalRefusal,
|
|
44
|
+
MigrationPlanRegistry: () => MigrationPlanRegistry,
|
|
43
45
|
NamespaceResolver: () => NamespaceResolver,
|
|
44
46
|
ObjectKernel: () => ObjectKernel,
|
|
45
47
|
ObjectKernelBase: () => ObjectKernelBase,
|
|
@@ -82,14 +84,17 @@ __export(index_exports, {
|
|
|
82
84
|
defaultIsTransientError: () => defaultIsTransientError,
|
|
83
85
|
derivePosture: () => derivePosture,
|
|
84
86
|
describeInitOrderFault: () => describeInitOrderFault,
|
|
87
|
+
engineCanRollBack: () => engineCanRollBack,
|
|
85
88
|
evaluateAuthGate: () => evaluateAuthGate,
|
|
86
89
|
extractApiKey: () => extractApiKey,
|
|
87
90
|
filterTokenContextFrom: () => filterTokenContextFrom,
|
|
91
|
+
findInterruptedRuns: () => findInterruptedRuns,
|
|
88
92
|
generateApiKey: () => generateApiKey,
|
|
89
93
|
generateEd25519KeyPair: () => generateEd25519KeyPair,
|
|
90
94
|
getEnv: () => getEnv,
|
|
91
95
|
getMemoryUsage: () => getMemoryUsage,
|
|
92
96
|
hashApiKey: () => hashApiKey,
|
|
97
|
+
hashMigrationPlan: () => hashMigrationPlan,
|
|
93
98
|
isAuthGateAllowlisted: () => isAuthGateAllowlisted,
|
|
94
99
|
isExpired: () => isExpired,
|
|
95
100
|
isGrantActive: () => isGrantActive,
|
|
@@ -98,8 +103,10 @@ __export(index_exports, {
|
|
|
98
103
|
nextUtcCalendarDay: () => import_data.nextUtcCalendarDay,
|
|
99
104
|
parseScopes: () => parseScopes,
|
|
100
105
|
parseSignature: () => parseSignature,
|
|
106
|
+
planChunks: () => planChunks,
|
|
101
107
|
postureVisibleRows: () => postureVisibleRows,
|
|
102
108
|
readAuthoredTranslationLayer: () => readAuthoredTranslationLayer,
|
|
109
|
+
readRunJournal: () => readRunJournal,
|
|
103
110
|
resolveApiKeyPrincipal: () => resolveApiKeyPrincipal,
|
|
104
111
|
resolveAuthzContext: () => resolveAuthzContext,
|
|
105
112
|
resolveFilterToken: () => resolveFilterToken,
|
|
@@ -108,6 +115,8 @@ __export(index_exports, {
|
|
|
108
115
|
resolveLocalizationContext: () => resolveLocalizationContext,
|
|
109
116
|
resolvePluginOrder: () => resolvePluginOrder,
|
|
110
117
|
resolveUserAuthzGrants: () => resolveUserAuthzGrants,
|
|
118
|
+
resumeMigrationJournal: () => resumeMigrationJournal,
|
|
119
|
+
runMigrationJournal: () => runMigrationJournal,
|
|
111
120
|
safeExit: () => safeExit,
|
|
112
121
|
shouldDenyAnonymous: () => shouldDenyAnonymous,
|
|
113
122
|
signPayload: () => signPayload,
|
|
@@ -1958,17 +1967,51 @@ var ObjectKernel = class {
|
|
|
1958
1967
|
assertInitServiceRequirements(plugin, (name) => this.hasAnyService(name));
|
|
1959
1968
|
this.currentlyInitializing = plugin.name;
|
|
1960
1969
|
try {
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
});
|
|
1967
|
-
await Promise.race([initPromise, timeoutPromise]);
|
|
1970
|
+
await this.raceStartupTimeout(
|
|
1971
|
+
plugin.init(this.context),
|
|
1972
|
+
timeout,
|
|
1973
|
+
`Plugin ${plugin.name} init timeout after ${timeout}ms`
|
|
1974
|
+
);
|
|
1968
1975
|
} finally {
|
|
1969
1976
|
this.currentlyInitializing = void 0;
|
|
1970
1977
|
}
|
|
1971
1978
|
}
|
|
1979
|
+
/**
|
|
1980
|
+
* Race a plugin lifecycle hook against its startup-timeout guard, and
|
|
1981
|
+
* reclaim the guard the moment the race settles (#4813).
|
|
1982
|
+
*
|
|
1983
|
+
* The guard used to be armed and then abandoned: when the plugin won the
|
|
1984
|
+
* race, its `setTimeout` stayed ref'd in the event loop for the full
|
|
1985
|
+
* `startupTimeout`, so every process idled that long after its work was
|
|
1986
|
+
* done. One `os migrate` finished in 3s and then sat for 120s
|
|
1987
|
+
* (`ObjectQLPlugin.startupTimeout`), held open by 8 orphaned guards — one
|
|
1988
|
+
* per init plus one per start.
|
|
1989
|
+
*
|
|
1990
|
+
* Clearing on settle rather than `unref()`-ing at arm time is deliberate.
|
|
1991
|
+
* An unref'd guard also stops pinning the loop, but it stops being a guard
|
|
1992
|
+
* as well: if the hook never settles and nothing else keeps the loop alive,
|
|
1993
|
+
* Node exits before the timer can fire and the timeout is never reported.
|
|
1994
|
+
* The guard has to stay ref'd exactly as long as the race is undecided,
|
|
1995
|
+
* which is what `clearTimeout` in a `finally` expresses.
|
|
1996
|
+
*
|
|
1997
|
+
* `operation` is widened to `T | PromiseLike<T>` because the Plugin
|
|
1998
|
+
* contract permits a synchronous hook (`init`/`start` return
|
|
1999
|
+
* `void | Promise<void>`); such a hook wins the race immediately and the
|
|
2000
|
+
* guard is reclaimed on the same turn.
|
|
2001
|
+
*/
|
|
2002
|
+
async raceStartupTimeout(operation, timeout, message) {
|
|
2003
|
+
let guard;
|
|
2004
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
2005
|
+
guard = setTimeout(() => {
|
|
2006
|
+
reject(new Error(message));
|
|
2007
|
+
}, timeout);
|
|
2008
|
+
});
|
|
2009
|
+
try {
|
|
2010
|
+
return await Promise.race([operation, timeoutPromise]);
|
|
2011
|
+
} finally {
|
|
2012
|
+
clearTimeout(guard);
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
1972
2015
|
/**
|
|
1973
2016
|
* Whether a service is resolvable on this kernel right now — direct
|
|
1974
2017
|
* registration or a loader-registered factory. Backs the init-service
|
|
@@ -1994,13 +2037,11 @@ var ObjectKernel = class {
|
|
|
1994
2037
|
const startTime = Date.now();
|
|
1995
2038
|
this.logger.debug(`Start: ${plugin.name}`, { plugin: plugin.name });
|
|
1996
2039
|
try {
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
});
|
|
2003
|
-
await Promise.race([startPromise, timeoutPromise]);
|
|
2040
|
+
await this.raceStartupTimeout(
|
|
2041
|
+
plugin.start(this.context),
|
|
2042
|
+
timeout,
|
|
2043
|
+
`Plugin ${plugin.name} start timeout after ${timeout}ms`
|
|
2044
|
+
);
|
|
2004
2045
|
const duration = Date.now() - startTime;
|
|
2005
2046
|
this.startedPlugins.add(plugin.name);
|
|
2006
2047
|
this.pluginStartTimes.set(plugin.name, duration);
|
|
@@ -5057,6 +5098,393 @@ async function bulkWrite(rows, opts) {
|
|
|
5057
5098
|
return results;
|
|
5058
5099
|
}
|
|
5059
5100
|
|
|
5101
|
+
// src/utils/migration-journal.ts
|
|
5102
|
+
var import_node_crypto3 = require("crypto");
|
|
5103
|
+
var import_system3 = require("@objectstack/spec/system");
|
|
5104
|
+
var SYSTEM_CTX = { isSystem: true };
|
|
5105
|
+
var DEFAULT_CHUNK_SIZE = 200;
|
|
5106
|
+
function engineCanRollBack(engine) {
|
|
5107
|
+
const e = engine;
|
|
5108
|
+
if (typeof e?.transaction !== "function") return false;
|
|
5109
|
+
const defaultDriverName = e.getDefaultDriverName?.();
|
|
5110
|
+
const defaultDriver = defaultDriverName ? e.getDriverByName?.(defaultDriverName) : void 0;
|
|
5111
|
+
return !defaultDriver || typeof defaultDriver.beginTransaction === "function";
|
|
5112
|
+
}
|
|
5113
|
+
var MigrationPlanRegistry = class {
|
|
5114
|
+
constructor() {
|
|
5115
|
+
this.plans = /* @__PURE__ */ new Map();
|
|
5116
|
+
}
|
|
5117
|
+
register(plan) {
|
|
5118
|
+
this.plans.set(plan.id, plan);
|
|
5119
|
+
}
|
|
5120
|
+
get(planId) {
|
|
5121
|
+
return this.plans.get(planId);
|
|
5122
|
+
}
|
|
5123
|
+
list() {
|
|
5124
|
+
return [...this.plans.values()];
|
|
5125
|
+
}
|
|
5126
|
+
};
|
|
5127
|
+
var MigrationJournalRefusal = class extends Error {
|
|
5128
|
+
constructor(code, message) {
|
|
5129
|
+
super(message);
|
|
5130
|
+
this.name = "MigrationJournalRefusal";
|
|
5131
|
+
this.code = code;
|
|
5132
|
+
}
|
|
5133
|
+
};
|
|
5134
|
+
function planChunks(plan, rowCounts, chunkSize = plan.chunkSize ?? DEFAULT_CHUNK_SIZE) {
|
|
5135
|
+
const size = Math.max(1, chunkSize);
|
|
5136
|
+
const chunks = [];
|
|
5137
|
+
plan.steps.forEach((step, stepIndex) => {
|
|
5138
|
+
const total = rowCounts[stepIndex] ?? 0;
|
|
5139
|
+
for (let offset = 0; offset < total; offset += size) {
|
|
5140
|
+
chunks.push({
|
|
5141
|
+
index: chunks.length,
|
|
5142
|
+
stepIndex,
|
|
5143
|
+
stepName: step.name,
|
|
5144
|
+
offset,
|
|
5145
|
+
length: Math.min(size, total - offset)
|
|
5146
|
+
});
|
|
5147
|
+
}
|
|
5148
|
+
});
|
|
5149
|
+
return chunks;
|
|
5150
|
+
}
|
|
5151
|
+
function hashMigrationPlan(plan, chunks) {
|
|
5152
|
+
const shape = JSON.stringify({
|
|
5153
|
+
id: plan.id,
|
|
5154
|
+
steps: plan.steps.map((s) => s.name),
|
|
5155
|
+
chunks: chunks.map((c) => [c.stepIndex, c.offset, c.length])
|
|
5156
|
+
});
|
|
5157
|
+
return (0, import_node_crypto3.createHash)("sha256").update(shape, "utf8").digest("hex").slice(0, 32);
|
|
5158
|
+
}
|
|
5159
|
+
async function appendEvent(engine, event, execContext) {
|
|
5160
|
+
await engine.insert(
|
|
5161
|
+
import_system3.MIGRATION_JOURNAL_OBJECT,
|
|
5162
|
+
{ ...event, created_at: event.created_at ?? (/* @__PURE__ */ new Date()).toISOString() },
|
|
5163
|
+
{ context: execContext ?? { ...SYSTEM_CTX } }
|
|
5164
|
+
);
|
|
5165
|
+
}
|
|
5166
|
+
async function readRunJournal(engine, runId) {
|
|
5167
|
+
const rows = await engine.find(
|
|
5168
|
+
import_system3.MIGRATION_JOURNAL_OBJECT,
|
|
5169
|
+
{ where: { run_id: runId } },
|
|
5170
|
+
{ context: { ...SYSTEM_CTX } }
|
|
5171
|
+
);
|
|
5172
|
+
return [...rows ?? []].sort((a, b) => Number(a.seq) - Number(b.seq));
|
|
5173
|
+
}
|
|
5174
|
+
function chunkSetOf(events, kind) {
|
|
5175
|
+
const out = /* @__PURE__ */ new Set();
|
|
5176
|
+
for (const e of events) {
|
|
5177
|
+
if (e.kind === kind && typeof e.chunk_index === "number") out.add(e.chunk_index);
|
|
5178
|
+
}
|
|
5179
|
+
return out;
|
|
5180
|
+
}
|
|
5181
|
+
async function findInterruptedRuns(engine) {
|
|
5182
|
+
const started = await engine.find(
|
|
5183
|
+
import_system3.MIGRATION_JOURNAL_OBJECT,
|
|
5184
|
+
{ where: { kind: "run_started" } },
|
|
5185
|
+
{ context: { ...SYSTEM_CTX } }
|
|
5186
|
+
);
|
|
5187
|
+
const out = [];
|
|
5188
|
+
for (const start of started ?? []) {
|
|
5189
|
+
const events = await readRunJournal(engine, start.run_id);
|
|
5190
|
+
if (events.some((e) => e.kind === "run_done")) continue;
|
|
5191
|
+
const committed = chunkSetOf(events, "chunk_done");
|
|
5192
|
+
const compensated = chunkSetOf(events, "compensated");
|
|
5193
|
+
const outstanding = [...committed].filter((i) => !compensated.has(i));
|
|
5194
|
+
if (events.some((e) => e.kind === "run_failed") && outstanding.length === 0) continue;
|
|
5195
|
+
const unknown = [...chunkSetOf(events, "chunk_started")].filter((i) => !committed.has(i));
|
|
5196
|
+
let planId = start.run_id;
|
|
5197
|
+
try {
|
|
5198
|
+
planId = start.detail ? JSON.parse(start.detail).planId ?? start.run_id : start.run_id;
|
|
5199
|
+
} catch {
|
|
5200
|
+
}
|
|
5201
|
+
out.push({
|
|
5202
|
+
runId: start.run_id,
|
|
5203
|
+
planId,
|
|
5204
|
+
planHash: start.plan_hash ?? "",
|
|
5205
|
+
migrationId: start.migration_id,
|
|
5206
|
+
startedAt: start.created_at,
|
|
5207
|
+
committedChunks: [...committed].sort((a, b) => a - b),
|
|
5208
|
+
unknownChunks: unknown.sort((a, b) => a - b),
|
|
5209
|
+
compensatedChunks: [...compensated].sort((a, b) => a - b)
|
|
5210
|
+
});
|
|
5211
|
+
}
|
|
5212
|
+
return out;
|
|
5213
|
+
}
|
|
5214
|
+
async function loadPlan(engine, plan, chunkSize) {
|
|
5215
|
+
const rowsByStep = [];
|
|
5216
|
+
for (const step of plan.steps) rowsByStep.push(await step.load(engine) ?? []);
|
|
5217
|
+
const chunks = planChunks(plan, rowsByStep.map((r) => r.length), chunkSize ?? plan.chunkSize);
|
|
5218
|
+
return { chunks, planHash: hashMigrationPlan(plan, chunks), rowsByStep };
|
|
5219
|
+
}
|
|
5220
|
+
async function runMigrationJournal(engine, plan, options = {}) {
|
|
5221
|
+
const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
5222
|
+
if (!engineCanRollBack(engine)) {
|
|
5223
|
+
throw new MigrationJournalRefusal(
|
|
5224
|
+
"NOT_IMPLEMENTED",
|
|
5225
|
+
`Migration plan '${plan.id}' requires engine transaction support; this runtime cannot roll back. The journal's chunk_done markers would not mean "committed", so the run is refused rather than started.`
|
|
5226
|
+
);
|
|
5227
|
+
}
|
|
5228
|
+
const { chunks, planHash, rowsByStep } = await loadPlan(engine, plan, options.chunkSize);
|
|
5229
|
+
const resuming = Boolean(options.runId);
|
|
5230
|
+
const runId = options.runId ?? (0, import_node_crypto3.randomUUID)();
|
|
5231
|
+
let events = [];
|
|
5232
|
+
let seq = 0;
|
|
5233
|
+
let committed = /* @__PURE__ */ new Set();
|
|
5234
|
+
let compensated = /* @__PURE__ */ new Set();
|
|
5235
|
+
const attemptsByChunk = /* @__PURE__ */ new Map();
|
|
5236
|
+
if (resuming) {
|
|
5237
|
+
events = await readRunJournal(engine, runId);
|
|
5238
|
+
if (events.length === 0) {
|
|
5239
|
+
throw new MigrationJournalRefusal("NO_SUCH_RUN", `No journal rows for run '${runId}'.`);
|
|
5240
|
+
}
|
|
5241
|
+
const start = events.find((e) => e.kind === "run_started");
|
|
5242
|
+
if (start?.plan_hash && start.plan_hash !== planHash) {
|
|
5243
|
+
throw new MigrationJournalRefusal(
|
|
5244
|
+
"PLAN_CHANGED",
|
|
5245
|
+
`Refusing to resume run '${runId}': plan hash ${planHash} does not match the journal's ${start.plan_hash}. The chunk boundaries recorded in the journal describe a different plan.`
|
|
5246
|
+
);
|
|
5247
|
+
}
|
|
5248
|
+
if (events.some((e) => e.kind === "run_done")) {
|
|
5249
|
+
return {
|
|
5250
|
+
runId,
|
|
5251
|
+
status: "completed",
|
|
5252
|
+
chunksTotal: chunks.length,
|
|
5253
|
+
chunksCommitted: chunkSetOf(events, "chunk_done").size,
|
|
5254
|
+
chunksCompensated: chunkSetOf(events, "compensated").size,
|
|
5255
|
+
planHash
|
|
5256
|
+
};
|
|
5257
|
+
}
|
|
5258
|
+
seq = events.reduce((m, e) => Math.max(m, Number(e.seq) + 1), 0);
|
|
5259
|
+
committed = chunkSetOf(events, "chunk_done");
|
|
5260
|
+
compensated = chunkSetOf(events, "compensated");
|
|
5261
|
+
for (const e of events) {
|
|
5262
|
+
if (e.kind === "chunk_started" && typeof e.chunk_index === "number") {
|
|
5263
|
+
attemptsByChunk.set(e.chunk_index, (attemptsByChunk.get(e.chunk_index) ?? 0) + 1);
|
|
5264
|
+
}
|
|
5265
|
+
}
|
|
5266
|
+
}
|
|
5267
|
+
for (const step of plan.steps) {
|
|
5268
|
+
if (!step.preflight) continue;
|
|
5269
|
+
try {
|
|
5270
|
+
await step.preflight(engine);
|
|
5271
|
+
} catch (err) {
|
|
5272
|
+
throw new MigrationJournalRefusal(
|
|
5273
|
+
"PREFLIGHT_FAILED",
|
|
5274
|
+
`Migration plan '${plan.id}' refused: preflight for step '${step.name}' failed: ${errText(err)}`
|
|
5275
|
+
);
|
|
5276
|
+
}
|
|
5277
|
+
}
|
|
5278
|
+
if (plan.onCrash === "compensate") {
|
|
5279
|
+
const missing = plan.steps.filter((s) => !s.compensate).map((s) => s.name);
|
|
5280
|
+
if (missing.length > 0) {
|
|
5281
|
+
throw new MigrationJournalRefusal(
|
|
5282
|
+
"NOT_COMPENSABLE",
|
|
5283
|
+
`Migration plan '${plan.id}' declares onCrash: 'compensate' but step(s) ${missing.join(", ")} declare no compensate().`
|
|
5284
|
+
);
|
|
5285
|
+
}
|
|
5286
|
+
}
|
|
5287
|
+
const rowsOf = (c) => rowsByStep[c.stepIndex].slice(c.offset, c.offset + c.length);
|
|
5288
|
+
const next = () => seq++;
|
|
5289
|
+
if (!resuming) {
|
|
5290
|
+
await appendEvent(engine, {
|
|
5291
|
+
run_id: runId,
|
|
5292
|
+
seq: next(),
|
|
5293
|
+
kind: "run_started",
|
|
5294
|
+
plan_hash: planHash,
|
|
5295
|
+
migration_id: plan.migrationId,
|
|
5296
|
+
created_at: now(),
|
|
5297
|
+
detail: JSON.stringify({
|
|
5298
|
+
planId: plan.id,
|
|
5299
|
+
onCrash: plan.onCrash ?? "resume",
|
|
5300
|
+
chunks: chunks.map((c) => ({ i: c.index, step: c.stepName, offset: c.offset, length: c.length }))
|
|
5301
|
+
})
|
|
5302
|
+
});
|
|
5303
|
+
}
|
|
5304
|
+
if (resuming && plan.onCrash === "compensate") {
|
|
5305
|
+
return await unwind(engine, plan, {
|
|
5306
|
+
runId,
|
|
5307
|
+
planHash,
|
|
5308
|
+
chunks,
|
|
5309
|
+
rowsOf,
|
|
5310
|
+
next,
|
|
5311
|
+
now,
|
|
5312
|
+
committed,
|
|
5313
|
+
compensated,
|
|
5314
|
+
chunksTotal: chunks.length,
|
|
5315
|
+
cause: new Error(`run '${runId}' rediscovered after interruption; plan policy is compensate`)
|
|
5316
|
+
});
|
|
5317
|
+
}
|
|
5318
|
+
for (const chunk of chunks) {
|
|
5319
|
+
if (committed.has(chunk.index)) continue;
|
|
5320
|
+
const attempt = (attemptsByChunk.get(chunk.index) ?? 0) + 1;
|
|
5321
|
+
attemptsByChunk.set(chunk.index, attempt);
|
|
5322
|
+
const step = plan.steps[chunk.stepIndex];
|
|
5323
|
+
const rows = rowsOf(chunk);
|
|
5324
|
+
await appendEvent(engine, {
|
|
5325
|
+
run_id: runId,
|
|
5326
|
+
seq: next(),
|
|
5327
|
+
kind: "chunk_started",
|
|
5328
|
+
chunk_index: chunk.index,
|
|
5329
|
+
attempt,
|
|
5330
|
+
migration_id: plan.migrationId,
|
|
5331
|
+
created_at: now()
|
|
5332
|
+
});
|
|
5333
|
+
try {
|
|
5334
|
+
await engine.transaction(async (trxCtx) => {
|
|
5335
|
+
await step.forward(rows, { runId, chunkIndex: chunk.index, attempt, context: trxCtx }, engine);
|
|
5336
|
+
await appendEvent(
|
|
5337
|
+
engine,
|
|
5338
|
+
{
|
|
5339
|
+
run_id: runId,
|
|
5340
|
+
seq: next(),
|
|
5341
|
+
kind: "chunk_done",
|
|
5342
|
+
chunk_index: chunk.index,
|
|
5343
|
+
attempt,
|
|
5344
|
+
migration_id: plan.migrationId,
|
|
5345
|
+
created_at: now()
|
|
5346
|
+
},
|
|
5347
|
+
trxCtx
|
|
5348
|
+
);
|
|
5349
|
+
}, { ...SYSTEM_CTX });
|
|
5350
|
+
committed.add(chunk.index);
|
|
5351
|
+
} catch (err) {
|
|
5352
|
+
return await unwind(engine, plan, {
|
|
5353
|
+
runId,
|
|
5354
|
+
planHash,
|
|
5355
|
+
chunks,
|
|
5356
|
+
rowsOf,
|
|
5357
|
+
next,
|
|
5358
|
+
now,
|
|
5359
|
+
committed,
|
|
5360
|
+
compensated,
|
|
5361
|
+
chunksTotal: chunks.length,
|
|
5362
|
+
cause: err
|
|
5363
|
+
});
|
|
5364
|
+
}
|
|
5365
|
+
}
|
|
5366
|
+
await appendEvent(engine, {
|
|
5367
|
+
run_id: runId,
|
|
5368
|
+
seq: next(),
|
|
5369
|
+
kind: "run_done",
|
|
5370
|
+
migration_id: plan.migrationId,
|
|
5371
|
+
created_at: now()
|
|
5372
|
+
});
|
|
5373
|
+
return {
|
|
5374
|
+
runId,
|
|
5375
|
+
status: "completed",
|
|
5376
|
+
chunksTotal: chunks.length,
|
|
5377
|
+
chunksCommitted: committed.size,
|
|
5378
|
+
chunksCompensated: compensated.size,
|
|
5379
|
+
planHash
|
|
5380
|
+
};
|
|
5381
|
+
}
|
|
5382
|
+
async function unwind(engine, plan, a) {
|
|
5383
|
+
const order = [...a.committed].sort((x, y) => y - x);
|
|
5384
|
+
for (const index of order) {
|
|
5385
|
+
if (a.compensated.has(index)) continue;
|
|
5386
|
+
const chunk = a.chunks[index];
|
|
5387
|
+
const step = plan.steps[chunk.stepIndex];
|
|
5388
|
+
if (!step.compensate) {
|
|
5389
|
+
await appendEvent(engine, {
|
|
5390
|
+
run_id: a.runId,
|
|
5391
|
+
seq: a.next(),
|
|
5392
|
+
kind: "run_failed",
|
|
5393
|
+
chunk_index: index,
|
|
5394
|
+
migration_id: plan.migrationId,
|
|
5395
|
+
created_at: a.now(),
|
|
5396
|
+
detail: JSON.stringify({
|
|
5397
|
+
phase: "compensate",
|
|
5398
|
+
reason: "step declares no compensate()",
|
|
5399
|
+
step: step.name,
|
|
5400
|
+
cause: errText(a.cause)
|
|
5401
|
+
})
|
|
5402
|
+
});
|
|
5403
|
+
return {
|
|
5404
|
+
runId: a.runId,
|
|
5405
|
+
status: "failed",
|
|
5406
|
+
chunksTotal: a.chunksTotal,
|
|
5407
|
+
chunksCommitted: a.committed.size,
|
|
5408
|
+
chunksCompensated: a.compensated.size,
|
|
5409
|
+
planHash: a.planHash,
|
|
5410
|
+
error: a.cause
|
|
5411
|
+
};
|
|
5412
|
+
}
|
|
5413
|
+
const attempt = 1;
|
|
5414
|
+
try {
|
|
5415
|
+
await engine.transaction(async (trxCtx) => {
|
|
5416
|
+
await step.compensate(a.rowsOf(chunk), { runId: a.runId, chunkIndex: index, attempt, context: trxCtx }, engine);
|
|
5417
|
+
await appendEvent(
|
|
5418
|
+
engine,
|
|
5419
|
+
{
|
|
5420
|
+
run_id: a.runId,
|
|
5421
|
+
seq: a.next(),
|
|
5422
|
+
kind: "compensated",
|
|
5423
|
+
chunk_index: index,
|
|
5424
|
+
attempt,
|
|
5425
|
+
migration_id: plan.migrationId,
|
|
5426
|
+
created_at: a.now()
|
|
5427
|
+
},
|
|
5428
|
+
trxCtx
|
|
5429
|
+
);
|
|
5430
|
+
}, { ...SYSTEM_CTX });
|
|
5431
|
+
a.compensated.add(index);
|
|
5432
|
+
} catch (err) {
|
|
5433
|
+
await appendEvent(engine, {
|
|
5434
|
+
run_id: a.runId,
|
|
5435
|
+
seq: a.next(),
|
|
5436
|
+
kind: "run_failed",
|
|
5437
|
+
chunk_index: index,
|
|
5438
|
+
migration_id: plan.migrationId,
|
|
5439
|
+
created_at: a.now(),
|
|
5440
|
+
detail: JSON.stringify({
|
|
5441
|
+
phase: "compensate",
|
|
5442
|
+
step: step.name,
|
|
5443
|
+
error: errText(err),
|
|
5444
|
+
cause: errText(a.cause)
|
|
5445
|
+
})
|
|
5446
|
+
});
|
|
5447
|
+
return {
|
|
5448
|
+
runId: a.runId,
|
|
5449
|
+
status: "failed",
|
|
5450
|
+
chunksTotal: a.chunksTotal,
|
|
5451
|
+
chunksCommitted: a.committed.size,
|
|
5452
|
+
chunksCompensated: a.compensated.size,
|
|
5453
|
+
planHash: a.planHash,
|
|
5454
|
+
error: err
|
|
5455
|
+
};
|
|
5456
|
+
}
|
|
5457
|
+
}
|
|
5458
|
+
await appendEvent(engine, {
|
|
5459
|
+
run_id: a.runId,
|
|
5460
|
+
seq: a.next(),
|
|
5461
|
+
kind: "run_failed",
|
|
5462
|
+
migration_id: plan.migrationId,
|
|
5463
|
+
created_at: a.now(),
|
|
5464
|
+
detail: JSON.stringify({ phase: "forward", error: errText(a.cause), compensated: [...a.compensated].sort((x, y) => x - y) })
|
|
5465
|
+
});
|
|
5466
|
+
return {
|
|
5467
|
+
runId: a.runId,
|
|
5468
|
+
status: "compensated",
|
|
5469
|
+
chunksTotal: a.chunksTotal,
|
|
5470
|
+
chunksCommitted: a.committed.size,
|
|
5471
|
+
chunksCompensated: a.compensated.size,
|
|
5472
|
+
planHash: a.planHash,
|
|
5473
|
+
error: a.cause
|
|
5474
|
+
};
|
|
5475
|
+
}
|
|
5476
|
+
async function resumeMigrationJournal(engine, plan, runId, options = {}) {
|
|
5477
|
+
return runMigrationJournal(engine, plan, { ...options, runId });
|
|
5478
|
+
}
|
|
5479
|
+
function errText(err) {
|
|
5480
|
+
if (err instanceof Error) return err.message;
|
|
5481
|
+
try {
|
|
5482
|
+
return String(err);
|
|
5483
|
+
} catch {
|
|
5484
|
+
return "<unprintable error>";
|
|
5485
|
+
}
|
|
5486
|
+
}
|
|
5487
|
+
|
|
5060
5488
|
// src/utils/filter-tokens.ts
|
|
5061
5489
|
var import_data2 = require("@objectstack/spec/data");
|
|
5062
5490
|
var UnknownFilterTokenError = class extends Error {
|
|
@@ -5503,7 +5931,7 @@ var PluginHealthMonitor = class {
|
|
|
5503
5931
|
};
|
|
5504
5932
|
|
|
5505
5933
|
// src/hot-reload.ts
|
|
5506
|
-
var
|
|
5934
|
+
var import_node_crypto4 = require("crypto");
|
|
5507
5935
|
var generateUUID = () => {
|
|
5508
5936
|
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
5509
5937
|
return crypto.randomUUID();
|
|
@@ -5598,7 +6026,7 @@ var PluginStateManager = class {
|
|
|
5598
6026
|
*/
|
|
5599
6027
|
calculateChecksum(state) {
|
|
5600
6028
|
const stateStr = JSON.stringify(state);
|
|
5601
|
-
return (0,
|
|
6029
|
+
return (0, import_node_crypto4.createHash)("sha256").update(stateStr).digest("hex");
|
|
5602
6030
|
}
|
|
5603
6031
|
/**
|
|
5604
6032
|
* Shutdown state manager
|
|
@@ -6185,6 +6613,8 @@ var NamespaceResolver = class {
|
|
|
6185
6613
|
DependencyResolver,
|
|
6186
6614
|
HotReloadManager,
|
|
6187
6615
|
LiteKernel,
|
|
6616
|
+
MigrationJournalRefusal,
|
|
6617
|
+
MigrationPlanRegistry,
|
|
6188
6618
|
NamespaceResolver,
|
|
6189
6619
|
ObjectKernel,
|
|
6190
6620
|
ObjectKernelBase,
|
|
@@ -6227,14 +6657,17 @@ var NamespaceResolver = class {
|
|
|
6227
6657
|
defaultIsTransientError,
|
|
6228
6658
|
derivePosture,
|
|
6229
6659
|
describeInitOrderFault,
|
|
6660
|
+
engineCanRollBack,
|
|
6230
6661
|
evaluateAuthGate,
|
|
6231
6662
|
extractApiKey,
|
|
6232
6663
|
filterTokenContextFrom,
|
|
6664
|
+
findInterruptedRuns,
|
|
6233
6665
|
generateApiKey,
|
|
6234
6666
|
generateEd25519KeyPair,
|
|
6235
6667
|
getEnv,
|
|
6236
6668
|
getMemoryUsage,
|
|
6237
6669
|
hashApiKey,
|
|
6670
|
+
hashMigrationPlan,
|
|
6238
6671
|
isAuthGateAllowlisted,
|
|
6239
6672
|
isExpired,
|
|
6240
6673
|
isGrantActive,
|
|
@@ -6243,8 +6676,10 @@ var NamespaceResolver = class {
|
|
|
6243
6676
|
nextUtcCalendarDay,
|
|
6244
6677
|
parseScopes,
|
|
6245
6678
|
parseSignature,
|
|
6679
|
+
planChunks,
|
|
6246
6680
|
postureVisibleRows,
|
|
6247
6681
|
readAuthoredTranslationLayer,
|
|
6682
|
+
readRunJournal,
|
|
6248
6683
|
resolveApiKeyPrincipal,
|
|
6249
6684
|
resolveAuthzContext,
|
|
6250
6685
|
resolveFilterToken,
|
|
@@ -6253,6 +6688,8 @@ var NamespaceResolver = class {
|
|
|
6253
6688
|
resolveLocalizationContext,
|
|
6254
6689
|
resolvePluginOrder,
|
|
6255
6690
|
resolveUserAuthzGrants,
|
|
6691
|
+
resumeMigrationJournal,
|
|
6692
|
+
runMigrationJournal,
|
|
6256
6693
|
safeExit,
|
|
6257
6694
|
shouldDenyAnonymous,
|
|
6258
6695
|
signPayload,
|