@jskit-ai/kernel 0.1.117 → 0.1.119
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/package.json
CHANGED
|
@@ -22,6 +22,19 @@ function nowMilliseconds() {
|
|
|
22
22
|
return Date.now();
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
function createProviderLifecycleError(providerId, phase, cause) {
|
|
26
|
+
const message = `Provider \"${providerId}\" failed during ${phase}().`;
|
|
27
|
+
const causeMessage = String(cause?.message || cause || "").trim();
|
|
28
|
+
return new ProviderLifecycleError(
|
|
29
|
+
causeMessage ? `${message} ${causeMessage}` : message,
|
|
30
|
+
{
|
|
31
|
+
providerId,
|
|
32
|
+
phase,
|
|
33
|
+
cause
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
25
38
|
class Application {
|
|
26
39
|
constructor({ profile = "", strict = true, container = null } = {}) {
|
|
27
40
|
this.profile = normalizeText(profile);
|
|
@@ -196,11 +209,7 @@ class Application {
|
|
|
196
209
|
await entry.provider.register(this);
|
|
197
210
|
}
|
|
198
211
|
} catch (error) {
|
|
199
|
-
throw
|
|
200
|
-
providerId: entry.id,
|
|
201
|
-
phase: "register",
|
|
202
|
-
cause: error
|
|
203
|
-
});
|
|
212
|
+
throw createProviderLifecycleError(entry.id, "register", error);
|
|
204
213
|
} finally {
|
|
205
214
|
this.diagnostics.timings.register[entry.id] = nowMilliseconds() - startedAt;
|
|
206
215
|
}
|
|
@@ -222,11 +231,7 @@ class Application {
|
|
|
222
231
|
await entry.provider.boot(this);
|
|
223
232
|
}
|
|
224
233
|
} catch (error) {
|
|
225
|
-
throw
|
|
226
|
-
providerId: entry.id,
|
|
227
|
-
phase: "boot",
|
|
228
|
-
cause: error
|
|
229
|
-
});
|
|
234
|
+
throw createProviderLifecycleError(entry.id, "boot", error);
|
|
230
235
|
} finally {
|
|
231
236
|
this.diagnostics.timings.boot[entry.id] = nowMilliseconds() - startedAt;
|
|
232
237
|
}
|
|
@@ -255,11 +260,7 @@ class Application {
|
|
|
255
260
|
await entry.provider.shutdown(this);
|
|
256
261
|
}
|
|
257
262
|
} catch (error) {
|
|
258
|
-
throw
|
|
259
|
-
providerId: entry.id,
|
|
260
|
-
phase: "shutdown",
|
|
261
|
-
cause: error
|
|
262
|
-
});
|
|
263
|
+
throw createProviderLifecycleError(entry.id, "shutdown", error);
|
|
263
264
|
} finally {
|
|
264
265
|
this.diagnostics.timings.shutdown[entry.id] = nowMilliseconds() - startedAt;
|
|
265
266
|
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
class KernelError extends Error {
|
|
2
2
|
constructor(message, details = {}) {
|
|
3
|
-
|
|
3
|
+
const normalizedDetails = details && typeof details === "object" ? { ...details } : {};
|
|
4
|
+
const hasCause = Object.prototype.hasOwnProperty.call(normalizedDetails, "cause");
|
|
5
|
+
super(
|
|
6
|
+
String(message || "Kernel error."),
|
|
7
|
+
hasCause ? { cause: normalizedDetails.cause } : undefined
|
|
8
|
+
);
|
|
4
9
|
this.name = this.constructor.name;
|
|
5
|
-
this.details =
|
|
10
|
+
this.details = normalizedDetails;
|
|
6
11
|
}
|
|
7
12
|
}
|
|
8
13
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import process from "node:process";
|
|
7
|
+
import test from "node:test";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { KernelError } from "./kernelErrors.js";
|
|
10
|
+
|
|
11
|
+
const APPLICATION_PATH = fileURLToPath(new URL("./application.js", import.meta.url));
|
|
12
|
+
|
|
13
|
+
test("KernelError preserves structured details while exposing the standard cause", () => {
|
|
14
|
+
const cause = new Error("underlying failure");
|
|
15
|
+
const error = new KernelError("outer failure", {
|
|
16
|
+
cause,
|
|
17
|
+
providerId: "example.provider"
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
assert.equal(error.cause, cause);
|
|
21
|
+
assert.equal(error.details.cause, cause);
|
|
22
|
+
assert.equal(error.details.providerId, "example.provider");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("provider lifecycle failures expose their cause in Node test output", async (context) => {
|
|
26
|
+
const fixtureRoot = await mkdtemp(path.join(tmpdir(), "jskit-kernel-error-"));
|
|
27
|
+
context.after(() => rm(fixtureRoot, { recursive: true, force: true }));
|
|
28
|
+
const fixturePath = path.join(fixtureRoot, "provider-lifecycle-failure.test.mjs");
|
|
29
|
+
const source = `
|
|
30
|
+
import { Application } from ${JSON.stringify(APPLICATION_PATH)};
|
|
31
|
+
import test from "node:test";
|
|
32
|
+
|
|
33
|
+
test("generated server starts", async () => {
|
|
34
|
+
const app = new Application();
|
|
35
|
+
app.configureProviders([{
|
|
36
|
+
id: "json-rest-api.core",
|
|
37
|
+
async boot() {
|
|
38
|
+
throw new Error("DB_CLIENT is required. Use mysql2 or pg.");
|
|
39
|
+
}
|
|
40
|
+
}]);
|
|
41
|
+
await app.bootProviders();
|
|
42
|
+
});
|
|
43
|
+
`;
|
|
44
|
+
await writeFile(fixturePath, source, "utf8");
|
|
45
|
+
const childEnvironment = { ...process.env };
|
|
46
|
+
delete childEnvironment.NODE_TEST_CONTEXT;
|
|
47
|
+
const result = spawnSync(process.execPath, ["--test", fixturePath], {
|
|
48
|
+
encoding: "utf8",
|
|
49
|
+
env: childEnvironment
|
|
50
|
+
});
|
|
51
|
+
const output = `${String(result.stdout || "")}\n${String(result.stderr || "")}`;
|
|
52
|
+
|
|
53
|
+
assert.equal(result.status, 1, output);
|
|
54
|
+
assert.match(output, /Provider "json-rest-api\.core" failed during boot\(\)\./u);
|
|
55
|
+
assert.match(output, /DB_CLIENT is required\. Use mysql2 or pg\./u);
|
|
56
|
+
});
|