@zudojs/plugins 1.3.2 → 1.4.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.
Files changed (53) hide show
  1. package/dist/index.d.ts +1 -2
  2. package/dist/index.js +1 -2
  3. package/dist/pluginDependencies/dependencyResolver.core.d.ts +0 -1
  4. package/dist/pluginDependencies/dependencyResolver.core.js +0 -1
  5. package/dist/pluginDependencies/index.d.ts +0 -1
  6. package/dist/pluginDependencies/index.js +0 -1
  7. package/dist/pluginDependencies/versionCheck.core.d.ts +0 -1
  8. package/dist/pluginDependencies/versionCheck.core.js +0 -1
  9. package/dist/pluginDiagnostics/index.d.ts +1 -2
  10. package/dist/pluginDiagnostics/index.js +1 -2
  11. package/dist/pluginDiagnostics/pluginDiagnostic.core.d.ts +4 -24
  12. package/dist/pluginDiagnostics/pluginDiagnostic.core.js +3 -24
  13. package/dist/pluginDiagnostics/pluginDiagnostic.health.d.ts +32 -0
  14. package/dist/pluginDiagnostics/pluginDiagnostic.health.js +65 -0
  15. package/dist/pluginEvents/index.d.ts +0 -1
  16. package/dist/pluginEvents/index.js +0 -1
  17. package/dist/pluginEvents/pluginEvent.bus.d.ts +0 -1
  18. package/dist/pluginEvents/pluginEvent.bus.js +0 -1
  19. package/dist/pluginEvents/pluginEvent.core.d.ts +0 -1
  20. package/dist/pluginEvents/pluginEvent.core.js +0 -1
  21. package/dist/pluginEvents/pluginEvent.deliver.d.ts +0 -1
  22. package/dist/pluginEvents/pluginEvent.deliver.js +0 -1
  23. package/dist/pluginIntegration/index.d.ts +0 -1
  24. package/dist/pluginIntegration/index.js +0 -1
  25. package/dist/pluginIntegration/pluginContext.core.d.ts +6 -1
  26. package/dist/pluginIntegration/pluginContext.core.js +1 -1
  27. package/dist/pluginLifecycle/pluginLifecycle.abandoned.d.ts +0 -1
  28. package/dist/pluginLifecycle/pluginLifecycle.abandoned.js +0 -1
  29. package/dist/pluginLifecycle/pluginLifecycle.core.d.ts +5 -1
  30. package/dist/pluginLifecycle/pluginLifecycle.core.js +9 -3
  31. package/dist/pluginLifecycle/pluginLifecycle.phaseError.d.ts +21 -0
  32. package/dist/pluginLifecycle/pluginLifecycle.phaseError.js +43 -0
  33. package/dist/pluginLifecycle/pluginLifecycle.report.d.ts +0 -1
  34. package/dist/pluginLifecycle/pluginLifecycle.report.js +0 -1
  35. package/dist/pluginManager/pluginManager.core.d.ts +6 -2
  36. package/dist/pluginManager/pluginManager.core.js +9 -1
  37. package/dist/pluginRegistry/index.d.ts +0 -1
  38. package/dist/pluginRegistry/index.js +0 -1
  39. package/dist/pluginRegistry/pluginRegistry.core.d.ts +0 -1
  40. package/dist/pluginRegistry/pluginRegistry.core.js +0 -1
  41. package/dist/pluginTypes/index.d.ts +1 -1
  42. package/dist/pluginTypes/index.js +0 -1
  43. package/dist/pluginTypes/plugin.type.d.ts +19 -1
  44. package/dist/pluginTypes/plugin.type.js +0 -1
  45. package/dist/pluginTypes/pluginContext.type.d.ts +13 -1
  46. package/dist/pluginTypes/pluginContext.type.js +0 -1
  47. package/dist/pluginTypes/pluginDependency.type.d.ts +0 -1
  48. package/dist/pluginTypes/pluginDependency.type.js +0 -1
  49. package/dist/pluginTypes/pluginMetadata.type.d.ts +0 -1
  50. package/dist/pluginTypes/pluginMetadata.type.js +0 -1
  51. package/dist/pluginTypes/pluginState.type.d.ts +0 -1
  52. package/dist/pluginTypes/pluginState.type.js +0 -1
  53. package/package.json +3 -3
package/dist/index.d.ts CHANGED
@@ -56,11 +56,10 @@ export type { LifecycleControllerOptions } from "./pluginLifecycle/pluginLifecyc
56
56
  export { PLUGIN_EVENTS, createPluginLifecycleEvent, } from "./pluginEvents/pluginEvent.core.js";
57
57
  export type { PluginLifecycleEvent } from "./pluginEvents/pluginEvent.core.js";
58
58
  export { isPluginEventBus, toPluginEvents, } from "./pluginEvents/pluginEvent.bus.js";
59
- export { buildDiagnosticReport, createHealthyHealth, createDegradedHealth, createUnhealthyHealth, } from "./pluginDiagnostics/pluginDiagnostic.core.js";
59
+ export { buildDiagnosticReport, resolvePluginHealth, createHealthyHealth, createDegradedHealth, createUnhealthyHealth, } from "./pluginDiagnostics/pluginDiagnostic.core.js";
60
60
  export type { PluginHealth, PluginHealthStatus, PluginDiagnostic, PluginDiagnosticReport, } from "./pluginDiagnostics/pluginDiagnostic.core.js";
