@usebetterdev/plugin 0.5.0 → 0.6.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/dist/hook-registry.d.ts +68 -0
- package/dist/hook-registry.d.ts.map +1 -0
- package/dist/index.cjs +214 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +199 -0
- package/dist/index.js.map +1 -1
- package/dist/resolve-plugins.d.ts +36 -0
- package/dist/resolve-plugins.d.ts.map +1 -0
- package/dist/schema.d.ts +18 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/validation.d.ts +11 -0
- package/dist/validation.d.ts.map +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { HookContract, HookHandlers } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Error captured when a plugin hook handler fails during `invokeAfter`.
|
|
4
|
+
*/
|
|
5
|
+
export interface PluginError {
|
|
6
|
+
pluginId: string;
|
|
7
|
+
hook: string;
|
|
8
|
+
error: unknown;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Error thrown by `invokeBefore` when a plugin hook handler fails.
|
|
12
|
+
* Wraps the original error with plugin and hook context so callers
|
|
13
|
+
* can identify which plugin caused the failure.
|
|
14
|
+
*/
|
|
15
|
+
export declare class PluginInvokeError extends Error {
|
|
16
|
+
readonly name = "PluginInvokeError";
|
|
17
|
+
readonly pluginId: string;
|
|
18
|
+
readonly hook: string;
|
|
19
|
+
constructor(pluginId: string, hook: string, cause: unknown);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Runtime registry that products use to register plugin hooks and invoke them
|
|
23
|
+
* at lifecycle points.
|
|
24
|
+
*
|
|
25
|
+
* Generic over THooks so products get type-safe hook names and payloads.
|
|
26
|
+
*/
|
|
27
|
+
export declare class HookRegistry<THooks extends HookContract> {
|
|
28
|
+
private hooks;
|
|
29
|
+
private registeredPlugins;
|
|
30
|
+
/**
|
|
31
|
+
* Register all hooks declared by a plugin. Skips entries whose value is not
|
|
32
|
+
* a function (defensive against partial/optional declarations).
|
|
33
|
+
*
|
|
34
|
+
* Throws if a plugin with the same ID has already been registered.
|
|
35
|
+
*/
|
|
36
|
+
register(pluginId: string, hooks: Partial<HookHandlers<THooks>>): void;
|
|
37
|
+
/**
|
|
38
|
+
* Sequential, fail-fast invocation — for "before" hooks.
|
|
39
|
+
* Runs handlers in registration order. Aborts on first throw and wraps
|
|
40
|
+
* the error in a {@link PluginInvokeError} so the caller knows which
|
|
41
|
+
* plugin failed. Later handlers are **not** called.
|
|
42
|
+
*
|
|
43
|
+
* Uses a snapshot of the handler list so that registrations during
|
|
44
|
+
* async execution do not affect the current invocation.
|
|
45
|
+
*/
|
|
46
|
+
invokeBefore<K extends keyof THooks & string>(hook: K, payload: THooks[K]): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Sequential, collect-errors invocation — for "after" hooks.
|
|
49
|
+
* Runs **all** handlers even when some fail. Failures are wrapped in
|
|
50
|
+
* {@link PluginError} and returned as an array. Returns an empty array
|
|
51
|
+
* when all handlers succeed or when no handlers are registered.
|
|
52
|
+
*
|
|
53
|
+
* Uses a snapshot of the handler list so that registrations during
|
|
54
|
+
* async execution do not affect the current invocation.
|
|
55
|
+
*/
|
|
56
|
+
invokeAfter<K extends keyof THooks & string>(hook: K, payload: THooks[K]): Promise<PluginError[]>;
|
|
57
|
+
/**
|
|
58
|
+
* Calls a stored handler with the given payload.
|
|
59
|
+
*
|
|
60
|
+
* Handlers are stored as `(payload: never) => …` for variance compatibility
|
|
61
|
+
* (see {@link RegisteredHook}). At runtime the payload always matches
|
|
62
|
+
* `THooks[K]` — guaranteed by the generic constraint on {@link register}.
|
|
63
|
+
* This single assertion is the only place where TypeScript's type system
|
|
64
|
+
* cannot statically verify the heterogeneous map access.
|
|
65
|
+
*/
|
|
66
|
+
private callHandler;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=hook-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-registry.d.ts","sourceRoot":"","sources":["../src/hook-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE7D;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,SAAkB,IAAI,uBAAuB;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;CAQ3D;AAYD;;;;;GAKG;AACH,qBAAa,YAAY,CAAC,MAAM,SAAS,YAAY;IACnD,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,iBAAiB,CAAqB;IAE9C;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI;IAoBtE;;;;;;;;OAQG;IACG,YAAY,CAAC,CAAC,SAAS,MAAM,MAAM,GAAG,MAAM,EAChD,IAAI,EAAE,CAAC,EACP,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,GACjB,OAAO,CAAC,IAAI,CAAC;IAehB;;;;;;;;OAQG;IACG,WAAW,CAAC,CAAC,SAAS,MAAM,MAAM,GAAG,MAAM,EAC/C,IAAI,EAAE,CAAC,EACP,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,GACjB,OAAO,CAAC,WAAW,EAAE,CAAC;IAsBzB;;;;;;;;OAQG;IACH,OAAO,CAAC,WAAW;CAMpB"}
|
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,10 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
6
10
|
var __copyProps = (to, from, except, desc) => {
|
|
7
11
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
12
|
for (let key of __getOwnPropNames(from))
|
|
@@ -15,5 +19,215 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
15
19
|
|
|
16
20
|
// src/index.ts
|
|
17
21
|
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
HookRegistry: () => HookRegistry,
|
|
24
|
+
PluginInitError: () => PluginInitError,
|
|
25
|
+
PluginInvokeError: () => PluginInvokeError,
|
|
26
|
+
assertDefined: () => assertDefined,
|
|
27
|
+
assertOneOf: () => assertOneOf,
|
|
28
|
+
mergeSchemas: () => mergeSchemas,
|
|
29
|
+
resolvePlugins: () => resolvePlugins
|
|
30
|
+
});
|
|
18
31
|
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
|
|
33
|
+
// src/hook-registry.ts
|
|
34
|
+
var PluginInvokeError = class extends Error {
|
|
35
|
+
name = "PluginInvokeError";
|
|
36
|
+
pluginId;
|
|
37
|
+
hook;
|
|
38
|
+
constructor(pluginId, hook, cause) {
|
|
39
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
40
|
+
super(`Plugin "${pluginId}" failed on hook "${hook}": ${detail}`, {
|
|
41
|
+
cause
|
|
42
|
+
});
|
|
43
|
+
this.pluginId = pluginId;
|
|
44
|
+
this.hook = hook;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var HookRegistry = class {
|
|
48
|
+
hooks = /* @__PURE__ */ new Map();
|
|
49
|
+
registeredPlugins = /* @__PURE__ */ new Set();
|
|
50
|
+
/**
|
|
51
|
+
* Register all hooks declared by a plugin. Skips entries whose value is not
|
|
52
|
+
* a function (defensive against partial/optional declarations).
|
|
53
|
+
*
|
|
54
|
+
* Throws if a plugin with the same ID has already been registered.
|
|
55
|
+
*/
|
|
56
|
+
register(pluginId, hooks) {
|
|
57
|
+
if (this.registeredPlugins.has(pluginId)) {
|
|
58
|
+
throw new Error(`Plugin "${pluginId}" is already registered`);
|
|
59
|
+
}
|
|
60
|
+
this.registeredPlugins.add(pluginId);
|
|
61
|
+
for (const [key, handler] of Object.entries(hooks)) {
|
|
62
|
+
if (typeof handler !== "function") {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
let list = this.hooks.get(key);
|
|
66
|
+
if (list === void 0) {
|
|
67
|
+
list = [];
|
|
68
|
+
this.hooks.set(key, list);
|
|
69
|
+
}
|
|
70
|
+
list.push({ pluginId, handler });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Sequential, fail-fast invocation — for "before" hooks.
|
|
75
|
+
* Runs handlers in registration order. Aborts on first throw and wraps
|
|
76
|
+
* the error in a {@link PluginInvokeError} so the caller knows which
|
|
77
|
+
* plugin failed. Later handlers are **not** called.
|
|
78
|
+
*
|
|
79
|
+
* Uses a snapshot of the handler list so that registrations during
|
|
80
|
+
* async execution do not affect the current invocation.
|
|
81
|
+
*/
|
|
82
|
+
async invokeBefore(hook, payload) {
|
|
83
|
+
const list = this.hooks.get(hook);
|
|
84
|
+
if (list === void 0) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const snapshot = [...list];
|
|
88
|
+
for (const entry of snapshot) {
|
|
89
|
+
try {
|
|
90
|
+
await this.callHandler(entry.handler, payload);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
throw new PluginInvokeError(entry.pluginId, hook, error);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Sequential, collect-errors invocation — for "after" hooks.
|
|
98
|
+
* Runs **all** handlers even when some fail. Failures are wrapped in
|
|
99
|
+
* {@link PluginError} and returned as an array. Returns an empty array
|
|
100
|
+
* when all handlers succeed or when no handlers are registered.
|
|
101
|
+
*
|
|
102
|
+
* Uses a snapshot of the handler list so that registrations during
|
|
103
|
+
* async execution do not affect the current invocation.
|
|
104
|
+
*/
|
|
105
|
+
async invokeAfter(hook, payload) {
|
|
106
|
+
const list = this.hooks.get(hook);
|
|
107
|
+
if (list === void 0) {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
const errors = [];
|
|
111
|
+
const snapshot = [...list];
|
|
112
|
+
for (const entry of snapshot) {
|
|
113
|
+
try {
|
|
114
|
+
await this.callHandler(entry.handler, payload);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
errors.push({
|
|
117
|
+
pluginId: entry.pluginId,
|
|
118
|
+
hook,
|
|
119
|
+
error
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return errors;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Calls a stored handler with the given payload.
|
|
127
|
+
*
|
|
128
|
+
* Handlers are stored as `(payload: never) => …` for variance compatibility
|
|
129
|
+
* (see {@link RegisteredHook}). At runtime the payload always matches
|
|
130
|
+
* `THooks[K]` — guaranteed by the generic constraint on {@link register}.
|
|
131
|
+
* This single assertion is the only place where TypeScript's type system
|
|
132
|
+
* cannot statically verify the heterogeneous map access.
|
|
133
|
+
*/
|
|
134
|
+
callHandler(handler, payload) {
|
|
135
|
+
return handler(payload);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// src/validation.ts
|
|
140
|
+
function assertDefined(value, name) {
|
|
141
|
+
if (value === void 0) {
|
|
142
|
+
throw new Error(`Plugin config error: "${name}" is required`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function assertOneOf(value, allowed, name) {
|
|
146
|
+
if (!allowed.includes(value)) {
|
|
147
|
+
const allowedStr = allowed.join(", ");
|
|
148
|
+
throw new Error(
|
|
149
|
+
`Plugin config error: "${name}" must be one of: ${allowedStr} (got: ${String(value)})`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/schema.ts
|
|
155
|
+
function mergeSchemas(_plugins) {
|
|
156
|
+
return { tables: {}, extend: {} };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/resolve-plugins.ts
|
|
160
|
+
var PluginInitError = class extends Error {
|
|
161
|
+
name = "PluginInitError";
|
|
162
|
+
pluginId;
|
|
163
|
+
constructor(pluginId, cause) {
|
|
164
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
165
|
+
super(`Plugin "${pluginId}" failed during init: ${detail}`, { cause });
|
|
166
|
+
this.pluginId = pluginId;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
async function resolvePlugins(productId, plugins, config) {
|
|
170
|
+
const seen = /* @__PURE__ */ new Set();
|
|
171
|
+
for (const plugin of plugins) {
|
|
172
|
+
if (!plugin.id || !plugin.id.trim()) {
|
|
173
|
+
throw new Error('Plugin config error: "id" is required');
|
|
174
|
+
}
|
|
175
|
+
if (plugin.id !== plugin.id.trim()) {
|
|
176
|
+
throw new Error(
|
|
177
|
+
`Plugin config error: "id" must not have leading or trailing whitespace (got: "${plugin.id}")`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
if (seen.has(plugin.id)) {
|
|
181
|
+
throw new Error(`Duplicate plugin ID: "${plugin.id}"`);
|
|
182
|
+
}
|
|
183
|
+
seen.add(plugin.id);
|
|
184
|
+
}
|
|
185
|
+
const hookRegistry = new HookRegistry();
|
|
186
|
+
for (const plugin of plugins) {
|
|
187
|
+
if (plugin.hooks) {
|
|
188
|
+
hookRegistry.register(plugin.id, plugin.hooks);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const endpoints = [];
|
|
192
|
+
for (const plugin of plugins) {
|
|
193
|
+
if (plugin.endpoints) {
|
|
194
|
+
for (const ep of plugin.endpoints) {
|
|
195
|
+
if (!ep.path || !ep.path.startsWith("/")) {
|
|
196
|
+
throw new Error(
|
|
197
|
+
`Plugin "${plugin.id}" endpoint path must start with "/" (got: "${ep.path}")`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
endpoints.push({ ...ep, pluginId: plugin.id });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const schema = mergeSchemas(plugins);
|
|
205
|
+
const pluginIds = plugins.map((p) => p.id);
|
|
206
|
+
const frozenConfig = Object.freeze({ ...config });
|
|
207
|
+
const context = {
|
|
208
|
+
productId,
|
|
209
|
+
hasPlugin: (id) => seen.has(id),
|
|
210
|
+
config: frozenConfig
|
|
211
|
+
};
|
|
212
|
+
for (const plugin of plugins) {
|
|
213
|
+
if (plugin.init) {
|
|
214
|
+
try {
|
|
215
|
+
await plugin.init(context);
|
|
216
|
+
} catch (err) {
|
|
217
|
+
throw new PluginInitError(plugin.id, err);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return { hookRegistry, endpoints, schema, pluginIds };
|
|
222
|
+
}
|
|
223
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
224
|
+
0 && (module.exports = {
|
|
225
|
+
HookRegistry,
|
|
226
|
+
PluginInitError,
|
|
227
|
+
PluginInvokeError,
|
|
228
|
+
assertDefined,
|
|
229
|
+
assertOneOf,
|
|
230
|
+
mergeSchemas,
|
|
231
|
+
resolvePlugins
|
|
232
|
+
});
|
|
19
233
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export type {\n HookContract,\n HookHandlers,\n PluginContext,\n FieldType,\n PluginFieldDefinition,\n PluginTableDefinition,\n PluginSchema,\n HttpMethod,\n PluginRequestContext,\n PluginResponse,\n PluginEndpointHandler,\n PluginEndpoint,\n PluginDefinition,\n} from \"./types.js\";\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/hook-registry.ts","../src/validation.ts","../src/schema.ts","../src/resolve-plugins.ts"],"sourcesContent":["export type {\n HookContract,\n HookHandlers,\n PluginContext,\n FieldType,\n PluginFieldDefinition,\n PluginTableDefinition,\n PluginSchema,\n HttpMethod,\n PluginRequestContext,\n PluginResponse,\n PluginEndpointHandler,\n PluginEndpoint,\n PluginDefinition,\n} from \"./types.js\";\n\nexport { HookRegistry, PluginInvokeError } from \"./hook-registry.js\";\nexport type { PluginError } from \"./hook-registry.js\";\n\nexport { assertDefined, assertOneOf } from \"./validation.js\";\n\nexport { mergeSchemas } from \"./schema.js\";\nexport type { MergedPluginSchema } from \"./schema.js\";\n\nexport { resolvePlugins, PluginInitError } from \"./resolve-plugins.js\";\nexport type { ResolvedPlugins, ResolvedEndpoint } from \"./resolve-plugins.js\";\n","import type { HookContract, HookHandlers } from \"./types.js\";\n\n/**\n * Error captured when a plugin hook handler fails during `invokeAfter`.\n */\nexport interface PluginError {\n pluginId: string;\n hook: string;\n error: unknown;\n}\n\n/**\n * Error thrown by `invokeBefore` when a plugin hook handler fails.\n * Wraps the original error with plugin and hook context so callers\n * can identify which plugin caused the failure.\n */\nexport class PluginInvokeError extends Error {\n override readonly name = \"PluginInvokeError\";\n readonly pluginId: string;\n readonly hook: string;\n\n constructor(pluginId: string, hook: string, cause: unknown) {\n const detail = cause instanceof Error ? cause.message : String(cause);\n super(`Plugin \"${pluginId}\" failed on hook \"${hook}\": ${detail}`, {\n cause,\n });\n this.pluginId = pluginId;\n this.hook = hook;\n }\n}\n\n/**\n * Internal handler entry. The handler is typed as `(payload: never) => …`\n * because any function `(payload: T) => R` is assignable to it (contravariance\n * on `never`). This avoids a type assertion at registration time.\n */\ninterface RegisteredHook {\n pluginId: string;\n handler: (payload: never) => void | Promise<void>;\n}\n\n/**\n * Runtime registry that products use to register plugin hooks and invoke them\n * at lifecycle points.\n *\n * Generic over THooks so products get type-safe hook names and payloads.\n */\nexport class HookRegistry<THooks extends HookContract> {\n private hooks = new Map<string, RegisteredHook[]>();\n private registeredPlugins = new Set<string>();\n\n /**\n * Register all hooks declared by a plugin. Skips entries whose value is not\n * a function (defensive against partial/optional declarations).\n *\n * Throws if a plugin with the same ID has already been registered.\n */\n register(pluginId: string, hooks: Partial<HookHandlers<THooks>>): void {\n if (this.registeredPlugins.has(pluginId)) {\n throw new Error(`Plugin \"${pluginId}\" is already registered`);\n }\n this.registeredPlugins.add(pluginId);\n\n for (const [key, handler] of Object.entries(hooks)) {\n if (typeof handler !== \"function\") {\n continue;\n }\n\n let list = this.hooks.get(key);\n if (list === undefined) {\n list = [];\n this.hooks.set(key, list);\n }\n list.push({ pluginId, handler });\n }\n }\n\n /**\n * Sequential, fail-fast invocation — for \"before\" hooks.\n * Runs handlers in registration order. Aborts on first throw and wraps\n * the error in a {@link PluginInvokeError} so the caller knows which\n * plugin failed. Later handlers are **not** called.\n *\n * Uses a snapshot of the handler list so that registrations during\n * async execution do not affect the current invocation.\n */\n async invokeBefore<K extends keyof THooks & string>(\n hook: K,\n payload: THooks[K],\n ): Promise<void> {\n const list = this.hooks.get(hook);\n if (list === undefined) {\n return;\n }\n const snapshot = [...list];\n for (const entry of snapshot) {\n try {\n await this.callHandler(entry.handler, payload);\n } catch (error: unknown) {\n throw new PluginInvokeError(entry.pluginId, hook, error);\n }\n }\n }\n\n /**\n * Sequential, collect-errors invocation — for \"after\" hooks.\n * Runs **all** handlers even when some fail. Failures are wrapped in\n * {@link PluginError} and returned as an array. Returns an empty array\n * when all handlers succeed or when no handlers are registered.\n *\n * Uses a snapshot of the handler list so that registrations during\n * async execution do not affect the current invocation.\n */\n async invokeAfter<K extends keyof THooks & string>(\n hook: K,\n payload: THooks[K],\n ): Promise<PluginError[]> {\n const list = this.hooks.get(hook);\n if (list === undefined) {\n return [];\n }\n\n const errors: PluginError[] = [];\n const snapshot = [...list];\n for (const entry of snapshot) {\n try {\n await this.callHandler(entry.handler, payload);\n } catch (error: unknown) {\n errors.push({\n pluginId: entry.pluginId,\n hook,\n error,\n });\n }\n }\n return errors;\n }\n\n /**\n * Calls a stored handler with the given payload.\n *\n * Handlers are stored as `(payload: never) => …` for variance compatibility\n * (see {@link RegisteredHook}). At runtime the payload always matches\n * `THooks[K]` — guaranteed by the generic constraint on {@link register}.\n * This single assertion is the only place where TypeScript's type system\n * cannot statically verify the heterogeneous map access.\n */\n private callHandler<K extends keyof THooks>(\n handler: (payload: never) => void | Promise<void>,\n payload: THooks[K],\n ): void | Promise<void> {\n return (handler as (p: THooks[K]) => void | Promise<void>)(payload);\n }\n}\n","/**\n * Asserts that a value is not `undefined`.\n * Throws with a descriptive error message following the plugin config error pattern.\n */\nexport function assertDefined<T>(\n value: T | undefined,\n name: string,\n): asserts value is T {\n if (value === undefined) {\n throw new Error(`Plugin config error: \"${name}\" is required`);\n }\n}\n\n/**\n * Asserts that a value is one of the allowed values.\n * Throws with a descriptive error message listing the allowed options.\n */\nexport function assertOneOf<T extends string | number | boolean>(value: T, allowed: T[], name: string): void {\n if (!allowed.includes(value)) {\n const allowedStr = allowed.join(\", \");\n throw new Error(\n `Plugin config error: \"${name}\" must be one of: ${allowedStr} (got: ${String(value)})`,\n );\n }\n}\n","import type {\n HookContract,\n PluginDefinition,\n PluginFieldDefinition,\n PluginTableDefinition,\n} from \"./types.js\";\n\n/**\n * Merged schema produced by combining all plugin schema declarations.\n * Unlike per-plugin `PluginSchema` (where fields are optional),\n * the merged result always contains both maps.\n */\nexport interface MergedPluginSchema {\n tables: Record<string, PluginTableDefinition>;\n extend: Record<string, Record<string, PluginFieldDefinition>>;\n}\n\n/**\n * Merges schema declarations from all plugins into a single result.\n *\n * Returns empty maps. Full merge logic (conflict detection, ordering)\n * will be added when schema merging is needed.\n */\nexport function mergeSchemas<THooks extends HookContract>(\n _plugins: PluginDefinition<THooks>[],\n): MergedPluginSchema {\n return { tables: {}, extend: {} };\n}\n","import { HookRegistry } from \"./hook-registry.js\";\nimport type { MergedPluginSchema } from \"./schema.js\";\nimport { mergeSchemas } from \"./schema.js\";\nimport type {\n HookContract,\n PluginContext,\n PluginDefinition,\n PluginEndpoint,\n} from \"./types.js\";\n\nexport class PluginInitError extends Error {\n override readonly name = \"PluginInitError\";\n readonly pluginId: string;\n\n constructor(pluginId: string, cause: unknown) {\n const detail = cause instanceof Error ? cause.message : String(cause);\n super(`Plugin \"${pluginId}\" failed during init: ${detail}`, { cause });\n this.pluginId = pluginId;\n }\n}\n\n/**\n * A collected endpoint annotated with the plugin that contributed it.\n */\nexport interface ResolvedEndpoint extends PluginEndpoint {\n pluginId: string;\n}\n\n/**\n * Result of resolving a set of plugins. Contains the fully wired\n * hook registry, collected endpoints, merged schema, and plugin IDs.\n */\nexport interface ResolvedPlugins<THooks extends HookContract> {\n hookRegistry: HookRegistry<THooks>;\n endpoints: ResolvedEndpoint[];\n schema: MergedPluginSchema;\n pluginIds: string[];\n}\n\n/**\n * Lifecycle orchestrator that products call during construction.\n *\n * Steps:\n * 1. **Validate** — duplicate IDs throw immediately\n * 2. **Register hooks** — into a new HookRegistry\n * 3. **Collect endpoints** — flat array from all plugins\n * 4. **Merge schemas** — calls mergeSchemas\n * 5. **Init** — call plugin.init(ctx) sequentially in declaration order\n */\nexport async function resolvePlugins<THooks extends HookContract>(\n productId: string,\n plugins: PluginDefinition<THooks>[],\n config: Record<string, unknown>,\n): Promise<ResolvedPlugins<THooks>> {\n // 1. Validate — check for empty/whitespace IDs and duplicates\n const seen = new Set<string>();\n for (const plugin of plugins) {\n if (!plugin.id || !plugin.id.trim()) {\n throw new Error('Plugin config error: \"id\" is required');\n }\n if (plugin.id !== plugin.id.trim()) {\n throw new Error(\n `Plugin config error: \"id\" must not have leading or trailing whitespace (got: \"${plugin.id}\")`,\n );\n }\n if (seen.has(plugin.id)) {\n throw new Error(`Duplicate plugin ID: \"${plugin.id}\"`);\n }\n seen.add(plugin.id);\n }\n\n // 2. Register hooks\n const hookRegistry = new HookRegistry<THooks>();\n for (const plugin of plugins) {\n if (plugin.hooks) {\n hookRegistry.register(plugin.id, plugin.hooks);\n }\n }\n\n // 3. Collect endpoints (annotated with pluginId)\n const endpoints: ResolvedEndpoint[] = [];\n for (const plugin of plugins) {\n if (plugin.endpoints) {\n for (const ep of plugin.endpoints) {\n if (!ep.path || !ep.path.startsWith(\"/\")) {\n throw new Error(\n `Plugin \"${plugin.id}\" endpoint path must start with \"/\" (got: \"${ep.path}\")`,\n );\n }\n endpoints.push({ ...ep, pluginId: plugin.id });\n }\n }\n }\n\n // 4. Merge schemas\n const schema = mergeSchemas(plugins);\n\n // 5. Init — sequential in declaration order\n const pluginIds = plugins.map((p) => p.id);\n const frozenConfig = Object.freeze({ ...config });\n const context: PluginContext = {\n productId,\n hasPlugin: (id: string) => seen.has(id),\n config: frozenConfig,\n };\n\n for (const plugin of plugins) {\n if (plugin.init) {\n try {\n await plugin.init(context);\n } catch (err) {\n throw new PluginInitError(plugin.id, err);\n }\n }\n }\n\n return { hookRegistry, endpoints, schema, pluginIds };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACgBO,IAAM,oBAAN,cAAgC,MAAM;AAAA,EACzB,OAAO;AAAA,EAChB;AAAA,EACA;AAAA,EAET,YAAY,UAAkB,MAAc,OAAgB;AAC1D,UAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACpE,UAAM,WAAW,QAAQ,qBAAqB,IAAI,MAAM,MAAM,IAAI;AAAA,MAChE;AAAA,IACF,CAAC;AACD,SAAK,WAAW;AAChB,SAAK,OAAO;AAAA,EACd;AACF;AAkBO,IAAM,eAAN,MAAgD;AAAA,EAC7C,QAAQ,oBAAI,IAA8B;AAAA,EAC1C,oBAAoB,oBAAI,IAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ5C,SAAS,UAAkB,OAA4C;AACrE,QAAI,KAAK,kBAAkB,IAAI,QAAQ,GAAG;AACxC,YAAM,IAAI,MAAM,WAAW,QAAQ,yBAAyB;AAAA,IAC9D;AACA,SAAK,kBAAkB,IAAI,QAAQ;AAEnC,eAAW,CAAC,KAAK,OAAO,KAAK,OAAO,QAAQ,KAAK,GAAG;AAClD,UAAI,OAAO,YAAY,YAAY;AACjC;AAAA,MACF;AAEA,UAAI,OAAO,KAAK,MAAM,IAAI,GAAG;AAC7B,UAAI,SAAS,QAAW;AACtB,eAAO,CAAC;AACR,aAAK,MAAM,IAAI,KAAK,IAAI;AAAA,MAC1B;AACA,WAAK,KAAK,EAAE,UAAU,QAAQ,CAAC;AAAA,IACjC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,aACJ,MACA,SACe;AACf,UAAM,OAAO,KAAK,MAAM,IAAI,IAAI;AAChC,QAAI,SAAS,QAAW;AACtB;AAAA,IACF;AACA,UAAM,WAAW,CAAC,GAAG,IAAI;AACzB,eAAW,SAAS,UAAU;AAC5B,UAAI;AACF,cAAM,KAAK,YAAY,MAAM,SAAS,OAAO;AAAA,MAC/C,SAAS,OAAgB;AACvB,cAAM,IAAI,kBAAkB,MAAM,UAAU,MAAM,KAAK;AAAA,MACzD;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,YACJ,MACA,SACwB;AACxB,UAAM,OAAO,KAAK,MAAM,IAAI,IAAI;AAChC,QAAI,SAAS,QAAW;AACtB,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,SAAwB,CAAC;AAC/B,UAAM,WAAW,CAAC,GAAG,IAAI;AACzB,eAAW,SAAS,UAAU;AAC5B,UAAI;AACF,cAAM,KAAK,YAAY,MAAM,SAAS,OAAO;AAAA,MAC/C,SAAS,OAAgB;AACvB,eAAO,KAAK;AAAA,UACV,UAAU,MAAM;AAAA,UAChB;AAAA,UACA;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,YACN,SACA,SACsB;AACtB,WAAQ,QAAmD,OAAO;AAAA,EACpE;AACF;;;ACrJO,SAAS,cACd,OACA,MACoB;AACpB,MAAI,UAAU,QAAW;AACvB,UAAM,IAAI,MAAM,yBAAyB,IAAI,eAAe;AAAA,EAC9D;AACF;AAMO,SAAS,YAAiD,OAAU,SAAc,MAAoB;AAC3G,MAAI,CAAC,QAAQ,SAAS,KAAK,GAAG;AAC5B,UAAM,aAAa,QAAQ,KAAK,IAAI;AACpC,UAAM,IAAI;AAAA,MACR,yBAAyB,IAAI,qBAAqB,UAAU,UAAU,OAAO,KAAK,CAAC;AAAA,IACrF;AAAA,EACF;AACF;;;ACDO,SAAS,aACd,UACoB;AACpB,SAAO,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE;AAClC;;;ACjBO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EACvB,OAAO;AAAA,EAChB;AAAA,EAET,YAAY,UAAkB,OAAgB;AAC5C,UAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACpE,UAAM,WAAW,QAAQ,yBAAyB,MAAM,IAAI,EAAE,MAAM,CAAC;AACrE,SAAK,WAAW;AAAA,EAClB;AACF;AA8BA,eAAsB,eACpB,WACA,SACA,QACkC;AAElC,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,UAAU,SAAS;AAC5B,QAAI,CAAC,OAAO,MAAM,CAAC,OAAO,GAAG,KAAK,GAAG;AACnC,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AACA,QAAI,OAAO,OAAO,OAAO,GAAG,KAAK,GAAG;AAClC,YAAM,IAAI;AAAA,QACR,iFAAiF,OAAO,EAAE;AAAA,MAC5F;AAAA,IACF;AACA,QAAI,KAAK,IAAI,OAAO,EAAE,GAAG;AACvB,YAAM,IAAI,MAAM,yBAAyB,OAAO,EAAE,GAAG;AAAA,IACvD;AACA,SAAK,IAAI,OAAO,EAAE;AAAA,EACpB;AAGA,QAAM,eAAe,IAAI,aAAqB;AAC9C,aAAW,UAAU,SAAS;AAC5B,QAAI,OAAO,OAAO;AAChB,mBAAa,SAAS,OAAO,IAAI,OAAO,KAAK;AAAA,IAC/C;AAAA,EACF;AAGA,QAAM,YAAgC,CAAC;AACvC,aAAW,UAAU,SAAS;AAC5B,QAAI,OAAO,WAAW;AACpB,iBAAW,MAAM,OAAO,WAAW;AACjC,YAAI,CAAC,GAAG,QAAQ,CAAC,GAAG,KAAK,WAAW,GAAG,GAAG;AACxC,gBAAM,IAAI;AAAA,YACR,WAAW,OAAO,EAAE,8CAA8C,GAAG,IAAI;AAAA,UAC3E;AAAA,QACF;AACA,kBAAU,KAAK,EAAE,GAAG,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MAC/C;AAAA,IACF;AAAA,EACF;AAGA,QAAM,SAAS,aAAa,OAAO;AAGnC,QAAM,YAAY,QAAQ,IAAI,CAAC,MAAM,EAAE,EAAE;AACzC,QAAM,eAAe,OAAO,OAAO,EAAE,GAAG,OAAO,CAAC;AAChD,QAAM,UAAyB;AAAA,IAC7B;AAAA,IACA,WAAW,CAAC,OAAe,KAAK,IAAI,EAAE;AAAA,IACtC,QAAQ;AAAA,EACV;AAEA,aAAW,UAAU,SAAS;AAC5B,QAAI,OAAO,MAAM;AACf,UAAI;AACF,cAAM,OAAO,KAAK,OAAO;AAAA,MAC3B,SAAS,KAAK;AACZ,cAAM,IAAI,gBAAgB,OAAO,IAAI,GAAG;AAAA,MAC1C;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,cAAc,WAAW,QAAQ,UAAU;AACtD;","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
export type { HookContract, HookHandlers, PluginContext, FieldType, PluginFieldDefinition, PluginTableDefinition, PluginSchema, HttpMethod, PluginRequestContext, PluginResponse, PluginEndpointHandler, PluginEndpoint, PluginDefinition, } from "./types.js";
|
|
2
|
+
export { HookRegistry, PluginInvokeError } from "./hook-registry.js";
|
|
3
|
+
export type { PluginError } from "./hook-registry.js";
|
|
4
|
+
export { assertDefined, assertOneOf } from "./validation.js";
|
|
5
|
+
export { mergeSchemas } from "./schema.js";
|
|
6
|
+
export type { MergedPluginSchema } from "./schema.js";
|
|
7
|
+
export { resolvePlugins, PluginInitError } from "./resolve-plugins.js";
|
|
8
|
+
export type { ResolvedPlugins, ResolvedEndpoint } from "./resolve-plugins.js";
|
|
2
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,SAAS,EACT,qBAAqB,EACrB,qBAAqB,EACrB,YAAY,EACZ,UAAU,EACV,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,cAAc,EACd,gBAAgB,GACjB,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,SAAS,EACT,qBAAqB,EACrB,qBAAqB,EACrB,YAAY,EACZ,UAAU,EACV,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,cAAc,EACd,gBAAgB,GACjB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACrE,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE7D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvE,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1 +1,200 @@
|
|
|
1
|
+
// src/hook-registry.ts
|
|
2
|
+
var PluginInvokeError = class extends Error {
|
|
3
|
+
name = "PluginInvokeError";
|
|
4
|
+
pluginId;
|
|
5
|
+
hook;
|
|
6
|
+
constructor(pluginId, hook, cause) {
|
|
7
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
8
|
+
super(`Plugin "${pluginId}" failed on hook "${hook}": ${detail}`, {
|
|
9
|
+
cause
|
|
10
|
+
});
|
|
11
|
+
this.pluginId = pluginId;
|
|
12
|
+
this.hook = hook;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var HookRegistry = class {
|
|
16
|
+
hooks = /* @__PURE__ */ new Map();
|
|
17
|
+
registeredPlugins = /* @__PURE__ */ new Set();
|
|
18
|
+
/**
|
|
19
|
+
* Register all hooks declared by a plugin. Skips entries whose value is not
|
|
20
|
+
* a function (defensive against partial/optional declarations).
|
|
21
|
+
*
|
|
22
|
+
* Throws if a plugin with the same ID has already been registered.
|
|
23
|
+
*/
|
|
24
|
+
register(pluginId, hooks) {
|
|
25
|
+
if (this.registeredPlugins.has(pluginId)) {
|
|
26
|
+
throw new Error(`Plugin "${pluginId}" is already registered`);
|
|
27
|
+
}
|
|
28
|
+
this.registeredPlugins.add(pluginId);
|
|
29
|
+
for (const [key, handler] of Object.entries(hooks)) {
|
|
30
|
+
if (typeof handler !== "function") {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
let list = this.hooks.get(key);
|
|
34
|
+
if (list === void 0) {
|
|
35
|
+
list = [];
|
|
36
|
+
this.hooks.set(key, list);
|
|
37
|
+
}
|
|
38
|
+
list.push({ pluginId, handler });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Sequential, fail-fast invocation — for "before" hooks.
|
|
43
|
+
* Runs handlers in registration order. Aborts on first throw and wraps
|
|
44
|
+
* the error in a {@link PluginInvokeError} so the caller knows which
|
|
45
|
+
* plugin failed. Later handlers are **not** called.
|
|
46
|
+
*
|
|
47
|
+
* Uses a snapshot of the handler list so that registrations during
|
|
48
|
+
* async execution do not affect the current invocation.
|
|
49
|
+
*/
|
|
50
|
+
async invokeBefore(hook, payload) {
|
|
51
|
+
const list = this.hooks.get(hook);
|
|
52
|
+
if (list === void 0) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const snapshot = [...list];
|
|
56
|
+
for (const entry of snapshot) {
|
|
57
|
+
try {
|
|
58
|
+
await this.callHandler(entry.handler, payload);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
throw new PluginInvokeError(entry.pluginId, hook, error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Sequential, collect-errors invocation — for "after" hooks.
|
|
66
|
+
* Runs **all** handlers even when some fail. Failures are wrapped in
|
|
67
|
+
* {@link PluginError} and returned as an array. Returns an empty array
|
|
68
|
+
* when all handlers succeed or when no handlers are registered.
|
|
69
|
+
*
|
|
70
|
+
* Uses a snapshot of the handler list so that registrations during
|
|
71
|
+
* async execution do not affect the current invocation.
|
|
72
|
+
*/
|
|
73
|
+
async invokeAfter(hook, payload) {
|
|
74
|
+
const list = this.hooks.get(hook);
|
|
75
|
+
if (list === void 0) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
const errors = [];
|
|
79
|
+
const snapshot = [...list];
|
|
80
|
+
for (const entry of snapshot) {
|
|
81
|
+
try {
|
|
82
|
+
await this.callHandler(entry.handler, payload);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
errors.push({
|
|
85
|
+
pluginId: entry.pluginId,
|
|
86
|
+
hook,
|
|
87
|
+
error
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return errors;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Calls a stored handler with the given payload.
|
|
95
|
+
*
|
|
96
|
+
* Handlers are stored as `(payload: never) => …` for variance compatibility
|
|
97
|
+
* (see {@link RegisteredHook}). At runtime the payload always matches
|
|
98
|
+
* `THooks[K]` — guaranteed by the generic constraint on {@link register}.
|
|
99
|
+
* This single assertion is the only place where TypeScript's type system
|
|
100
|
+
* cannot statically verify the heterogeneous map access.
|
|
101
|
+
*/
|
|
102
|
+
callHandler(handler, payload) {
|
|
103
|
+
return handler(payload);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// src/validation.ts
|
|
108
|
+
function assertDefined(value, name) {
|
|
109
|
+
if (value === void 0) {
|
|
110
|
+
throw new Error(`Plugin config error: "${name}" is required`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function assertOneOf(value, allowed, name) {
|
|
114
|
+
if (!allowed.includes(value)) {
|
|
115
|
+
const allowedStr = allowed.join(", ");
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Plugin config error: "${name}" must be one of: ${allowedStr} (got: ${String(value)})`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/schema.ts
|
|
123
|
+
function mergeSchemas(_plugins) {
|
|
124
|
+
return { tables: {}, extend: {} };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/resolve-plugins.ts
|
|
128
|
+
var PluginInitError = class extends Error {
|
|
129
|
+
name = "PluginInitError";
|
|
130
|
+
pluginId;
|
|
131
|
+
constructor(pluginId, cause) {
|
|
132
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
133
|
+
super(`Plugin "${pluginId}" failed during init: ${detail}`, { cause });
|
|
134
|
+
this.pluginId = pluginId;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
async function resolvePlugins(productId, plugins, config) {
|
|
138
|
+
const seen = /* @__PURE__ */ new Set();
|
|
139
|
+
for (const plugin of plugins) {
|
|
140
|
+
if (!plugin.id || !plugin.id.trim()) {
|
|
141
|
+
throw new Error('Plugin config error: "id" is required');
|
|
142
|
+
}
|
|
143
|
+
if (plugin.id !== plugin.id.trim()) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Plugin config error: "id" must not have leading or trailing whitespace (got: "${plugin.id}")`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
if (seen.has(plugin.id)) {
|
|
149
|
+
throw new Error(`Duplicate plugin ID: "${plugin.id}"`);
|
|
150
|
+
}
|
|
151
|
+
seen.add(plugin.id);
|
|
152
|
+
}
|
|
153
|
+
const hookRegistry = new HookRegistry();
|
|
154
|
+
for (const plugin of plugins) {
|
|
155
|
+
if (plugin.hooks) {
|
|
156
|
+
hookRegistry.register(plugin.id, plugin.hooks);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const endpoints = [];
|
|
160
|
+
for (const plugin of plugins) {
|
|
161
|
+
if (plugin.endpoints) {
|
|
162
|
+
for (const ep of plugin.endpoints) {
|
|
163
|
+
if (!ep.path || !ep.path.startsWith("/")) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Plugin "${plugin.id}" endpoint path must start with "/" (got: "${ep.path}")`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
endpoints.push({ ...ep, pluginId: plugin.id });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const schema = mergeSchemas(plugins);
|
|
173
|
+
const pluginIds = plugins.map((p) => p.id);
|
|
174
|
+
const frozenConfig = Object.freeze({ ...config });
|
|
175
|
+
const context = {
|
|
176
|
+
productId,
|
|
177
|
+
hasPlugin: (id) => seen.has(id),
|
|
178
|
+
config: frozenConfig
|
|
179
|
+
};
|
|
180
|
+
for (const plugin of plugins) {
|
|
181
|
+
if (plugin.init) {
|
|
182
|
+
try {
|
|
183
|
+
await plugin.init(context);
|
|
184
|
+
} catch (err) {
|
|
185
|
+
throw new PluginInitError(plugin.id, err);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return { hookRegistry, endpoints, schema, pluginIds };
|
|
190
|
+
}
|
|
191
|
+
export {
|
|
192
|
+
HookRegistry,
|
|
193
|
+
PluginInitError,
|
|
194
|
+
PluginInvokeError,
|
|
195
|
+
assertDefined,
|
|
196
|
+
assertOneOf,
|
|
197
|
+
mergeSchemas,
|
|
198
|
+
resolvePlugins
|
|
199
|
+
};
|
|
1
200
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/hook-registry.ts","../src/validation.ts","../src/schema.ts","../src/resolve-plugins.ts"],"sourcesContent":["import type { HookContract, HookHandlers } from \"./types.js\";\n\n/**\n * Error captured when a plugin hook handler fails during `invokeAfter`.\n */\nexport interface PluginError {\n pluginId: string;\n hook: string;\n error: unknown;\n}\n\n/**\n * Error thrown by `invokeBefore` when a plugin hook handler fails.\n * Wraps the original error with plugin and hook context so callers\n * can identify which plugin caused the failure.\n */\nexport class PluginInvokeError extends Error {\n override readonly name = \"PluginInvokeError\";\n readonly pluginId: string;\n readonly hook: string;\n\n constructor(pluginId: string, hook: string, cause: unknown) {\n const detail = cause instanceof Error ? cause.message : String(cause);\n super(`Plugin \"${pluginId}\" failed on hook \"${hook}\": ${detail}`, {\n cause,\n });\n this.pluginId = pluginId;\n this.hook = hook;\n }\n}\n\n/**\n * Internal handler entry. The handler is typed as `(payload: never) => …`\n * because any function `(payload: T) => R` is assignable to it (contravariance\n * on `never`). This avoids a type assertion at registration time.\n */\ninterface RegisteredHook {\n pluginId: string;\n handler: (payload: never) => void | Promise<void>;\n}\n\n/**\n * Runtime registry that products use to register plugin hooks and invoke them\n * at lifecycle points.\n *\n * Generic over THooks so products get type-safe hook names and payloads.\n */\nexport class HookRegistry<THooks extends HookContract> {\n private hooks = new Map<string, RegisteredHook[]>();\n private registeredPlugins = new Set<string>();\n\n /**\n * Register all hooks declared by a plugin. Skips entries whose value is not\n * a function (defensive against partial/optional declarations).\n *\n * Throws if a plugin with the same ID has already been registered.\n */\n register(pluginId: string, hooks: Partial<HookHandlers<THooks>>): void {\n if (this.registeredPlugins.has(pluginId)) {\n throw new Error(`Plugin \"${pluginId}\" is already registered`);\n }\n this.registeredPlugins.add(pluginId);\n\n for (const [key, handler] of Object.entries(hooks)) {\n if (typeof handler !== \"function\") {\n continue;\n }\n\n let list = this.hooks.get(key);\n if (list === undefined) {\n list = [];\n this.hooks.set(key, list);\n }\n list.push({ pluginId, handler });\n }\n }\n\n /**\n * Sequential, fail-fast invocation — for \"before\" hooks.\n * Runs handlers in registration order. Aborts on first throw and wraps\n * the error in a {@link PluginInvokeError} so the caller knows which\n * plugin failed. Later handlers are **not** called.\n *\n * Uses a snapshot of the handler list so that registrations during\n * async execution do not affect the current invocation.\n */\n async invokeBefore<K extends keyof THooks & string>(\n hook: K,\n payload: THooks[K],\n ): Promise<void> {\n const list = this.hooks.get(hook);\n if (list === undefined) {\n return;\n }\n const snapshot = [...list];\n for (const entry of snapshot) {\n try {\n await this.callHandler(entry.handler, payload);\n } catch (error: unknown) {\n throw new PluginInvokeError(entry.pluginId, hook, error);\n }\n }\n }\n\n /**\n * Sequential, collect-errors invocation — for \"after\" hooks.\n * Runs **all** handlers even when some fail. Failures are wrapped in\n * {@link PluginError} and returned as an array. Returns an empty array\n * when all handlers succeed or when no handlers are registered.\n *\n * Uses a snapshot of the handler list so that registrations during\n * async execution do not affect the current invocation.\n */\n async invokeAfter<K extends keyof THooks & string>(\n hook: K,\n payload: THooks[K],\n ): Promise<PluginError[]> {\n const list = this.hooks.get(hook);\n if (list === undefined) {\n return [];\n }\n\n const errors: PluginError[] = [];\n const snapshot = [...list];\n for (const entry of snapshot) {\n try {\n await this.callHandler(entry.handler, payload);\n } catch (error: unknown) {\n errors.push({\n pluginId: entry.pluginId,\n hook,\n error,\n });\n }\n }\n return errors;\n }\n\n /**\n * Calls a stored handler with the given payload.\n *\n * Handlers are stored as `(payload: never) => …` for variance compatibility\n * (see {@link RegisteredHook}). At runtime the payload always matches\n * `THooks[K]` — guaranteed by the generic constraint on {@link register}.\n * This single assertion is the only place where TypeScript's type system\n * cannot statically verify the heterogeneous map access.\n */\n private callHandler<K extends keyof THooks>(\n handler: (payload: never) => void | Promise<void>,\n payload: THooks[K],\n ): void | Promise<void> {\n return (handler as (p: THooks[K]) => void | Promise<void>)(payload);\n }\n}\n","/**\n * Asserts that a value is not `undefined`.\n * Throws with a descriptive error message following the plugin config error pattern.\n */\nexport function assertDefined<T>(\n value: T | undefined,\n name: string,\n): asserts value is T {\n if (value === undefined) {\n throw new Error(`Plugin config error: \"${name}\" is required`);\n }\n}\n\n/**\n * Asserts that a value is one of the allowed values.\n * Throws with a descriptive error message listing the allowed options.\n */\nexport function assertOneOf<T extends string | number | boolean>(value: T, allowed: T[], name: string): void {\n if (!allowed.includes(value)) {\n const allowedStr = allowed.join(\", \");\n throw new Error(\n `Plugin config error: \"${name}\" must be one of: ${allowedStr} (got: ${String(value)})`,\n );\n }\n}\n","import type {\n HookContract,\n PluginDefinition,\n PluginFieldDefinition,\n PluginTableDefinition,\n} from \"./types.js\";\n\n/**\n * Merged schema produced by combining all plugin schema declarations.\n * Unlike per-plugin `PluginSchema` (where fields are optional),\n * the merged result always contains both maps.\n */\nexport interface MergedPluginSchema {\n tables: Record<string, PluginTableDefinition>;\n extend: Record<string, Record<string, PluginFieldDefinition>>;\n}\n\n/**\n * Merges schema declarations from all plugins into a single result.\n *\n * Returns empty maps. Full merge logic (conflict detection, ordering)\n * will be added when schema merging is needed.\n */\nexport function mergeSchemas<THooks extends HookContract>(\n _plugins: PluginDefinition<THooks>[],\n): MergedPluginSchema {\n return { tables: {}, extend: {} };\n}\n","import { HookRegistry } from \"./hook-registry.js\";\nimport type { MergedPluginSchema } from \"./schema.js\";\nimport { mergeSchemas } from \"./schema.js\";\nimport type {\n HookContract,\n PluginContext,\n PluginDefinition,\n PluginEndpoint,\n} from \"./types.js\";\n\nexport class PluginInitError extends Error {\n override readonly name = \"PluginInitError\";\n readonly pluginId: string;\n\n constructor(pluginId: string, cause: unknown) {\n const detail = cause instanceof Error ? cause.message : String(cause);\n super(`Plugin \"${pluginId}\" failed during init: ${detail}`, { cause });\n this.pluginId = pluginId;\n }\n}\n\n/**\n * A collected endpoint annotated with the plugin that contributed it.\n */\nexport interface ResolvedEndpoint extends PluginEndpoint {\n pluginId: string;\n}\n\n/**\n * Result of resolving a set of plugins. Contains the fully wired\n * hook registry, collected endpoints, merged schema, and plugin IDs.\n */\nexport interface ResolvedPlugins<THooks extends HookContract> {\n hookRegistry: HookRegistry<THooks>;\n endpoints: ResolvedEndpoint[];\n schema: MergedPluginSchema;\n pluginIds: string[];\n}\n\n/**\n * Lifecycle orchestrator that products call during construction.\n *\n * Steps:\n * 1. **Validate** — duplicate IDs throw immediately\n * 2. **Register hooks** — into a new HookRegistry\n * 3. **Collect endpoints** — flat array from all plugins\n * 4. **Merge schemas** — calls mergeSchemas\n * 5. **Init** — call plugin.init(ctx) sequentially in declaration order\n */\nexport async function resolvePlugins<THooks extends HookContract>(\n productId: string,\n plugins: PluginDefinition<THooks>[],\n config: Record<string, unknown>,\n): Promise<ResolvedPlugins<THooks>> {\n // 1. Validate — check for empty/whitespace IDs and duplicates\n const seen = new Set<string>();\n for (const plugin of plugins) {\n if (!plugin.id || !plugin.id.trim()) {\n throw new Error('Plugin config error: \"id\" is required');\n }\n if (plugin.id !== plugin.id.trim()) {\n throw new Error(\n `Plugin config error: \"id\" must not have leading or trailing whitespace (got: \"${plugin.id}\")`,\n );\n }\n if (seen.has(plugin.id)) {\n throw new Error(`Duplicate plugin ID: \"${plugin.id}\"`);\n }\n seen.add(plugin.id);\n }\n\n // 2. Register hooks\n const hookRegistry = new HookRegistry<THooks>();\n for (const plugin of plugins) {\n if (plugin.hooks) {\n hookRegistry.register(plugin.id, plugin.hooks);\n }\n }\n\n // 3. Collect endpoints (annotated with pluginId)\n const endpoints: ResolvedEndpoint[] = [];\n for (const plugin of plugins) {\n if (plugin.endpoints) {\n for (const ep of plugin.endpoints) {\n if (!ep.path || !ep.path.startsWith(\"/\")) {\n throw new Error(\n `Plugin \"${plugin.id}\" endpoint path must start with \"/\" (got: \"${ep.path}\")`,\n );\n }\n endpoints.push({ ...ep, pluginId: plugin.id });\n }\n }\n }\n\n // 4. Merge schemas\n const schema = mergeSchemas(plugins);\n\n // 5. Init — sequential in declaration order\n const pluginIds = plugins.map((p) => p.id);\n const frozenConfig = Object.freeze({ ...config });\n const context: PluginContext = {\n productId,\n hasPlugin: (id: string) => seen.has(id),\n config: frozenConfig,\n };\n\n for (const plugin of plugins) {\n if (plugin.init) {\n try {\n await plugin.init(context);\n } catch (err) {\n throw new PluginInitError(plugin.id, err);\n }\n }\n }\n\n return { hookRegistry, endpoints, schema, pluginIds };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { HookRegistry } from "./hook-registry.js";
|
|
2
|
+
import type { MergedPluginSchema } from "./schema.js";
|
|
3
|
+
import type { HookContract, PluginDefinition, PluginEndpoint } from "./types.js";
|
|
4
|
+
export declare class PluginInitError extends Error {
|
|
5
|
+
readonly name = "PluginInitError";
|
|
6
|
+
readonly pluginId: string;
|
|
7
|
+
constructor(pluginId: string, cause: unknown);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A collected endpoint annotated with the plugin that contributed it.
|
|
11
|
+
*/
|
|
12
|
+
export interface ResolvedEndpoint extends PluginEndpoint {
|
|
13
|
+
pluginId: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Result of resolving a set of plugins. Contains the fully wired
|
|
17
|
+
* hook registry, collected endpoints, merged schema, and plugin IDs.
|
|
18
|
+
*/
|
|
19
|
+
export interface ResolvedPlugins<THooks extends HookContract> {
|
|
20
|
+
hookRegistry: HookRegistry<THooks>;
|
|
21
|
+
endpoints: ResolvedEndpoint[];
|
|
22
|
+
schema: MergedPluginSchema;
|
|
23
|
+
pluginIds: string[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Lifecycle orchestrator that products call during construction.
|
|
27
|
+
*
|
|
28
|
+
* Steps:
|
|
29
|
+
* 1. **Validate** — duplicate IDs throw immediately
|
|
30
|
+
* 2. **Register hooks** — into a new HookRegistry
|
|
31
|
+
* 3. **Collect endpoints** — flat array from all plugins
|
|
32
|
+
* 4. **Merge schemas** — calls mergeSchemas
|
|
33
|
+
* 5. **Init** — call plugin.init(ctx) sequentially in declaration order
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolvePlugins<THooks extends HookContract>(productId: string, plugins: PluginDefinition<THooks>[], config: Record<string, unknown>): Promise<ResolvedPlugins<THooks>>;
|
|
36
|
+
//# sourceMappingURL=resolve-plugins.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-plugins.d.ts","sourceRoot":"","sources":["../src/resolve-plugins.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,OAAO,KAAK,EACV,YAAY,EAEZ,gBAAgB,EAChB,cAAc,EACf,MAAM,YAAY,CAAC;AAEpB,qBAAa,eAAgB,SAAQ,KAAK;IACxC,SAAkB,IAAI,qBAAqB;IAC3C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAEd,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;CAK7C;AAED;;GAEG;AACH,MAAM,WAAW,gBAAiB,SAAQ,cAAc;IACtD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe,CAAC,MAAM,SAAS,YAAY;IAC1D,YAAY,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IACnC,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,MAAM,SAAS,YAAY,EAC9D,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,gBAAgB,CAAC,MAAM,CAAC,EAAE,EACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAgElC"}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { HookContract, PluginDefinition, PluginFieldDefinition, PluginTableDefinition } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Merged schema produced by combining all plugin schema declarations.
|
|
4
|
+
* Unlike per-plugin `PluginSchema` (where fields are optional),
|
|
5
|
+
* the merged result always contains both maps.
|
|
6
|
+
*/
|
|
7
|
+
export interface MergedPluginSchema {
|
|
8
|
+
tables: Record<string, PluginTableDefinition>;
|
|
9
|
+
extend: Record<string, Record<string, PluginFieldDefinition>>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Merges schema declarations from all plugins into a single result.
|
|
13
|
+
*
|
|
14
|
+
* Returns empty maps. Full merge logic (conflict detection, ordering)
|
|
15
|
+
* will be added when schema merging is needed.
|
|
16
|
+
*/
|
|
17
|
+
export declare function mergeSchemas<THooks extends HookContract>(_plugins: PluginDefinition<THooks>[]): MergedPluginSchema;
|
|
18
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EAChB,qBAAqB,EACrB,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAEpB;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;IAC9C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAC;CAC/D;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,SAAS,YAAY,EACtD,QAAQ,EAAE,gBAAgB,CAAC,MAAM,CAAC,EAAE,GACnC,kBAAkB,CAEpB"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asserts that a value is not `undefined`.
|
|
3
|
+
* Throws with a descriptive error message following the plugin config error pattern.
|
|
4
|
+
*/
|
|
5
|
+
export declare function assertDefined<T>(value: T | undefined, name: string): asserts value is T;
|
|
6
|
+
/**
|
|
7
|
+
* Asserts that a value is one of the allowed values.
|
|
8
|
+
* Throws with a descriptive error message listing the allowed options.
|
|
9
|
+
*/
|
|
10
|
+
export declare function assertOneOf<T extends string | number | boolean>(value: T, allowed: T[], name: string): void;
|
|
11
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,KAAK,EAAE,CAAC,GAAG,SAAS,EACpB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,KAAK,IAAI,CAAC,CAIpB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAO3G"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usebetterdev/plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"repository": "github:usebetter-dev/usebetter",
|
|
5
5
|
"bugs": "https://github.com/usebetter-dev/usebetter/issues",
|
|
6
6
|
"homepage": "https://github.com/usebetter-dev/usebetter#readme",
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"node": ">=22"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
|
-
"build": "tsup && tsc
|
|
36
|
+
"build": "tsup && tsc --build tsconfig.build.json --force",
|
|
37
|
+
"build:types": "tsc --build tsconfig.build.json",
|
|
37
38
|
"lint": "oxlint",
|
|
38
39
|
"test": "vitest run",
|
|
39
40
|
"typecheck": "tsc --noEmit"
|