@zudojs/plugins 0.1.0 → 1.1.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 (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +135 -29
  3. package/dist/index.d.ts +34 -13
  4. package/dist/index.js +28 -10
  5. package/dist/pluginDependencies/dependencyResolver.core.d.ts +47 -6
  6. package/dist/pluginDependencies/dependencyResolver.core.js +108 -46
  7. package/dist/pluginDependencies/index.d.ts +6 -2
  8. package/dist/pluginDependencies/index.js +4 -1
  9. package/dist/pluginDependencies/versionCheck.core.d.ts +67 -0
  10. package/dist/pluginDependencies/versionCheck.core.js +199 -0
  11. package/dist/pluginDiagnostics/pluginDiagnostic.core.d.ts +9 -0
  12. package/dist/pluginDiagnostics/pluginDiagnostic.core.js +16 -12
  13. package/dist/pluginIntegration/index.d.ts +2 -1
  14. package/dist/pluginIntegration/index.js +1 -1
  15. package/dist/pluginIntegration/pluginContext.core.d.ts +35 -5
  16. package/dist/pluginIntegration/pluginContext.core.js +28 -5
  17. package/dist/pluginLifecycle/pluginLifecycle.core.d.ts +42 -0
  18. package/dist/pluginLifecycle/pluginLifecycle.core.js +132 -70
  19. package/dist/pluginManager/pluginManager.core.d.ts +121 -3
  20. package/dist/pluginManager/pluginManager.core.js +282 -47
  21. package/dist/pluginRegistry/pluginRegistry.core.d.ts +15 -6
  22. package/dist/pluginRegistry/pluginRegistry.core.js +6 -1
  23. package/dist/pluginTypes/index.d.ts +1 -1
  24. package/dist/pluginTypes/pluginContext.type.d.ts +11 -3
  25. package/dist/pluginTypes/pluginState.type.js +6 -3
  26. package/package.json +26 -15
  27. package/dist/.tsbuildinfo +0 -1
  28. package/dist/index.d.ts.map +0 -1
  29. package/dist/index.js.map +0 -1
  30. package/dist/pluginDependencies/dependencyResolver.core.d.ts.map +0 -1
  31. package/dist/pluginDependencies/dependencyResolver.core.js.map +0 -1
  32. package/dist/pluginDependencies/index.d.ts.map +0 -1
  33. package/dist/pluginDependencies/index.js.map +0 -1
  34. package/dist/pluginDiagnostics/index.d.ts.map +0 -1
  35. package/dist/pluginDiagnostics/index.js.map +0 -1
  36. package/dist/pluginDiagnostics/pluginDiagnostic.core.d.ts.map +0 -1
  37. package/dist/pluginDiagnostics/pluginDiagnostic.core.js.map +0 -1
  38. package/dist/pluginEvents/index.d.ts.map +0 -1
  39. package/dist/pluginEvents/index.js.map +0 -1
  40. package/dist/pluginEvents/pluginEvent.core.d.ts.map +0 -1
  41. package/dist/pluginEvents/pluginEvent.core.js.map +0 -1
  42. package/dist/pluginIntegration/index.d.ts.map +0 -1
  43. package/dist/pluginIntegration/index.js.map +0 -1
  44. package/dist/pluginIntegration/pluginContext.core.d.ts.map +0 -1
  45. package/dist/pluginIntegration/pluginContext.core.js.map +0 -1
  46. package/dist/pluginLifecycle/pluginLifecycle.core.d.ts.map +0 -1
  47. package/dist/pluginLifecycle/pluginLifecycle.core.js.map +0 -1
  48. package/dist/pluginManager/pluginManager.core.d.ts.map +0 -1
  49. package/dist/pluginManager/pluginManager.core.js.map +0 -1
  50. package/dist/pluginRegistry/index.d.ts.map +0 -1
  51. package/dist/pluginRegistry/index.js.map +0 -1
  52. package/dist/pluginRegistry/pluginRegistry.core.d.ts.map +0 -1
  53. package/dist/pluginRegistry/pluginRegistry.core.js.map +0 -1
  54. package/dist/pluginTypes/index.d.ts.map +0 -1
  55. package/dist/pluginTypes/index.js.map +0 -1
  56. package/dist/pluginTypes/plugin.type.d.ts.map +0 -1
  57. package/dist/pluginTypes/plugin.type.js.map +0 -1
  58. package/dist/pluginTypes/pluginContext.type.d.ts.map +0 -1
  59. package/dist/pluginTypes/pluginContext.type.js.map +0 -1
  60. package/dist/pluginTypes/pluginDependency.type.d.ts.map +0 -1
  61. package/dist/pluginTypes/pluginDependency.type.js.map +0 -1
  62. package/dist/pluginTypes/pluginMetadata.type.d.ts.map +0 -1
  63. package/dist/pluginTypes/pluginMetadata.type.js.map +0 -1
  64. package/dist/pluginTypes/pluginState.type.d.ts.map +0 -1
  65. package/dist/pluginTypes/pluginState.type.js.map +0 -1
@@ -0,0 +1,67 @@
1
+ import type { Plugin } from "../pluginTypes/plugin.type.js";
2
+ import { PluginDependencyError } from "@zudojs/errors";
3
+ /**
4
+ * A dependency that is registered but whose version does not satisfy the
5
+ * declared constraint.
6
+ *
7
+ * `PluginDependencyError` hardcodes the message "...which is not
8
+ * registered" and replaces any `metadata` it is handed, so the reason a
9
+ * version check failed could never reach the operator: every version
10
+ * mismatch was reported as a missing plugin. This subclass keeps the
11
+ * type — existing `instanceof PluginDependencyError` handlers still
12
+ * match — and replaces the message with one that names the requirement,
13
+ * what is actually registered, and what to do about it.
14
+ */
15
+ export declare class PluginDependencyVersionError extends PluginDependencyError {
16
+ /** The plugin that declared the constraint. */
17
+ readonly requiredBy: string;
18
+ /** The dependency whose version was checked. */
19
+ readonly dependencyName: string;
20
+ /** The declared range, e.g. `^2.0.0`. */
21
+ readonly required: string;
22
+ /** The version actually registered, if it declared one. */
23
+ readonly actual: string | undefined;
24
+ constructor(requiredBy: string, dependencyName: string, required: string, actual: string | undefined, reason: string);
25
+ }
26
+ /**
27
+ * A parsed semantic version.
28
+ *
29
+ * Exported because {@link parseVersion} returns one and
30
+ * {@link compareVersions} accepts two: a consumer that cannot name the
31
+ * type cannot use either function from TypeScript.
32
+ */
33
+ export interface SemVer {
34
+ readonly major: number;
35
+ readonly minor: number;
36
+ readonly patch: number;
37
+ readonly prerelease?: string;
38
+ }
39
+ /**
40
+ * Parses a semantic version, returning `undefined` if it is not one.
41
+ */
42
+ export declare function parseVersion(version: string): SemVer | undefined;
43
+ /**
44
+ * Compares two versions. Prerelease versions sort below their release.
45
+ */
46
+ export declare function compareVersions(a: SemVer, b: SemVer): number;
47
+ /**
48
+ * Tests a version against a range.
49
+ *
50
+ * Supports the range forms a plugin manifest realistically uses:
51
+ * exact (`1.2.3`), caret (`^1.2.3`), tilde (`~1.2.3`), comparators
52
+ * (`>=1.2.3`, `>`, `<=`, `<`), and `*` for any version. An
53
+ * unrecognisable range is reported by the caller rather than silently
54
+ * passing.
55
+ */
56
+ export declare function satisfiesVersion(version: string, range: string): boolean | undefined;
57
+ /**
58
+ * Verifies every declared dependency version constraint.
59
+ *
60
+ * A dependency that declares no `version` is unconstrained. A plugin
61
+ * whose own `metadata.version` is missing cannot be checked, so a
62
+ * constraint against it is reported rather than quietly passing.
63
+ *
64
+ * @throws {PluginDependencyError} on the first unsatisfied constraint.
65
+ */
66
+ export declare function assertDependencyVersions(plugins: ReadonlyMap<string, Plugin>): void;
67
+ //# sourceMappingURL=versionCheck.core.d.ts.map
@@ -0,0 +1,199 @@
1
+ import { PluginDependencyError } from "@zudojs/errors";
2
+ /**
3
+ * A dependency that is registered but whose version does not satisfy the
4
+ * declared constraint.
5
+ *
6
+ * `PluginDependencyError` hardcodes the message "...which is not
7
+ * registered" and replaces any `metadata` it is handed, so the reason a
8
+ * version check failed could never reach the operator: every version
9
+ * mismatch was reported as a missing plugin. This subclass keeps the
10
+ * type — existing `instanceof PluginDependencyError` handlers still
11
+ * match — and replaces the message with one that names the requirement,
12
+ * what is actually registered, and what to do about it.
13
+ */
14
+ export class PluginDependencyVersionError extends PluginDependencyError {
15
+ /** The plugin that declared the constraint. */
16
+ requiredBy;
17
+ /** The dependency whose version was checked. */
18
+ dependencyName;
19
+ /** The declared range, e.g. `^2.0.0`. */
20
+ required;
21
+ /** The version actually registered, if it declared one. */
22
+ actual;
23
+ constructor(requiredBy, dependencyName, required, actual, reason) {
24
+ super(requiredBy, dependencyName);
25
+ this.name = "PluginDependencyVersionError";
26
+ this.message = reason;
27
+ this.requiredBy = requiredBy;
28
+ this.dependencyName = dependencyName;
29
+ this.required = required;
30
+ this.actual = actual;
31
+ }
32
+ }
33
+ const SEMVER_PATTERN = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
34
+ // The input is already trimmed, so the outer `\s*` are unnecessary, and
35
+ // `(\S.*)` rather than `(.+?)\s*$`: `.` matches spaces too, so a lazy group
36
+ // followed by `\s*$` lets the engine split a whitespace run at every
37
+ // position before giving up.
38
+ const RANGE_PATTERN = /^(\^|~|>=|<=|>|<|=)?\s*(\S.*)$/;
39
+ /**
40
+ * Parses a semantic version, returning `undefined` if it is not one.
41
+ */
42
+ export function parseVersion(version) {
43
+ const match = SEMVER_PATTERN.exec(version.trim());
44
+ if (!match) {
45
+ return undefined;
46
+ }
47
+ return {
48
+ major: Number(match[1]),
49
+ minor: Number(match[2]),
50
+ patch: Number(match[3]),
51
+ prerelease: match[4],
52
+ };
53
+ }
54
+ /**
55
+ * Compares two versions. Prerelease versions sort below their release.
56
+ */
57
+ export function compareVersions(a, b) {
58
+ if (a.major !== b.major)
59
+ return a.major - b.major;
60
+ if (a.minor !== b.minor)
61
+ return a.minor - b.minor;
62
+ if (a.patch !== b.patch)
63
+ return a.patch - b.patch;
64
+ if (a.prerelease === b.prerelease)
65
+ return 0;
66
+ if (a.prerelease === undefined)
67
+ return 1;
68
+ if (b.prerelease === undefined)
69
+ return -1;
70
+ return comparePrerelease(a.prerelease, b.prerelease);
71
+ }
72
+ /**
73
+ * Compares two prerelease strings identifier by identifier, as semver
74
+ * specifies: numeric identifiers compare numerically and rank below
75
+ * alphanumeric ones, and a longer identifier list ranks higher when the
76
+ * shared prefix is equal. A plain string comparison put `alpha.10`
77
+ * before `alpha.9`.
78
+ */
79
+ function comparePrerelease(a, b) {
80
+ const left = a.split(".");
81
+ const right = b.split(".");
82
+ const length = Math.min(left.length, right.length);
83
+ for (let index = 0; index < length; index += 1) {
84
+ const x = left[index];
85
+ const y = right[index];
86
+ if (x === y)
87
+ continue;
88
+ const xNumeric = /^\d+$/.test(x);
89
+ const yNumeric = /^\d+$/.test(y);
90
+ if (xNumeric && yNumeric)
91
+ return Number(x) - Number(y);
92
+ if (xNumeric)
93
+ return -1;
94
+ if (yNumeric)
95
+ return 1;
96
+ return x < y ? -1 : 1;
97
+ }
98
+ return left.length - right.length;
99
+ }
100
+ /**
101
+ * Tests a version against a range.
102
+ *
103
+ * Supports the range forms a plugin manifest realistically uses:
104
+ * exact (`1.2.3`), caret (`^1.2.3`), tilde (`~1.2.3`), comparators
105
+ * (`>=1.2.3`, `>`, `<=`, `<`), and `*` for any version. An
106
+ * unrecognisable range is reported by the caller rather than silently
107
+ * passing.
108
+ */
109
+ export function satisfiesVersion(version, range) {
110
+ const trimmedRange = range.trim();
111
+ if (trimmedRange === "*" || trimmedRange === "" || trimmedRange === "x") {
112
+ return true;
113
+ }
114
+ const actual = parseVersion(version);
115
+ if (!actual) {
116
+ return undefined;
117
+ }
118
+ const match = RANGE_PATTERN.exec(trimmedRange);
119
+ if (!match) {
120
+ return undefined;
121
+ }
122
+ const operator = match[1] ?? "=";
123
+ const expected = parseVersion(match[2] ?? "");
124
+ if (!expected) {
125
+ return undefined;
126
+ }
127
+ const comparison = compareVersions(actual, expected);
128
+ switch (operator) {
129
+ case "=":
130
+ return comparison === 0;
131
+ case ">":
132
+ return comparison > 0;
133
+ case ">=":
134
+ return comparison >= 0;
135
+ case "<":
136
+ return comparison < 0;
137
+ case "<=":
138
+ return comparison <= 0;
139
+ case "^": {
140
+ if (comparison < 0)
141
+ return false;
142
+ // Caret allows changes that do not modify the left-most non-zero
143
+ // element, matching npm's semantics for 0.x versions.
144
+ if (expected.major > 0)
145
+ return actual.major === expected.major;
146
+ if (expected.minor > 0)
147
+ return actual.major === 0 && actual.minor === expected.minor;
148
+ return (actual.major === 0 &&
149
+ actual.minor === 0 &&
150
+ actual.patch === expected.patch);
151
+ }
152
+ case "~": {
153
+ if (comparison < 0)
154
+ return false;
155
+ return actual.major === expected.major && actual.minor === expected.minor;
156
+ }
157
+ default:
158
+ return undefined;
159
+ }
160
+ }
161
+ /**
162
+ * Verifies every declared dependency version constraint.
163
+ *
164
+ * A dependency that declares no `version` is unconstrained. A plugin
165
+ * whose own `metadata.version` is missing cannot be checked, so a
166
+ * constraint against it is reported rather than quietly passing.
167
+ *
168
+ * @throws {PluginDependencyError} on the first unsatisfied constraint.
169
+ */
170
+ export function assertDependencyVersions(plugins) {
171
+ for (const [name, plugin] of plugins) {
172
+ const declared = [
173
+ ...(plugin.dependencies ?? []),
174
+ ...(plugin.optionalDependencies ?? []),
175
+ ];
176
+ for (const dependency of declared) {
177
+ if (dependency.version === undefined) {
178
+ continue;
179
+ }
180
+ const target = plugins.get(dependency.name);
181
+ if (!target) {
182
+ // Absent optional dependencies are not a version problem.
183
+ continue;
184
+ }
185
+ const actual = target.metadata.version;
186
+ if (actual === undefined) {
187
+ throw new PluginDependencyVersionError(name, dependency.name, dependency.version, undefined, `Plugin "${name}" requires "${dependency.name}@${dependency.version}", but "${dependency.name}" declares no version. Add a "version" to that plugin's metadata, or drop the constraint from "${name}".`);
188
+ }
189
+ const satisfied = satisfiesVersion(actual, dependency.version);
190
+ if (satisfied === undefined) {
191
+ throw new PluginDependencyVersionError(name, dependency.name, dependency.version, actual, `Plugin "${name}" declares an unsupported version range "${dependency.version}" for "${dependency.name}". Supported forms are an exact version (1.2.3), a caret or tilde range (^1.2.3, ~1.2.3), a comparator (>=1.2.3, >, <=, <) or "*".`);
192
+ }
193
+ if (!satisfied) {
194
+ throw new PluginDependencyVersionError(name, dependency.name, dependency.version, actual, `Plugin "${name}" requires "${dependency.name}@${dependency.version}", but version ${actual} is registered. Register a "${dependency.name}" that satisfies ${dependency.version}, relax the constraint on "${name}", or construct the manager with { checkVersions: false }.`);
195
+ }
196
+ }
197
+ }
198
+ }
199
+ //# sourceMappingURL=versionCheck.core.js.map
@@ -18,6 +18,8 @@ export interface PluginHealth {
18
18
  export interface PluginDiagnostic {
19
19
  readonly plugin: PluginMetadata;
20
20
  readonly state: PluginState;
21
+ /** Whether the plugin failed at any point in its lifecycle. */
22
+ readonly failed: boolean;
21
23
  readonly health: PluginHealth;
22
24
  readonly dependencies: readonly string[];
23
25
  readonly optionalDependencies: readonly string[];
@@ -51,5 +53,12 @@ export declare function createUnhealthyHealth(details?: unknown): PluginHealth;
51
53
  export declare function buildDiagnosticReport(plugins: Array<{
52
54
  readonly plugin: Plugin;
53
55
  readonly state: PluginState;
56
+ /**
57
+ * Whether the plugin failed at any point. A plugin that failed and
58
+ * was then disposed during rollback is still a failure worth
59
+ * reporting, which its current state alone would not show.
60
+ */
61
+ readonly failed?: boolean;
62
+ readonly error?: unknown;
54
63
  }>): PluginDiagnosticReport;
55
64
  //# sourceMappingURL=pluginDiagnostic.core.d.ts.map
@@ -20,21 +20,25 @@ export function createUnhealthyHealth(details) {
20
20
  * Builds a diagnostic report from registered plugins.
21
21
  */
22
22
  export function buildDiagnosticReport(plugins) {
23
- const pluginDiagnostics = plugins.map(({ plugin, state }) => ({
24
- plugin: plugin.metadata,
25
- state,
26
- health: state === "started"
27
- ? createHealthyHealth()
28
- : state === "failed"
29
- ? createUnhealthyHealth()
30
- : createDegradedHealth(),
31
- dependencies: plugin.dependencies?.map((d) => d.name) ?? [],
32
- optionalDependencies: plugin.optionalDependencies?.map((d) => d.name) ?? [],
33
- }));
23
+ const pluginDiagnostics = plugins.map(({ plugin, state, failed, error }) => {
24
+ const hasFailed = failed === true || state === "failed";
25
+ return {
26
+ plugin: plugin.metadata,
27
+ state,
28
+ failed: hasFailed,
29
+ health: hasFailed
30
+ ? createUnhealthyHealth(error instanceof Error ? error.message : error)
31
+ : state === "started"
32
+ ? createHealthyHealth()
33
+ : createDegradedHealth(),
34
+ dependencies: plugin.dependencies?.map((d) => d.name) ?? [],
35
+ optionalDependencies: plugin.optionalDependencies?.map((d) => d.name) ?? [],
36
+ };
37
+ });
34
38
  const healthy = pluginDiagnostics.filter((d) => d.health.status === "healthy").length;
35
39
  const degraded = pluginDiagnostics.filter((d) => d.health.status === "degraded").length;
36
40
  const unhealthy = pluginDiagnostics.filter((d) => d.health.status === "unhealthy").length;
37
- const failed = pluginDiagnostics.filter((d) => d.state === "failed").length;
41
+ const failed = pluginDiagnostics.filter((d) => d.failed).length;
38
42
  return {
39
43
  plugins: Object.freeze(pluginDiagnostics),
40
44
  total: pluginDiagnostics.length,
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Plugin context factory and integration helpers.
5
5
  */
6
- export { createPluginContext } from "./pluginContext.core.js";
6
+ export type { CreatePluginContextOptions, OwnedPluginContext, } from "./pluginContext.core.js";
7
+ export { createPluginContext, createOwnedPluginContext, } from "./pluginContext.core.js";
7
8
  export type { PluginContext } from "../pluginTypes/pluginContext.type.js";
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -3,5 +3,5 @@
3
3
  *
4
4
  * Plugin context factory and integration helpers.
5
5
  */
6
- export { createPluginContext } from "./pluginContext.core.js";
6
+ export { createPluginContext, createOwnedPluginContext, } from "./pluginContext.core.js";
7
7
  //# sourceMappingURL=index.js.map
@@ -1,9 +1,5 @@
1
1
  import type { PluginMetadata } from "../pluginTypes/pluginMetadata.type.js";
2
- import type { PluginContext } from "../pluginTypes/pluginContext.type.js";
3
- import type { PluginContainer } from "../pluginTypes/pluginContext.type.js";
4
- import type { PluginConfig } from "../pluginTypes/pluginContext.type.js";
5
- import type { PluginLogger } from "../pluginTypes/pluginContext.type.js";
6
- import type { PluginEvents } from "../pluginTypes/pluginContext.type.js";
2
+ import type { PluginContext, PluginContainer, PluginConfig, PluginLogger, PluginEvents, PluginDisposable } from "../pluginTypes/pluginContext.type.js";
7
3
  /**
8
4
  * Options for creating a plugin context.
9
5
  */
@@ -12,9 +8,43 @@ export interface CreatePluginContextOptions {
12
8
  readonly config?: PluginConfig;
13
9
  readonly logger?: PluginLogger;
14
10
  readonly events?: PluginEvents;
11
+ /**
12
+ * Collection that receives everything the plugin registers for
13
+ * cleanup. The plugin manager passes the registered plugin's own
14
+ * array, which is what the lifecycle disposes; without it, anything
15
+ * registered here would be collected and then never run.
16
+ */
17
+ readonly disposables?: PluginDisposable[];
18
+ /**
19
+ * Controller whose signal the plugin observes. The manager supplies
20
+ * one it aborts on shutdown so plugins are told to stop.
21
+ */
22
+ readonly abortController?: AbortController;
15
23
  }
24
+ /**
25
+ * A plugin context together with the handles needed to tear it down.
26
+ */
27
+ export interface OwnedPluginContext {
28
+ readonly context: PluginContext;
29
+ /** Everything the plugin registered for cleanup, in registration order. */
30
+ readonly disposables: PluginDisposable[];
31
+ /** Aborts `context.signal`. */
32
+ readonly abort: (reason?: unknown) => void;
33
+ }
34
+ /**
35
+ * Creates a plugin context and returns it with its teardown handles.
36
+ *
37
+ * The disposables array and the abort function are the two capabilities
38
+ * a context cannot expose to its own consumer but its owner needs, so
39
+ * they are returned alongside rather than trapped in the closure.
40
+ */
41
+ export declare function createOwnedPluginContext(plugin: PluginMetadata, options?: CreatePluginContextOptions): OwnedPluginContext;
16
42
  /**
17
43
  * Creates a plugin context for testing and basic usage.
44
+ *
45
+ * Pass `options.disposables` to reach what the plugin registers;
46
+ * otherwise use {@link createOwnedPluginContext}, which returns the
47
+ * collection and the abort handle with the context.
18
48
  */
19
49
  export declare function createPluginContext(plugin: PluginMetadata, options?: CreatePluginContextOptions): PluginContext;
20
50
  //# sourceMappingURL=pluginContext.core.d.ts.map
@@ -1,10 +1,14 @@
1
1
  /**
2
- * Creates a plugin context for testing and basic usage.
2
+ * Creates a plugin context and returns it with its teardown handles.
3
+ *
4
+ * The disposables array and the abort function are the two capabilities
5
+ * a context cannot expose to its own consumer but its owner needs, so
6
+ * they are returned alongside rather than trapped in the closure.
3
7
  */
4
- export function createPluginContext(plugin, options = {}) {
5
- const abortController = new AbortController();
6
- const disposables = [];
7
- return {
8
+ export function createOwnedPluginContext(plugin, options = {}) {
9
+ const abortController = options.abortController ?? new AbortController();
10
+ const disposables = options.disposables ?? [];
11
+ const context = {
8
12
  plugin,
9
13
  signal: abortController.signal,
10
14
  container: options.container,
@@ -18,5 +22,24 @@ export function createPluginContext(plugin, options = {}) {
18
22
  disposables.push(disposable);
19
23
  },
20
24
  };
25
+ return {
26
+ context,
27
+ disposables,
28
+ abort: (reason) => {
29
+ if (!abortController.signal.aborted) {
30
+ abortController.abort(reason);
31
+ }
32
+ },
33
+ };
34
+ }
35
+ /**
36
+ * Creates a plugin context for testing and basic usage.
37
+ *
38
+ * Pass `options.disposables` to reach what the plugin registers;
39
+ * otherwise use {@link createOwnedPluginContext}, which returns the
40
+ * collection and the abort handle with the context.
41
+ */
42
+ export function createPluginContext(plugin, options = {}) {
43
+ return createOwnedPluginContext(plugin, options).context;
21
44
  }
22
45
  //# sourceMappingURL=pluginContext.core.js.map
@@ -1,15 +1,57 @@
1
1
  import type { Plugin } from "../pluginTypes/plugin.type.js";
2
2
  import type { PluginContext } from "../pluginTypes/pluginContext.type.js";
3
3
  import type { RegisteredPlugin } from "../pluginRegistry/pluginRegistry.core.js";
4
+ /**
5
+ * Options controlling lifecycle execution.
6
+ */
7
+ export interface LifecycleControllerOptions {
8
+ /**
9
+ * Maximum time a single lifecycle hook may run, in milliseconds.
10
+ *
11
+ * Defaults to `0` (unbounded), preserving existing behaviour. Set a
12
+ * value to bound boot and shutdown: without one, a plugin whose
13
+ * `start()` never settles hangs the whole application with no
14
+ * diagnostic.
15
+ */
16
+ readonly hookTimeout?: number;
17
+ }
4
18
  /**
5
19
  * Executes plugin lifecycle phases with state management and event emission.
6
20
  */
7
21
  export declare class LifecycleController {
22
+ private readonly options;
23
+ constructor(options?: LifecycleControllerOptions);
24
+ /**
25
+ * Runs a hook under the configured timeout, clearing the timer either
26
+ * way so a completed hook never leaves one armed.
27
+ */
28
+ private runHook;
8
29
  install<TPlugin extends Plugin>(registered: RegisteredPlugin<TPlugin>, context: PluginContext): Promise<void>;
9
30
  initialize<TPlugin extends Plugin>(registered: RegisteredPlugin<TPlugin>, context: PluginContext): Promise<void>;
10
31
  start<TPlugin extends Plugin>(registered: RegisteredPlugin<TPlugin>, context: PluginContext): Promise<void>;
11
32
  stop<TPlugin extends Plugin>(registered: RegisteredPlugin<TPlugin>, context: PluginContext): Promise<void>;
33
+ /**
34
+ * Runs one lifecycle phase, moving through its transient state.
35
+ *
36
+ * On failure the plugin moves to `failed` through the state machine
37
+ * rather than around it, so the recorded state is always one the
38
+ * machine actually permits.
39
+ */
40
+ private runPhase;
41
+ /**
42
+ * Disposes a plugin and everything it registered for cleanup.
43
+ *
44
+ * Disposables run in reverse registration order — the mirror of how
45
+ * they were acquired — and the list is emptied so a second dispose
46
+ * cannot run them again. Every failure is collected; the plugin still
47
+ * reaches a terminal state so it cannot be disposed twice.
48
+ */
12
49
  dispose<TPlugin extends Plugin>(registered: RegisteredPlugin<TPlugin>, context: PluginContext): Promise<void>;
50
+ /**
51
+ * Moves a plugin to `failed`, via `stopping` when required.
52
+ */
53
+ private transitionToFailed;
54
+ private canTransition;
13
55
  private ensureTransition;
14
56
  }
15
57
  //# sourceMappingURL=pluginLifecycle.core.d.ts.map