61
61
  export { isValidTransition, VALID_STATE_TRANSITIONS, } from "./pluginTypes/pluginState.type.js";
62
62
  export { createPluginContext, createOwnedPluginContext, } from "./pluginIntegration/pluginContext.core.js";
63
63
  export type { CreatePluginContextOptions, OwnedPluginContext, } from "./pluginIntegration/pluginContext.core.js";
64
64
  export { PluginError, PluginRegistrationError, PluginAlreadyRegisteredError, PluginNotFoundError, PluginDependencyError, PluginDependencyCycleError, PluginInitializationError, PluginStartError, PluginStopError, PluginDisposeError, PluginTimeoutError, PluginStateError, createPluginError, isPluginError, } from "@zudojs/errors";
65
65
  export type { PluginState, PluginMetadata, PluginDependency, PluginContext, PluginContainer, PluginConfig, PluginLogger, PluginEvents, PluginEventBus, PluginEventSource, PluginDisposable, Plugin, PluginErrorOptions, } from "./pluginTypes/index.js";
66
- //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -50,8 +50,7 @@ export { PluginDependencyVersionError } from "./pluginDependencies/versionCheck.
50
50
  export { LifecycleController } from "./pluginLifecycle/pluginLifecycle.core.js";
51
51
  export { PLUGIN_EVENTS, createPluginLifecycleEvent, } from "./pluginEvents/pluginEvent.core.js";
52
52
  export { isPluginEventBus, toPluginEvents, } from "./pluginEvents/pluginEvent.bus.js";
53
- export { buildDiagnosticReport, createHealthyHealth, createDegradedHealth, createUnhealthyHealth, } from "./pluginDiagnostics/pluginDiagnostic.core.js";
53
+ export { buildDiagnosticReport, resolvePluginHealth, createHealthyHealth, createDegradedHealth, createUnhealthyHealth, } from "./pluginDiagnostics/pluginDiagnostic.core.js";
54
54
  export { isValidTransition, VALID_STATE_TRANSITIONS, } from "./pluginTypes/pluginState.type.js";
55
55
  export { createPluginContext, createOwnedPluginContext, } from "./pluginIntegration/pluginContext.core.js";
56
56
  export { PluginError, PluginRegistrationError, PluginAlreadyRegisteredError, PluginNotFoundError, PluginDependencyError, PluginDependencyCycleError, PluginInitializationError, PluginStartError, PluginStopError, PluginDisposeError, PluginTimeoutError, PluginStateError, createPluginError, isPluginError, } from "@zudojs/errors";
57
- //# sourceMappingURL=index.js.map
@@ -67,4 +67,3 @@ export declare class DependencyResolver {
67
67
  * and the dependency itself, so a startup failure says who needed what.
68
68
  */
69
69
  export declare function assertResolutionValid(resolution: DependencyResolution): void;
70
- //# sourceMappingURL=dependencyResolver.core.d.ts.map
@@ -148,4 +148,3 @@ export function assertResolutionValid(resolution) {
148
148
  throw new PluginDependencyCycleError(resolution.cycles[0].split(" -> "));
149
149
  }
150
150
  }
151
- //# sourceMappingURL=dependencyResolver.core.js.map
@@ -9,4 +9,3 @@ export { DependencyResolver, assertResolutionValid, } from "./dependencyResolver
9
9
  export type { SemVer } from "./versionCheck.core.js";
10
10
  export { PluginDependencyVersionError } from "./versionCheck.core.js";
11
11
  export { parseVersion, compareVersions, satisfiesVersion, assertDependencyVersions, } from "./versionCheck.core.js";
12
- //# sourceMappingURL=index.d.ts.map
@@ -7,4 +7,3 @@
7
7
  export { DependencyResolver, assertResolutionValid, } from "./dependencyResolver.core.js";
8
8
  export { PluginDependencyVersionError } from "./versionCheck.core.js";
9
9
  export { parseVersion, compareVersions, satisfiesVersion, assertDependencyVersions, } from "./versionCheck.core.js";
10
- //# sourceMappingURL=index.js.map
@@ -64,4 +64,3 @@ export declare function satisfiesVersion(version: string, range: string): boolea
64
64
  * @throws {PluginDependencyError} on the first unsatisfied constraint.
65
65
  */
66
66
  export declare function assertDependencyVersions(plugins: ReadonlyMap<string, Plugin>): void;
67
- //# sourceMappingURL=versionCheck.core.d.ts.map
@@ -196,4 +196,3 @@ export function assertDependencyVersions(plugins) {
196
196
  }
197
197
  }
198
198
  }
199
- //# sourceMappingURL=versionCheck.core.js.map
@@ -4,5 +4,4 @@
4
4
  * Plugin health checks and diagnostic reporting.
5
5
  */
6
6
  export type { PluginHealthStatus, PluginHealth, PluginDiagnostic, PluginDiagnosticReport, } from "./pluginDiagnostic.core.js";
