@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/kernel",
3
- "version": "0.1.117",
3
+ "version": "0.1.119",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "json-rest-schema": "1.x.x"
@@ -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 new ProviderLifecycleError(`Provider \"${entry.id}\" failed during register().`, {
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 new ProviderLifecycleError(`Provider \"${entry.id}\" failed during boot().`, {
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 new ProviderLifecycleError(`Provider \"${entry.id}\" failed during shutdown().`, {
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
- super(String(message || "Kernel error."));
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 = details && typeof details === "object" ? { ...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
+ });