@esauflores/plugin-sdk 0.0.0-stage → 0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cesar Flores
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,214 @@
1
- # Temporary Holding Version
1
+ # plugin-sdk
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A plugin system for **Bun and Node (24+)**. Its one dependency is `@standard-schema/spec` (types only). Plugins are npm packages (or local objects),
4
+ trusted and run in-process. To use it, copy `src/` into your project (e.g. as `plugin-sdk/`).
5
+
6
+ ```
7
+ src/
8
+ index.ts PluginHost, definePlugin, ordering + timeouts; re-exports types and hooks
9
+ hooks.ts HookBus: call / parallel / waterfall / bail
10
+ loader.ts finds, checks and imports plugin packages (the only file using node:fs)
11
+ types.ts the public contract: Plugin, PluginContext, options, errors, schema checks
12
+ ```
13
+
14
+ `index.ts` imports `loader.ts` lazily inside `host.load()`. If you only use `host.register()`, the core never touches
15
+ Node APIs, so it can run in a browser, a Worker or an edge runtime (after a TypeScript build; only Bun and Node are tested).
16
+
17
+ The code only uses TypeScript syntax that can be erased (no enums, no parameter properties), and imports use `.ts`
18
+ extensions. Bun runs it as is, and so does Node 23.6+ (type stripping). For a project with a build step, keep
19
+ `allowImportingTsExtensions` or rewrite the imports to `.js`.
20
+
21
+ ## Lifecycle
22
+
23
+ ```
24
+ register / load ─► setup (all) ─► start (all) ─► stop (all, reversed) ─► dispose
25
+ │ │ │
26
+ └── failed / disabled ◄────────┘ (torn down, dependents disabled)
27
+ ```
28
+
29
+ - **Order**: plugins are flat (no nesting) and sorted by `dependsOn`; ties keep registration order.
30
+ - **setup**: register hooks and `provide` services. Nothing with side effects goes here.
31
+ - **start**: open sockets, start timers. Every plugin has finished setup by this point.
32
+ - **stop**: `ctx.signal` aborts first, then `stop()` runs, then the `onDispose` callbacks run in reverse order. Hooks and services are removed automatically.
33
+ - **Failure** (`failure: "isolate"`, the default): the plugin is torn down and its dependents are disabled; everything else keeps running.
34
+ With `"strict"`, the first failure tears everything down and `start()` rejects.
35
+ - **Hook errors at runtime** follow `hookErrors` (see below), so one bad handler doesn't break a hook for everyone.
36
+ - Each phase has a timeout (`timeoutMs`, 10s by default). A timeout aborts the plugin's signal.
37
+ - **A host is single-use.** ES modules can't be unloaded, so to reload plugins, create a new host or restart the process.
38
+
39
+ ## The app (host)
40
+
41
+ ```ts
42
+ import { PluginHost, definePluginFor } from "./plugin-sdk/index.ts";
43
+
44
+ export interface AppHooks {
45
+ request: (req: Request) => Response | void;
46
+ }
47
+ export interface AppServices {
48
+ db: Database;
49
+ }
50
+ export const definePlugin = definePluginFor<AppHooks, AppServices>();
51
+
52
+ const host = new PluginHost<AppHooks, AppServices>({
53
+ app: { name: "my-app", version: "2.1.0" }, // name = manifest key in plugin package.json
54
+ apiVersion: 1, // bump on breaking plugin-API changes
55
+ });
56
+
57
+ host.register(localPlugin);
58
+ await host.load(["my-app-plugin-auth", ["@acme/my-app-metrics", { interval: 10 }]]); // from node_modules
59
+ await host.start();
60
+
61
+ const res = await host.hooks.bail("request", req);
62
+
63
+ process.once("SIGTERM", () => host.stop());
64
+ ```
65
+
66
+ `host.list()` / `host.get(name)` / `host.onStateChange()` report each plugin's state and, when one failed or was disabled, why.
67
+
68
+ ## Writing a plugin
69
+
70
+ ```ts
71
+ export default definePlugin<{ prefix: string }>({
72
+ name: "auth",
73
+ dependsOn: ["db"], // ordering + permission to use("db")
74
+ setup(ctx) {
75
+ const db = ctx.use("db");
76
+ ctx.provide("auth", createAuth(db));
77
+ ctx.hooks.on("request", (req) => (req.url.startsWith(ctx.options.prefix) ? check(req) : undefined));
78
+ },
79
+ async start(ctx) {
80
+ const timer = setInterval(sweepSessions, 60_000);
81
+ ctx.onDispose(() => clearInterval(timer));
82
+ },
83
+ });
84
+ ```
85
+
86
+ | Hook style | Behaviour |
87
+ | ----------- | ------------------------------------------------------------------------------------------------------------- |
88
+ | `call` | handlers run one after another; results ignored |
89
+ | `parallel` | handlers run concurrently |
90
+ | `waterfall` | each handler gets the previous handler's result as its first argument (returning `undefined` keeps the value) |
91
+ | `bail` | first result that isn't `undefined` wins; the remaining handlers are skipped |
92
+
93
+ ### Hook order
94
+
95
+ Handlers run in three buckets: `"pre"`, then `"normal"` (the default), then `"post"`, in registration order within
96
+ each. Use it when two plugins that don't depend on each other must agree on an order in a `waterfall` hook:
97
+
98
+ ```ts
99
+ ctx.hooks.on("render", addBanner, { order: "post" }); // after minify, wherever minify registers
100
+ ```
101
+
102
+ Order only places handlers; it doesn't create a dependency. Use `dependsOn` when you need the other plugin's services.
103
+
104
+ ### Interceptors (host only)
105
+
106
+ `host.hooks.intercept()` observes every hook call, for tracing, metrics or finding slow plugins. Plugins can't
107
+ use it: their `ctx.hooks` has no `intercept`.
108
+
109
+ ```ts
110
+ const remove = host.hooks.intercept({
111
+ call: ({ hook, style, args }) => {}, // before any handler runs
112
+ handler: ({ hook, plugin, ms, error }) => {
113
+ // after each handler
114
+ if (ms > 50) log.warn(`${plugin} took ${ms.toFixed(0)}ms in ${hook}`);
115
+ },
116
+ });
117
+ ```
118
+
119
+ - `plugin` is `undefined` for your app's own handlers, and `error` is set when the handler threw or returned an
120
+ invalid value (even when `hookErrors` then skipped it).
121
+ - `ms` covers the handler and its result check, not what `hookErrors` does afterwards.
122
+ - Interceptors are your code: if one throws, the hook call rejects.
123
+
124
+ ## Checking data at runtime (Standard Schema)
125
+
126
+ TypeScript types disappear at runtime, and plugin code from npm is JavaScript you don't control. The SDK accepts
127
+ any [Standard Schema](https://standardschema.dev) (zod, valibot, ArkType…) at the two places where data crosses
128
+ from a plugin into your app:
129
+
130
+ ```ts
131
+ import { z } from "zod";
132
+
133
+ // 1. Plugin options: checked before setup(); ctx.options is the parsed value, typed from the schema.
134
+ const server = definePlugin({
135
+ name: "server",
136
+ optionsSchema: z.object({ port: z.number().int(), host: z.string().default("localhost") }),
137
+ setup(ctx) {
138
+ ctx.options.port; // number
139
+ },
140
+ });
141
+ host.register(server, { port: 8080 }); // typed as the schema's input: `host` is optional
142
+
143
+ // 2. Hook results: every value a plugin returns from waterfall/bail is checked.
144
+ const host = new PluginHost<AppHooks>({
145
+ app,
146
+ hookSchemas: { render: z.string().max(10_000) },
147
+ });
148
+ ```
149
+
150
+ - **Invalid options** mark the plugin `failed` with a reason like `invalid options: port: Invalid input…`, and
151
+ its dependents are disabled. Options passed to `host.load()` are checked the same way.
152
+ - **An invalid hook result** counts as a hook error from the plugin that returned it, handled by `hookErrors`.
153
+ If the schema transforms the value, the next handler (and your app) gets the transformed value.
154
+ - Hooks without a schema, and `call`/`parallel` (whose results are ignored), aren't checked.
155
+ - To give third-party plugin authors your types, publish the schemas in a small package (e.g. `my-app-plugin-api`):
156
+ authors get types through `z.infer`, and the same schemas run at runtime.
157
+
158
+ ## Hook errors at runtime
159
+
160
+ After startup, a plugin's hook handler can still throw, or return a value its hook schema rejects. The
161
+ `hookErrors` host option decides what happens:
162
+
163
+ | `hookErrors` | The failing handler | The plugin | The hook call |
164
+ | ----------------- | ------------------- | ------------------------------------------------------------- | ---------------------------------------------- |
165
+ | `"log"` (default) | logged and skipped | keeps running | continues with the other handlers |
166
+ | `"disable"` | logged and skipped | stopped as `failed`, and its started dependents as `disabled` | continues |
167
+ | `"throw"` | — | keeps running | rejects with a `PluginError` naming the plugin |
168
+
169
+ - When a handler is skipped, `waterfall` keeps the value from before it, and `bail` moves on to the next handler.
170
+ - `"disable"` stops plugins the same way `host.stop()` does: signal aborted, `stop()`, disposers, hooks and
171
+ services removed. Dependents through `optionalDependsOn` keep running.
172
+ - In a nested hook call, the plugin whose handler actually failed is the one blamed.
173
+ - Errors from your app's own handlers (`host.hooks.on(...)`) always propagate: that's your code, not a plugin's.
174
+
175
+ ## Publishing a plugin package
176
+
177
+ ```json
178
+ {
179
+ "name": "my-app-plugin-auth",
180
+ "type": "module",
181
+ "keywords": ["my-app-plugin"],
182
+ "peerDependencies": { "my-app": "^2.0.0" },
183
+ "my-app": { "apiVersion": 1, "entry": "./dist/index.js" }
184
+ }
185
+ ```
186
+
187
+ - **Ship JavaScript.** Node won't strip types inside `node_modules`.
188
+ - **`apiVersion` is the compatibility check.** It must equal the host's `apiVersion`; bump it in the host when the plugin API breaks. `peerDependencies` ranges aren't checked.
189
+ - **Put the host in `peerDependencies`, not `dependencies`,** so the plugin doesn't install its own copy. The SDK checks plugins by shape rather than with `instanceof`, so a second copy isn't fatal, but it's wasteful.
190
+ - **`entry` is required.** It's the file the loader imports, relative to the package root.
191
+ - The loader looks for packages in `node_modules` in `cwd` and then in each parent directory. Relative paths (`"./plugins/foo"`) also work.
192
+ - Install scripts: npm runs `postinstall`; Bun doesn't unless the package is listed in `trustedDependencies`. Users install plugins at their own risk.
193
+
194
+ ## Development
195
+
196
+ Tooling follows the `node-bun-base` template: Vite+ (`vp`) for format, lint and tests, TypeScript 7, and mise for
197
+ tool versions. The tests import `@/…`, which maps to `src/`; the SDK itself only uses relative imports, so `src/`
198
+ stays copyable.
199
+
200
+ ```sh
201
+ mise install # Node 24 + Bun
202
+ bun run check # vp fmt --check, vp lint (type-aware), tsc
203
+ bun run check:fix # same, applying fixes
204
+ bun run test # vp test (Vitest on Node)
205
+ bun run test:bun # the same tests on Bun
206
+ bun run example # examples/basic on Bun and Node
207
+ ```
208
+
209
+ CI (`.github/workflows/ci.yml`) runs the same steps on every push to `main` and on pull requests, with the
210
+ Node and Bun versions from `mise.toml`.
211
+
212
+ ## License
213
+
214
+ MIT © 2026 Cesar Flores
@@ -0,0 +1,76 @@
1
+ import { PluginError, type StandardSchemaV1 } from "./types.ts";
2
+ export type HookFn = (...args: any[]) => unknown;
3
+ export type HookName<H> = keyof H & string;
4
+ type Fn<H, K extends keyof H> = H[K] extends HookFn ? H[K] : never;
5
+ type Args<H, K extends keyof H> = H[K] extends (...args: infer A) => any ? A : never[];
6
+ type Result<H, K extends keyof H> = H[K] extends (...args: any[]) => infer R ? Awaited<R> : never;
7
+ /** Per hook, a schema for the values handlers return (checked by `waterfall` and `bail`). */
8
+ export type HookSchemas<H> = {
9
+ [K in HookName<H>]?: StandardSchemaV1<unknown, Exclude<Result<H, K>, undefined | void>>;
10
+ };
11
+ /** "pre" handlers run first, then "normal" (the default), then "post"; registration order within each. */
12
+ export type HookOrder = "pre" | "normal" | "post";
13
+ export interface HookOptions {
14
+ order?: HookOrder;
15
+ }
16
+ export type HookStyle = "call" | "parallel" | "waterfall" | "bail";
17
+ /** Observes every hook call on the host's bus: for tracing, metrics, finding slow plugins. */
18
+ export interface HookInterceptor {
19
+ /** A hook was called, before any handler runs. */
20
+ call?(event: {
21
+ hook: string;
22
+ style: HookStyle;
23
+ args: readonly unknown[];
24
+ }): void;
25
+ /**
26
+ * One handler finished. `plugin` is undefined for the app's own handlers; `ms` excludes
27
+ * error handling; `error` is the PluginError if it threw or returned an invalid value.
28
+ */
29
+ handler?(event: {
30
+ hook: string;
31
+ plugin: string | undefined;
32
+ ms: number;
33
+ error?: unknown;
34
+ }): void;
35
+ }
36
+ export interface Hooks<H extends object> {
37
+ /** Returns a function that removes the handler. */
38
+ on<K extends HookName<H>>(name: K, fn: Fn<H, K>, options?: HookOptions): () => void;
39
+ /** Runs handlers one after another, in order. */
40
+ call<K extends HookName<H>>(name: K, ...args: Args<H, K>): Promise<void>;
41
+ /** Runs all handlers concurrently. */
42
+ parallel<K extends HookName<H>>(name: K, ...args: Args<H, K>): Promise<void>;
43
+ /**
44
+ * Each handler receives the previous one's result as its first argument; returning undefined keeps the value.
45
+ * Results are checked against the hook's schema, if it has one.
46
+ */
47
+ waterfall<K extends HookName<H>>(name: K, ...args: Args<H, K>): Promise<Args<H, K>[0]>;
48
+ /** Returns the first result that is not undefined, skipping the remaining handlers. Checked like `waterfall`. */
49
+ bail<K extends HookName<H>>(name: K, ...args: Args<H, K>): Promise<Exclude<Result<H, K>, undefined | void> | undefined>;
50
+ count(name: HookName<H>): number;
51
+ }
52
+ /**
53
+ * Called when a plugin's handler throws or returns an invalid value. Rethrow to make the hook
54
+ * call reject; return to skip that handler and continue. App handlers (no owner) always rethrow.
55
+ */
56
+ export type HookErrorHandler = (error: PluginError) => void | Promise<void>;
57
+ export declare class HookBus<H extends object = any> implements Hooks<H> {
58
+ #private;
59
+ constructor(schemas?: HookSchemas<H>, onError?: HookErrorHandler);
60
+ on<K extends HookName<H>>(name: K, fn: Fn<H, K>, options?: HookOptions): () => void;
61
+ /**
62
+ * Adds an observer of every hook call; returns a function that removes it. Host-only: the
63
+ * scoped view plugins get has no `intercept`. Errors thrown by an interceptor propagate.
64
+ */
65
+ intercept(interceptor: HookInterceptor): () => void;
66
+ /** A view whose `on` tags handlers with `owner`, so `removeOwner` can drop them all. */
67
+ scope(owner: string): Hooks<H>;
68
+ removeOwner(owner: string): void;
69
+ count(name: HookName<H>): number;
70
+ call<K extends HookName<H>>(name: K, ...args: Args<H, K>): Promise<void>;
71
+ parallel<K extends HookName<H>>(name: K, ...args: Args<H, K>): Promise<void>;
72
+ waterfall<K extends HookName<H>>(name: K, ...args: Args<H, K>): Promise<Args<H, K>[0]>;
73
+ bail<K extends HookName<H>>(name: K, ...args: Args<H, K>): Promise<Exclude<Result<H, K>, undefined | void> | undefined>;
74
+ }
75
+ export {};
76
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE3E,MAAM,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC;AACjD,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM,CAAC;AAC3C,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;AACnE,KAAK,IAAI,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,KAAK,EAAE,CAAC;AACvF,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;AAElG,6FAA6F;AAC7F,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;KAC1B,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC,CAAC;CACxF,CAAC;AAEF,0GAA0G;AAC1G,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAElD,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,MAAM,CAAC;AAEnE,8FAA8F;AAC9F,MAAM,WAAW,eAAe;IAC9B,kDAAkD;IAClD,IAAI,CAAC,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,SAAS,CAAC;QAAC,IAAI,EAAE,SAAS,OAAO,EAAE,CAAA;KAAE,GAAG,IAAI,CAAC;IACjF;;;OAGG;IACH,OAAO,CAAC,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CAClG;AAED,MAAM,WAAW,KAAK,CAAC,CAAC,SAAS,MAAM;IACrC,mDAAmD;IACnD,EAAE,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,MAAM,IAAI,CAAC;IACpF,iDAAiD;IACjD,IAAI,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzE,sCAAsC;IACtC,QAAQ,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7E;;;OAGG;IACH,SAAS,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvF,iHAAiH;IACjH,IAAI,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EACxB,IAAI,EAAE,CAAC,EACP,GAAG,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAClB,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC,GAAG,SAAS,CAAC,CAAC;IAChE,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;CAClC;AAUD;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE5E,qBAAa,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,GAAG,CAAE,YAAW,KAAK,CAAC,CAAC,CAAC;;IAM9D,YAAY,OAAO,GAAE,WAAW,CAAC,CAAC,CAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,EAGnE;IAED,EAAE,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,MAAM,IAAI,CAElF;IAED;;;OAGG;IACH,SAAS,CAAC,WAAW,EAAE,eAAe,GAAG,MAAM,IAAI,CAMlD;IAED,wFAAwF;IACxF,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAS7B;IAED,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAO/B;IAED,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,CAE/B;IAEK,IAAI,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7E;IAEK,QAAQ,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAIjF;IAEK,SAAS,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAW3F;IAEK,IAAI,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EAC9B,IAAI,EAAE,CAAC,EACP,GAAG,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAClB,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC,GAAG,SAAS,CAAC,CAS9D;CAgGF"}
package/dist/hooks.js ADDED
@@ -0,0 +1,155 @@
1
+ import { parseWith, PluginError } from "./types.js";
2
+ const RANKS = { pre: 0, normal: 1, post: 2 };
3
+ export class HookBus {
4
+ #handlers = new Map();
5
+ #schemas;
6
+ #onError;
7
+ #interceptors = [];
8
+ constructor(schemas = {}, onError) {
9
+ this.#schemas = schemas;
10
+ this.#onError = onError;
11
+ }
12
+ on(name, fn, options) {
13
+ return this.#on(name, fn, undefined, options);
14
+ }
15
+ /**
16
+ * Adds an observer of every hook call; returns a function that removes it. Host-only: the
17
+ * scoped view plugins get has no `intercept`. Errors thrown by an interceptor propagate.
18
+ */
19
+ intercept(interceptor) {
20
+ this.#interceptors = [...this.#interceptors, interceptor];
21
+ return () => {
22
+ this.#interceptors = this.#interceptors.filter((i) => i !== interceptor);
23
+ };
24
+ }
25
+ /** A view whose `on` tags handlers with `owner`, so `removeOwner` can drop them all. */
26
+ scope(owner) {
27
+ return {
28
+ on: (name, fn, options) => this.#on(name, fn, owner, options),
29
+ call: (name, ...args) => this.call(name, ...args),
30
+ parallel: (name, ...args) => this.parallel(name, ...args),
31
+ waterfall: (name, ...args) => this.waterfall(name, ...args),
32
+ bail: (name, ...args) => this.bail(name, ...args),
33
+ count: (name) => this.count(name),
34
+ };
35
+ }
36
+ removeOwner(owner) {
37
+ for (const [name, list] of this.#handlers) {
38
+ this.#handlers.set(name, list.filter((h) => h.owner !== owner));
39
+ }
40
+ }
41
+ count(name) {
42
+ return this.#handlers.get(name)?.length ?? 0;
43
+ }
44
+ async call(name, ...args) {
45
+ this.#notifyCall(name, "call", args);
46
+ for (const h of this.#snapshot(name))
47
+ await this.#handle(h, name, args, false);
48
+ }
49
+ async parallel(name, ...args) {
50
+ this.#notifyCall(name, "parallel", args);
51
+ await Promise.all(this.#snapshot(name).map((h) => this.#handle(h, name, args, false)));
52
+ }
53
+ async waterfall(name, ...args) {
54
+ this.#notifyCall(name, "waterfall", args);
55
+ let [value, ...rest] = args;
56
+ for (const h of this.#snapshot(name)) {
57
+ const next = await this.#handle(h, name, [value, ...rest], true);
58
+ if (next?.value !== undefined)
59
+ value = next.value;
60
+ }
61
+ return value;
62
+ }
63
+ async bail(name, ...args) {
64
+ this.#notifyCall(name, "bail", args);
65
+ for (const h of this.#snapshot(name)) {
66
+ const result = await this.#handle(h, name, args, true);
67
+ if (result?.value !== undefined)
68
+ return result.value;
69
+ }
70
+ return undefined;
71
+ }
72
+ #on(name, fn, owner, options = {}) {
73
+ if (typeof fn !== "function")
74
+ throw new TypeError(`Hook handler for "${name}" must be a function`);
75
+ const rank = RANKS[options.order ?? "normal"];
76
+ if (rank === undefined)
77
+ throw new TypeError(`Hook order must be "pre", "normal" or "post", got "${options.order}"`);
78
+ // Insert after the last handler of the same or an earlier bucket.
79
+ const handler = { fn, owner, rank };
80
+ const list = this.#handlers.get(name) ?? [];
81
+ const at = list.findIndex((h) => h.rank > rank);
82
+ this.#handlers.set(name, at === -1 ? [...list, handler] : [...list.slice(0, at), handler, ...list.slice(at)]);
83
+ return () => {
84
+ const list = this.#handlers.get(name);
85
+ if (list)
86
+ this.#handlers.set(name, list.filter((h) => h !== handler));
87
+ };
88
+ }
89
+ // Lists are replaced, never mutated, so handlers added mid-emit don't run in that emit.
90
+ // Handlers removed mid-emit (e.g. their plugin was disabled) are skipped by #handle.
91
+ #snapshot(name) {
92
+ return this.#handlers.get(name) ?? [];
93
+ }
94
+ /** Runs one handler (and checks its result); `undefined` means it was skipped or its error was handled. */
95
+ async #handle(h, name, args, checked) {
96
+ if (!this.#handlers.get(name)?.includes(h))
97
+ return undefined;
98
+ const startedAt = performance.now();
99
+ let result;
100
+ try {
101
+ const value = await this.#invoke(h, name, args);
102
+ result = { value: checked && value !== undefined ? await this.#check(h, name, value) : value };
103
+ }
104
+ catch (error) {
105
+ this.#notifyHandler(name, h, startedAt, error);
106
+ if (!this.#onError || h.owner === undefined || !(error instanceof PluginError))
107
+ throw error;
108
+ await this.#onError(error);
109
+ return undefined;
110
+ }
111
+ this.#notifyHandler(name, h, startedAt);
112
+ return result;
113
+ }
114
+ #notifyCall(hook, style, args) {
115
+ for (const interceptor of this.#interceptors)
116
+ interceptor.call?.({ hook, style, args });
117
+ }
118
+ #notifyHandler(hook, h, startedAt, error) {
119
+ if (this.#interceptors.length === 0)
120
+ return;
121
+ const event = {
122
+ hook,
123
+ plugin: h.owner,
124
+ ms: performance.now() - startedAt,
125
+ ...(error === undefined ? {} : { error }),
126
+ };
127
+ for (const interceptor of this.#interceptors)
128
+ interceptor.handler?.(event);
129
+ }
130
+ async #invoke(h, name, args) {
131
+ try {
132
+ return await h.fn(...args);
133
+ }
134
+ catch (cause) {
135
+ if (cause instanceof PluginError && cause.phase === "hook")
136
+ throw cause;
137
+ throw new PluginError(h.owner ?? "<host>", "hook", `handler for "${name}" threw`, { cause });
138
+ }
139
+ }
140
+ async #check(h, name, value) {
141
+ const schema = this.#schemas[name];
142
+ if (!schema)
143
+ return value;
144
+ try {
145
+ return await parseWith(schema, value);
146
+ }
147
+ catch (cause) {
148
+ const detail = cause instanceof Error ? cause.message : String(cause);
149
+ throw new PluginError(h.owner ?? "<host>", "hook", `handler for "${name}" returned an invalid value: ${detail}`, {
150
+ cause,
151
+ });
152
+ }
153
+ }
154
+ }
155
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { HookBus } from "./hooks.ts";
2
+ import { type AppInfo, type HostOptions, type HostPhase, type Plugin, type PluginInfo, type PluginSpec, type StateListener } from "./types.ts";
3
+ export * from "./types.ts";
4
+ export { HookBus, type HookErrorHandler, type HookFn, type HookInterceptor, type HookName, type HookOptions, type HookOrder, type Hooks, type HookSchemas, type HookStyle, } from "./hooks.ts";
5
+ /** Identity helper for type inference. Untyped hooks/services; see `definePluginFor` for app-typed plugins. */
6
+ export declare function definePlugin<Options = void, Input = Options>(plugin: Plugin<Options, any, any, Input>): Plugin<Options, any, any, Input>;
7
+ /**
8
+ * Returns a `definePlugin` bound to your app's hook and service maps:
9
+ * export const definePlugin = definePluginFor<MyHooks, MyServices>();
10
+ */
11
+ export declare function definePluginFor<H extends object, S extends object = {}>(): <Options = void, Input = Options>(plugin: Plugin<Options, H, S, Input>) => Plugin<Options, H, S, Input>;
12
+ /**
13
+ * The host is single-use: once stopped it can't be started again. To reload plugins,
14
+ * create a new host (or restart the process; ES modules can't be unloaded).
15
+ */
16
+ export declare class PluginHost<H extends object = any, S extends object = any> {
17
+ #private;
18
+ readonly app: AppInfo;
19
+ /** Host-owned view of the hook bus: emit hooks from your app through this. */
20
+ readonly hooks: HookBus<H>;
21
+ constructor(options: HostOptions<H>);
22
+ get phase(): HostPhase;
23
+ /** Adds a plugin object directly (local plugins, or ones you imported yourself). */
24
+ register<O, I = O>(plugin: Plugin<O, H, S, I>, options?: I): this;
25
+ /**
26
+ * Loads installed plugin packages, resolving from `cwd`'s node_modules upwards.
27
+ * Imports run in parallel; plugins are registered in the order given.
28
+ * Under "isolate", a package that fails to load is listed as failed and skipped.
29
+ */
30
+ load(specs: readonly PluginSpec[], { cwd }?: {
31
+ cwd?: string;
32
+ }): Promise<this>;
33
+ /** Sets up every plugin, then starts every plugin, both in dependency order. */
34
+ start(): Promise<void>;
35
+ /** Stops started plugins in reverse order and tears everything down. Safe to call repeatedly or mid-start. */
36
+ stop(): Promise<void>;
37
+ /** Services provided by the host are usable by every plugin without declaring a dependency. */
38
+ provide<K extends keyof S & string>(key: K, value: S[K]): void;
39
+ service<K extends keyof S & string>(key: K): S[K] | undefined;
40
+ list(): PluginInfo[];
41
+ get(name: string): PluginInfo | undefined;
42
+ onStateChange(listener: StateListener): () => void;
43
+ }
44
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EACL,KAAK,OAAO,EAGZ,KAAK,WAAW,EAEhB,KAAK,SAAS,EAId,KAAK,MAAM,EAGX,KAAK,UAAU,EACf,KAAK,UAAU,EAEf,KAAK,aAAa,EAEnB,MAAM,YAAY,CAAC;AAEpB,cAAc,YAAY,CAAC;AAC3B,OAAO,EACL,OAAO,EACP,KAAK,gBAAgB,EACrB,KAAK,MAAM,EACX,KAAK,eAAe,EACpB,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,KAAK,EACV,KAAK,WAAW,EAChB,KAAK,SAAS,GACf,MAAM,YAAY,CAAC;AAEpB,+GAA+G;AAC/G,wBAAgB,YAAY,CAAC,OAAO,GAAG,IAAI,EAAE,KAAK,GAAG,OAAO,EAC1D,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,GACvC,MAAM,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,CAElC;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,GAAG,EAAE,MAC7D,OAAO,GAAG,IAAI,EAAE,KAAK,GAAG,OAAO,UAAU,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,KAAG,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAE7G;AAiBD;;;GAGG;AACH,qBAAa,UAAU,CAAC,CAAC,SAAS,MAAM,GAAG,GAAG,EAAE,CAAC,SAAS,MAAM,GAAG,GAAG;;IACpE,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAkB3B,YAAY,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,EASlC;IAED,IAAI,KAAK,IAAI,SAAS,CAErB;IAED,oFAAoF;IACpF,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,IAAI,CAIhE;IAED;;;;OAIG;IACG,IAAI,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,EAAE,EAAE,GAAmB,EAAE,GAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAoCtG;IAED,gFAAgF;IAChF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAOrB;IAED,8GAA8G;IAC9G,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAOpB;IAED,+FAA+F;IAC/F,OAAO,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAE7D;IAED,OAAO,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS,CAE5D;IAED,IAAI,IAAI,UAAU,EAAE,CAEnB;IAED,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAIxC;IAED,aAAa,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,IAAI,CAIjD;CAmSF"}