7
- export { createHealthyHealth, createDegradedHealth, createUnhealthyHealth, buildDiagnosticReport, } from "./pluginDiagnostic.core.js";
8
- //# sourceMappingURL=index.d.ts.map
7
+ export { createHealthyHealth, createDegradedHealth, createUnhealthyHealth, buildDiagnosticReport, resolvePluginHealth, } from "./pluginDiagnostic.core.js";
@@ -3,5 +3,4 @@
3
3
  *
4
4
  * Plugin health checks and diagnostic reporting.
5
5
  */
6
- export { createHealthyHealth, createDegradedHealth, createUnhealthyHealth, buildDiagnosticReport, } from "./pluginDiagnostic.core.js";
7
- //# sourceMappingURL=index.js.map
6
+ export { createHealthyHealth, createDegradedHealth, createUnhealthyHealth, buildDiagnosticReport, resolvePluginHealth, } from "./pluginDiagnostic.core.js";
@@ -1,17 +1,10 @@
1
1
  import type { Plugin } from "../pluginTypes/plugin.type.js";
2
+ import type { PluginHealth, PluginHealthStatus } from "../pluginTypes/plugin.type.js";
2
3
  import type { PluginMetadata } from "../pluginTypes/pluginMetadata.type.js";
3
4
  import type { PluginState } from "../pluginTypes/pluginState.type.js";
4
- /**
5
- * Plugin health status.
6
- */
7
- export type PluginHealthStatus = "healthy" | "degraded" | "unhealthy";
8
- /**
9
- * Plugin health information.
10
- */
11
- export interface PluginHealth {
12
- readonly status: PluginHealthStatus;
13
- readonly details?: unknown;
14
- }
5
+ import { createDegradedHealth, createHealthyHealth, createUnhealthyHealth, resolvePluginHealth } from "./pluginDiagnostic.health.js";
6
+ export type { PluginHealth, PluginHealthStatus };
7
+ export { createDegradedHealth, createHealthyHealth, createUnhealthyHealth, resolvePluginHealth, };
15
8
  /**
16
9
  * Individual plugin diagnostic information.
17
10
  */
@@ -35,18 +28,6 @@ export interface PluginDiagnosticReport {
35
28
  readonly unhealthy: number;
36
29
  readonly failed: number;
37
30
  }
38
- /**
39
- * Creates a default healthy status for a plugin.
40
- */
41
- export declare function createHealthyHealth(): PluginHealth;
42
- /**
43
- * Creates a degraded health status for a plugin.
44
- */
45
- export declare function createDegradedHealth(details?: unknown): PluginHealth;
46
- /**
47
- * Creates an unhealthy health status for a plugin.
48
- */
49
- export declare function createUnhealthyHealth(details?: unknown): PluginHealth;
50
31
  /**
51
32
  * Builds a diagnostic report from registered plugins.
52
33
  */
@@ -61,4 +42,3 @@ export declare function buildDiagnosticReport(plugins: Array<{
61
42
  readonly failed?: boolean;
62
43
  readonly error?: unknown;
63
44
  }>): PluginDiagnosticReport;
64
- //# sourceMappingURL=pluginDiagnostic.core.d.ts.map
@@ -1,21 +1,5 @@
1
- /**
2
- * Creates a default healthy status for a plugin.
3
- */
4
- export function createHealthyHealth() {
5
- return { status: "healthy" };
6
- }
7
- /**
8
- * Creates a degraded health status for a plugin.
9
- */
10
- export function createDegradedHealth(details) {
11
- return { status: "degraded", ...(details !== undefined ? { details } : {}) };
12
- }
13
- /**
14
- * Creates an unhealthy health status for a plugin.
15
- */
16
- export function createUnhealthyHealth(details) {
17
- return { status: "unhealthy", ...(details !== undefined ? { details } : {}) };
18
- }
1
+ import { createDegradedHealth, createHealthyHealth, createUnhealthyHealth, resolvePluginHealth, } from "./pluginDiagnostic.health.js";
2
+ export { createDegradedHealth, createHealthyHealth, createUnhealthyHealth, resolvePluginHealth, };
19
3
  /**
20
4
  * Builds a diagnostic report from registered plugins.
21
5
  */
@@ -26,11 +10,7 @@ export function buildDiagnosticReport(plugins) {
26
10
  plugin: plugin.metadata,
27
11
  state,
28
12
  failed: hasFailed,
29
- health: hasFailed
30
- ? createUnhealthyHealth(error instanceof Error ? error.message : error)
31
- : state === "started"
32
- ? createHealthyHealth()
33
- : createDegradedHealth(),
13
+ health: resolvePluginHealth(plugin, state, hasFailed, error),
34
14
  dependencies: plugin.dependencies?.map((d) => d.name) ?? [],
35
15
  optionalDependencies: plugin.optionalDependencies?.map((d) => d.name) ?? [],
36
16
  };
@@ -48,4 +28,3 @@ export function buildDiagnosticReport(plugins) {
48
28
  failed,
49
29
  };
50
30
  }
