@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 +21 -0
- package/README.md +213 -2
- package/dist/hooks.d.ts +76 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +155 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +429 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +12 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +78 -0
- package/dist/loader.js.map +1 -0
- package/dist/types.d.ts +135 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +49 -0
- package/dist/types.js.map +1 -0
- package/package.json +65 -4
- package/src/hooks.ts +253 -0
- package/src/index.ts +568 -0
- package/src/loader.ts +83 -0
- package/src/types.ts +196 -0
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
|
-
#
|
|
1
|
+
# plugin-sdk
|
|
2
2
|
|
|
3
|
-
|
|
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
|
package/dist/hooks.d.ts
ADDED
|
@@ -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":"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"}
|
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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|