51
- //# sourceMappingURL=pluginDiagnostic.core.js.map
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Health resolution for one plugin, from its lifecycle state and its own
3
+ * `health()` hook.
4
+ *
5
+ * @module pluginDiagnostics/pluginDiagnostic.health
6
+ */
7
+ import type { Plugin } from "../pluginTypes/plugin.type.js";
8
+ import type { PluginHealth } from "../pluginTypes/plugin.type.js";
9
+ import type { PluginState } from "../pluginTypes/pluginState.type.js";
10
+ /**
11
+ * Creates a default healthy status for a plugin.
12
+ */
13
+ export declare function createHealthyHealth(): PluginHealth;
14
+ /**
15
+ * Creates a degraded health status for a plugin.
16
+ */
17
+ export declare function createDegradedHealth(details?: unknown): PluginHealth;
18
+ /**
19
+ * Creates an unhealthy health status for a plugin.
20
+ */
21
+ export declare function createUnhealthyHealth(details?: unknown): PluginHealth;
22
+ /**
23
+ * Health of one plugin.
24
+ *
25
+ * A failed plugin is `unhealthy`. A started plugin answers with its own
26
+ * `health()` when it has one (a throwing `health()` is `unhealthy`), else
27
+ * `healthy`. A plugin part-way through boot or a transition is `degraded`.
28
+ * A plugin that is idle — `registered`, or cleanly `stopped` or `disposed`
29
+ * — is `healthy`: before this rule every plugin read `degraded` after a
30
+ * clean `stop()`, which made the report useless during shutdown.
31
+ */
32
+ export declare function resolvePluginHealth(plugin: Plugin, state: PluginState, failed: boolean, error?: unknown): PluginHealth;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Health resolution for one plugin, from its lifecycle state and its own
3
+ * `health()` hook.
4
+ *
5
+ * @module pluginDiagnostics/pluginDiagnostic.health
6
+ */
7
+ /**
8
+ * States in which a plugin is between two settled states, or brought
9
+ * part-way up by a `start()` that has not finished.
10
+ */
11
+ const IN_PROGRESS_STATES = new Set([
12
+ "installing",
13
+ "installed",
14
+ "initializing",
15
+ "initialized",
16
+ "starting",
17
+ "stopping",
18
+ "disposing",
19
+ ]);
20
+ /**
21
+ * Creates a default healthy status for a plugin.
22
+ */
23
+ export function createHealthyHealth() {
24
+ return { status: "healthy" };
25
+ }
26
+ /**
27
+ * Creates a degraded health status for a plugin.
28
+ */
29
+ export function createDegradedHealth(details) {
30
+ return { status: "degraded", ...(details !== undefined ? { details } : {}) };
31
+ }
32
+ /**
33
+ * Creates an unhealthy health status for a plugin.
34
+ */
35
+ export function createUnhealthyHealth(details) {
36
+ return { status: "unhealthy", ...(details !== undefined ? { details } : {}) };
37
+ }
38
+ /**
39
+ * Health of one plugin.
40
+ *
41
+ * A failed plugin is `unhealthy`. A started plugin answers with its own
42
+ * `health()` when it has one (a throwing `health()` is `unhealthy`), else
43
+ * `healthy`. A plugin part-way through boot or a transition is `degraded`.
44
+ * A plugin that is idle — `registered`, or cleanly `stopped` or `disposed`
45
+ * — is `healthy`: before this rule every plugin read `degraded` after a
46
+ * clean `stop()`, which made the report useless during shutdown.
47
+ */
48
+ export function resolvePluginHealth(plugin, state, failed, error) {
49
+ if (failed) {
50
+ return createUnhealthyHealth(error instanceof Error ? error.message : error);
51
+ }
52
+ if (state === "started") {
53
+ if (typeof plugin.health !== "function")
54
+ return createHealthyHealth();
55
+ try {
56
+ return plugin.health();
57
+ }
58
+ catch (cause) {
59
+ return createUnhealthyHealth(cause instanceof Error ? cause.message : cause);
60
+ }
61
+ }
62
+ return IN_PROGRESS_STATES.has(state)
63
+ ? createDegradedHealth({ state })
64
+ : createHealthyHealth();
65
+ }
@@ -7,4 +7,3 @@ export type { PluginLifecycleEvent } from "./pluginEvent.core.js";
7
7
  export { PLUGIN_EVENTS, createPluginLifecycleEvent, } from "./pluginEvent.core.js";
8
8
  export { isPluginEventBus, toPluginEvents } from "./pluginEvent.bus.js";
9
9
  export { deliverPluginEvent } from "./pluginEvent.deliver.js";
10
- //# sourceMappingURL=index.d.ts.map
@@ -6,4 +6,3 @@
6
6
  export { PLUGIN_EVENTS, createPluginLifecycleEvent, } from "./pluginEvent.core.js";
7
7
  export { isPluginEventBus, toPluginEvents } from "./pluginEvent.bus.js";
8
8
  export { deliverPluginEvent } from "./pluginEvent.deliver.js";
9
- //# sourceMappingURL=index.js.map
@@ -23,4 +23,3 @@ export declare function isPluginEventBus(source: PluginEventSource): source is P
23
23
  * @returns A {@link PluginEvents} view of it.
24
24
  */
25
25
  export declare function toPluginEvents(source: PluginEventSource): PluginEvents;
26
- //# sourceMappingURL=pluginEvent.bus.d.ts.map
@@ -53,4 +53,3 @@ export function toPluginEvents(source) {
53
53
  },
54
54
  };
55
55
  }
56
- //# sourceMappingURL=pluginEvent.bus.js.map
@@ -31,4 +31,3 @@ export declare const PLUGIN_EVENTS: {
31
31
  * Creates a plugin lifecycle event.
32
32
  */
33
33
  export declare function createPluginLifecycleEvent(plugin: PluginMetadata, state: PluginState, previousState?: PluginState, error?: unknown): PluginLifecycleEvent;
34
- //# sourceMappingURL=pluginEvent.core.d.ts.map
@@ -27,4 +27,3 @@ export function createPluginLifecycleEvent(plugin, state, previousState, error)
27
27
  ...(error !== undefined ? { error } : {}),
28
28
  };
29
29
  }
30
- //# sourceMappingURL=pluginEvent.core.js.map
@@ -15,4 +15,3 @@ import type { PluginEvents } from "../pluginTypes/pluginContext.type.js";
15
15
  * @param onFailure - Receives a synchronous throw or an async rejection.
16
16
  */
17
17
  export declare function deliverPluginEvent(events: PluginEvents, name: string, payload: unknown, onFailure: (error: unknown) => void): void;
18
- //# sourceMappingURL=pluginEvent.deliver.d.ts.map
@@ -38,4 +38,3 @@ function isThenable(value) {
38
38
  (typeof value === "object" || typeof value === "function") &&
39
39
  typeof value.then === "function");
40
40
  }
41
- //# sourceMappingURL=pluginEvent.deliver.js.map
@@ -6,4 +6,3 @@
6
6
  export type { CreatePluginContextOptions, OwnedPluginContext, } from "./pluginContext.core.js";
7
7
  export { createPluginContext, createOwnedPluginContext, } from "./pluginContext.core.js";
8
8
  export type { PluginContext } from "../pluginTypes/pluginContext.type.js";
9
- //# sourceMappingURL=index.d.ts.map
@@ -4,4 +4,3 @@
4
4
  * Plugin context factory and integration helpers.
5
5
  */
6
6
  export { createPluginContext, createOwnedPluginContext, } from "./pluginContext.core.js";
7
- //# sourceMappingURL=index.js.map
@@ -24,6 +24,12 @@ export interface CreatePluginContextOptions {
24
24
  * one it aborts on shutdown so plugins are told to stop.
25
25
  */
26
26
  readonly abortController?: AbortController;
27
+ /**
28
+ * Metadata of the host application, exposed to the plugin as
29
+ * `context.host`. The manager fills it from the `plugin` metadata of
30
+ * the context passed to `start()`/`stop()`.
31
+ */
32
+ readonly host?: PluginMetadata;
27
33
  }
28
34
  /**
29
35
  * A plugin context together with the handles needed to tear it down.
@@ -51,4 +57,3 @@ export declare function createOwnedPluginContext(plugin: PluginMetadata, options
51
57
  * collection and the abort handle with the context.
52
58
  */
53
59
  export declare function createPluginContext(plugin: PluginMetadata, options?: CreatePluginContextOptions): PluginContext;
54
- //# sourceMappingURL=pluginContext.core.d.ts.map
@@ -11,6 +11,7 @@ export function createOwnedPluginContext(plugin, options = {}) {
11
11
  const disposables = options.disposables ?? [];
12
12
  const context = {
13
13
  plugin,
14
+ ...(options.host !== undefined ? { host: options.host } : {}),
14
15
  signal: abortController.signal,
15
16
  container: options.container,
16
17
  config: options.config,
@@ -43,4 +44,3 @@ export function createOwnedPluginContext(plugin, options = {}) {
43
44
  export function createPluginContext(plugin, options = {}) {
44
45
  return createOwnedPluginContext(plugin, options).context;
45
46
  }
46
- //# sourceMappingURL=pluginContext.core.js.map
@@ -27,4 +27,3 @@ export declare class AbandonedHooks {
27
27
  hook: Promise<void>;
28
28
  } | undefined>;
29
29
  }
30
- //# sourceMappingURL=pluginLifecycle.abandoned.d.ts.map
@@ -43,4 +43,3 @@ export class AbandonedHooks {
43
43
  }
44
44
  }
45
45
  }
46
- //# sourceMappingURL=pluginLifecycle.abandoned.js.map
@@ -25,6 +25,11 @@ export declare class LifecycleController {
25
25
  /**
26
26
  * Runs a hook under the configured timeout, clearing the timer either
27
27
  * way so a completed hook never leaves one armed.
28
+ *
29
+ * The timer is deliberately ref'd: it guards pending work, and an
30
+ * unref'd one let Node exit (code 13, "unsettled top-level await")
31
+ * when a `start()` hook hung before anything else held the event
32
+ * loop, so no `PluginTimeoutError` was raised and nothing rolled back.
28
33
  */
29
34
  private runHook;
30
35
  install<TPlugin extends Plugin>(registered: RegisteredPlugin<TPlugin>, context: PluginContext): Promise<void>;
@@ -63,4 +68,3 @@ export declare class LifecycleController {
63
68
  private canTransition;
64
69
  private ensureTransition;
65
70
  }
66
- //# sourceMappingURL=pluginLifecycle.core.d.ts.map
@@ -2,6 +2,7 @@ import { isValidTransition } from "../pluginTypes/pluginState.type.js";
2
2
  import { PluginDisposeError, PluginStateError, PluginTimeoutError, } from "@zudojs/errors";
3
3
  import { PLUGIN_EVENTS, createPluginLifecycleEvent, } from "../pluginEvents/pluginEvent.core.js";
4
4
  import { AbandonedHooks } from "./pluginLifecycle.abandoned.js";
5
+ import { toPhaseError } from "./pluginLifecycle.phaseError.js";
5
6
  import { reportPluginFailure } from "./pluginLifecycle.report.js";
6
7
  import { deliverPluginEvent } from "../pluginEvents/pluginEvent.deliver.js";
7
8
  /**
@@ -36,6 +37,11 @@ export class LifecycleController {
36
37
  /**
37
38
  * Runs a hook under the configured timeout, clearing the timer either
38
39
  * way so a completed hook never leaves one armed.
40
+ *
41
+ * The timer is deliberately ref'd: it guards pending work, and an
42
+ * unref'd one let Node exit (code 13, "unsettled top-level await")
43
+ * when a `start()` hook hung before anything else held the event
44
+ * loop, so no `PluginTimeoutError` was raised and nothing rolled back.
39
45
  */
40
46
  async runHook(pluginName, phase, run, onAbandoned) {
41
47
  const timeout = this.options.hookTimeout ?? 0;
@@ -58,7 +64,6 @@ export class LifecycleController {
58
64
  metadata: { phase },
59
65
  }));
60
66
  }, Math.min(timeout, MAX_TIMER_DELAY));
61
- timer.unref?.();
62
67
  }),
63
68
  ]);
64
69
  }
@@ -100,10 +105,12 @@ export class LifecycleController {
100
105
  emitLifecycleEvent(context, endEvent, metadata, settled, transient);
101
106
  }
102
107
  catch (error) {
108
+ // Diagnostics and the `failed` event keep what the hook threw; the
109
+ // caller gets it wrapped in the phase's error, naming the plugin.
103
110
  registered.setError(error);
104
111
  this.transitionToFailed(registered);
105
112
  emitLifecycleEvent(context, PLUGIN_EVENTS.FAILED, metadata, "failed", transient, error);
106
- throw error;
113
+ throw toPhaseError(transient, metadata.name, error);
107
114
  }
108
115
  }
109
116
  /**
@@ -218,4 +225,3 @@ export class LifecycleController {
218
225
  }
219
226
  }
220
227
  }
221
- //# sourceMappingURL=pluginLifecycle.core.js.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Wraps a lifecycle hook's failure in the typed error for its phase.
3
+ *
4
+ * `PluginInitializationError`, `PluginStartError` and `PluginStopError`
5
+ * were exported but never thrown: a hook's raw error was rethrown as-is,
6
+ * so nothing but `diagnostics()` said which plugin had failed. The wrap
7
+ * names the plugin and the phase and keeps the original as `cause`.
8
+ *
9
+ * @module pluginLifecycle/pluginLifecycle.phaseError
10
+ */
11
+ import type { PluginState } from "../pluginTypes/pluginState.type.js";
12
+ /**
13
+ * Returns the error to throw for a hook that failed in `phase`.
14
+ *
15
+ * A `PluginError` — a `PluginTimeoutError`, `PluginStateError`, or a
16
+ * typed error a plugin threw on purpose — already names the plugin and
17
+ * lets callers branch on it, so it propagates unchanged. Anything else is
18
+ * wrapped in the phase's error class with the original as `cause`; the
19
+ * message quotes the original's so existing substring matches still hold.
20
+ */
21
+ export declare function toPhaseError(phase: PluginState, pluginName: string, error: unknown): unknown;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Wraps a lifecycle hook's failure in the typed error for its phase.
3
+ *
4
+ * `PluginInitializationError`, `PluginStartError` and `PluginStopError`
5
+ * were exported but never thrown: a hook's raw error was rethrown as-is,
6
+ * so nothing but `diagnostics()` said which plugin had failed. The wrap
7
+ * names the plugin and the phase and keeps the original as `cause`.
8
+ *
9
+ * @module pluginLifecycle/pluginLifecycle.phaseError
10
+ */
11
+ import { PluginError, PluginInitializationError, PluginStartError, PluginStopError, } from "@zudojs/errors";
12
+ /** The lifecycle-hook error class for each transient phase. */
13
+ const PHASE_ERRORS = {
14
+ installing: PluginInitializationError,
15
+ initializing: PluginInitializationError,
16
+ starting: PluginStartError,
17
+ stopping: PluginStopError,
18
+ };
19
+ /** The verb a phase's failure message uses. */
20
+ const PHASE_VERBS = {
21
+ installing: "install",
22
+ initializing: "initialize",
23
+ starting: "start",
24
+ stopping: "stop",
25
+ };
26
+ /**
27
+ * Returns the error to throw for a hook that failed in `phase`.
28
+ *
29
+ * A `PluginError` — a `PluginTimeoutError`, `PluginStateError`, or a
30
+ * typed error a plugin threw on purpose — already names the plugin and
31
+ * lets callers branch on it, so it propagates unchanged. Anything else is
32
+ * wrapped in the phase's error class with the original as `cause`; the
33
+ * message quotes the original's so existing substring matches still hold.
34
+ */
35
+ export function toPhaseError(phase, pluginName, error) {
36
+ if (error instanceof PluginError)
37
+ return error;
38
+ const PhaseError = PHASE_ERRORS[phase];
39
+ if (PhaseError === undefined)
40
+ return error;
41
+ const detail = error instanceof Error ? error.message : String(error);
42
+ return new PhaseError(`Plugin "${pluginName}" failed to ${PHASE_VERBS[phase]}: ${detail}`, pluginName, { cause: error });
43
+ }
@@ -13,4 +13,3 @@ import type { PluginLogger } from "../pluginTypes/pluginContext.type.js";
13
13
  * @param fields - Extra structured context.
14
14
  */
15
15
  export declare function reportPluginFailure(logger: PluginLogger | undefined, message: string, error: unknown, fields?: Record<string, unknown>): void;
16
- //# sourceMappingURL=pluginLifecycle.report.d.ts.map
@@ -28,4 +28,3 @@ export function reportPluginFailure(logger, message, error, fields = {}) {
28
28
  detail,
29
29
  });
30
30
  }
31
- //# sourceMappingURL=pluginLifecycle.report.js.map
@@ -85,10 +85,15 @@ export declare class PluginManager {
85
85
  /**
86
86
  * Registers a plugin.
87
87
  *
88
+ * `options` is typed from the plugin: for a `Plugin<TOptions>` (a
89
+ * plugin whose `install(context, options)` declares its options type)
90
+ * it must be a `TOptions`, so a mistyped options object is a compile
91
+ * error rather than a runtime surprise in `install`.
92
+ *
88
93
  * @throws {PluginAlreadyRegisteredError} when the name is taken, so
89
94
  * callers can implement idempotent registration.
90
95
  */
91
- register<TPlugin extends Plugin>(plugin: TPlugin, options?: unknown): void;
96
+ register<TOptions = unknown>(plugin: Plugin<TOptions>, options?: TOptions): void;
92
97
  /**
93
98
  * Removes a plugin that has not been started.
94
99
  *
@@ -151,4 +156,3 @@ export declare class PluginManager {
151
156
  private report;
152
157
  private toDependencyMap;
153
158
  }
154
- //# sourceMappingURL=pluginManager.core.d.ts.map
@@ -61,6 +61,11 @@ export class PluginManager {
61
61
  /**
62
62
  * Registers a plugin.
63
63
  *
64
+ * `options` is typed from the plugin: for a `Plugin<TOptions>` (a
65
+ * plugin whose `install(context, options)` declares its options type)
66
+ * it must be a `TOptions`, so a mistyped options object is a compile
67
+ * error rather than a runtime surprise in `install`.
68
+ *
64
69
  * @throws {PluginAlreadyRegisteredError} when the name is taken, so
65
70
  * callers can implement idempotent registration.
66
71
  */
@@ -302,6 +307,10 @@ export class PluginManager {
302
307
  logger: base.logger,
303
308
  events: base.events,
304
309
  disposables: registered.disposables,
310
+ // The host's own metadata used to be dropped here: every plugin saw
311
+ // only itself under `context.plugin` and had no way to learn which
312
+ // application was hosting it.
313
+ host: base.host ?? base.plugin,
305
314
  });
306
315
  this.contexts.set(name, { context: owned.context, abort: owned.abort });
307
316
  return owned.context;
@@ -347,4 +356,3 @@ export class PluginManager {
347
356
  return map;
348
357
  }
349
358
  }
350
- //# sourceMappingURL=pluginManager.core.js.map
@@ -5,4 +5,3 @@
5
5
  */
6
6
  export type { PluginRegistry, RegisteredPlugin, } from "./pluginRegistry.core.js";
7
7
  export { PluginRegistryImpl } from "./pluginRegistry.core.js";
8
- //# sourceMappingURL=index.d.ts.map
@@ -4,4 +4,3 @@
4
4
  * Plugin registry for storing and retrieving registered plugins.
5
5
  */
6
6
  export { PluginRegistryImpl } from "./pluginRegistry.core.js";
7
- //# sourceMappingURL=index.js.map
@@ -47,4 +47,3 @@ export declare class PluginRegistryImpl implements PluginRegistry {
47
47
  remove(name: string): boolean;
48
48
  clear(): void;
49
49
  }
50
- //# sourceMappingURL=pluginRegistry.core.d.ts.map
@@ -53,4 +53,3 @@ export class PluginRegistryImpl {
53
53
  this.plugins.clear();
54
54
  }
55
55
  }
56
- //# sourceMappingURL=pluginRegistry.core.js.map
@@ -8,5 +8,5 @@ export type { PluginMetadata } from "./pluginMetadata.type.js";
8
8
  export type { PluginDependency } from "./pluginDependency.type.js";
9
9
  export type { PluginContext, PluginContainer, PluginConfig, PluginLogger, PluginEvents, PluginEventBus, PluginEventSource, PluginDisposable, } from "./pluginContext.type.js";
10
10
  export type { Plugin } from "./plugin.type.js";
11
+ export type { PluginHealth, PluginHealthStatus } from "./plugin.type.js";
11
12
  export type { PluginErrorOptions } from "@zudojs/errors";
12
- //# sourceMappingURL=index.d.ts.map
@@ -4,4 +4,3 @@
4
4
  * Core types for the Zudojs plugin system.
5
5
  */
6
6
  export {};
7
- //# sourceMappingURL=index.js.map
@@ -1,6 +1,18 @@
1
1
  import type { PluginMetadata } from "./pluginMetadata.type.js";
2
2
  import type { PluginDependency } from "./pluginDependency.type.js";
3
3
  import type { PluginContext } from "./pluginContext.type.js";
4
+ /**
5
+ * Plugin health status.
6
+ */
7
+ export type PluginHealthStatus = "healthy" | "degraded" | "unhealthy";
8
+ /**
9
+ * Plugin health information: what a plugin's own `health()` returns and
10
+ * what `PluginManager.diagnostics()` reports for it.
11
+ */
12
+ export interface PluginHealth {
13
+ readonly status: PluginHealthStatus;
14
+ readonly details?: unknown;
15
+ }
4
16
  /**
5
17
  * Plugin interface definition.
6
18
  */
@@ -13,5 +25,11 @@ export interface Plugin<TOptions = unknown> {
13
25
  start?(context: PluginContext): void | Promise<void>;
14
26
  stop?(context: PluginContext): void | Promise<void>;
15
27
  dispose?(context: PluginContext): void | Promise<void>;
28
+ /**
29
+ * Reports the plugin's own health while it is `started`. Consulted by
30
+ * `PluginManager.diagnostics()`, which otherwise derives health from
31
+ * lifecycle state alone; a throwing `health()` is reported as
32
+ * `unhealthy` with the error message as `details`.
33
+ */
34
+ health?(): PluginHealth;
16
35
  }
17
- //# sourceMappingURL=plugin.type.d.ts.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=plugin.type.js.map
@@ -1,9 +1,16 @@
1
1
  import type { PluginMetadata } from "./pluginMetadata.type.js";
2
2
  /**
3
3
  * Minimal container interface for plugin context.
4
+ *
5
+ * `register` is all the plugin system itself needs. `resolve` and `has`
6
+ * are declared optional so a plugin can read from a real container
7
+ * (`@zudojs/container`'s satisfies this shape) without casting; a host
8
+ * that passes a register-only object leaves them `undefined`.
4
9
  */
5
10
  export interface PluginContainer {
6
11
  register(token: unknown, provider: unknown): void;
12
+ resolve?<T = unknown>(token: unknown): T;
13
+ has?(token: unknown): boolean;
7
14
  }
8
15
  /**
9
16
  * Minimal config interface for plugin context.
@@ -63,6 +70,12 @@ export interface PluginDisposable {
63
70
  */
64
71
  export interface PluginContext {
65
72
  readonly plugin: PluginMetadata;
73
+ /**
74
+ * Metadata of the host application — the `plugin` metadata of the
75
+ * context handed to `PluginManager.start()`. Absent on a context built
76
+ * directly with `createPluginContext` and no `host` option.
77
+ */
78
+ readonly host?: PluginMetadata;
66
79
  readonly container?: PluginContainer;
67
80
  readonly config?: PluginConfig;
68
81
  readonly logger?: PluginLogger;
@@ -75,4 +88,3 @@ export interface PluginContext {
75
88
  onDispose(handler: () => void | Promise<void>): void;
76
89
  registerDisposable(disposable: PluginDisposable): void;
77
90
  }
78
- //# sourceMappingURL=pluginContext.type.d.ts.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=pluginContext.type.js.map
@@ -11,4 +11,3 @@ export interface PluginDependency {
11
11
  */
12
12
  readonly optional?: boolean;
13
13
  }
14
- //# sourceMappingURL=pluginDependency.type.d.ts.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=pluginDependency.type.js.map
@@ -10,4 +10,3 @@ export interface PluginMetadata {
10
10
  readonly keywords?: readonly string[];
11
11
  readonly capabilities?: readonly string[];
12
12
  }
13
- //# sourceMappingURL=pluginMetadata.type.d.ts.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=pluginMetadata.type.js.map
@@ -10,4 +10,3 @@ export declare const VALID_STATE_TRANSITIONS: Readonly<Record<PluginState, reado
10
10
  * Determines whether a state transition is valid.
11
11
  */
12
12
  export declare function isValidTransition(from: PluginState, to: PluginState): boolean;
13
- //# sourceMappingURL=pluginState.type.d.ts.map
@@ -24,4 +24,3 @@ export const VALID_STATE_TRANSITIONS = {
24
24
  export function isValidTransition(from, to) {
25
25
  return VALID_STATE_TRANSITIONS[from]?.includes(to) ?? false;
26
26
  }
27
- //# sourceMappingURL=pluginState.type.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zudojs/plugins",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
4
4
  "description": "Plugin system for extending Zudojs applications with modular capabilities.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -24,11 +24,11 @@
24
24
  "!dist/.tsbuildinfo"
25
25
  ],
26
26
  "dependencies": {
27
- "@zudojs/errors": "1.3.1"
27
+ "@zudojs/errors": "1.4.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/node": "^26.6.2",
31
- "@zudojs/events": "1.3.2",
31
+ "@zudojs/events": "1.4.0",
32
32
  "typescript": "7.0.2",
33
33
  "vitest": "^5.0.1"
34
34
  },