@moku-labs/core 0.1.0-alpha.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 +315 -0
- package/dist/index.cjs +715 -0
- package/dist/index.d.cts +900 -0
- package/dist/index.d.mts +900 -0
- package/dist/index.mjs +713 -0
- package/package.json +85 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,900 @@
|
|
|
1
|
+
//#region src/utilities.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Base constraint for framework configuration objects.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* function createConfig<C extends FrameworkConfig>(defaults: C): C { return defaults; }
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
type FrameworkConfig = Record<string, unknown>;
|
|
11
|
+
/**
|
|
12
|
+
* Base constraint for framework event maps.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* function createEvents<E extends FrameworkEventMap>(events: E): E { return events; }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
type FrameworkEventMap = Record<string, unknown>;
|
|
20
|
+
/**
|
|
21
|
+
* Empty event map used as the default when a plugin declares no custom events.
|
|
22
|
+
* `Record<never, never>` is the identity element for intersection (`T & {} = T`).
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* type PluginEvents = EmptyPluginEventMap; // default when no events declared
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
type EmptyPluginEventMap = Record<never, never>;
|
|
30
|
+
/**
|
|
31
|
+
* Convert a union to an intersection via distributive conditional + contra-variance.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* type Result = UnionToIntersection<{ a: 1 } | { b: 2 }>; // { a: 1 } & { b: 2 }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
|
|
39
|
+
/**
|
|
40
|
+
* Detect if a string type is a literal (e.g. "router") vs the general `string` type.
|
|
41
|
+
* Used by BuildPluginApis to exclude plugins with non-literal names from the mapped type.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* type Yes = IsLiteralString<"router">; // true
|
|
46
|
+
* type No = IsLiteralString<string>; // false
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
type IsLiteralString<S extends string> = string extends S ? false : true;
|
|
50
|
+
/**
|
|
51
|
+
* Auto-generate overloaded emit call signatures from an event map type.
|
|
52
|
+
*
|
|
53
|
+
* Converts `{ "a": X; "b": Y }` into `((name: "a", payload: X) => void) & ((name: "b", payload: Y) => void)`.
|
|
54
|
+
* Uses `UnionToIntersection` to transform a union of per-event functions into
|
|
55
|
+
* an intersection (overloaded call signatures).
|
|
56
|
+
*
|
|
57
|
+
* When `E` has no keys, produces an uncallable but constructible function type
|
|
58
|
+
* so `vi.fn()` and `() => {}` remain assignable while calling emit is a compile error.
|
|
59
|
+
*
|
|
60
|
+
* Exported from the package entry point for plugin authors at Standard+ tier.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* type CmsEmit = EmitFn<{
|
|
65
|
+
* "cms:publish": { contentId: string; path: string };
|
|
66
|
+
* "cms:draft": { contentId: string };
|
|
67
|
+
* }>;
|
|
68
|
+
* // Equivalent to:
|
|
69
|
+
* // ((name: "cms:publish", payload: { contentId: string; path: string }) => void)
|
|
70
|
+
* // & ((name: "cms:draft", payload: { contentId: string }) => void)
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
type EmitFunction<E extends Record<string, unknown>> = [keyof E] extends [never] ? (...arguments_: never[]) => void : UnionToIntersection<{ [K in string & keyof E]: (name: K, payload: E[K]) => void }[string & keyof E]>;
|
|
74
|
+
/**
|
|
75
|
+
* Checks whether a value is a non-null, non-array object record.
|
|
76
|
+
*
|
|
77
|
+
* @param value - Value to inspect.
|
|
78
|
+
* @returns `true` when value is an object record.
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* isRecord({ key: "value" }); // => true
|
|
82
|
+
* isRecord([1, 2, 3]); // => false
|
|
83
|
+
* isRecord(null); // => false
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/types.d.ts
|
|
88
|
+
/**
|
|
89
|
+
* Teardown context — the most minimal context tier.
|
|
90
|
+
* Used by: onStop
|
|
91
|
+
*
|
|
92
|
+
* During teardown, plugins may be partially or fully stopped. Only the frozen
|
|
93
|
+
* global config is available.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* type StopCtx = TeardownContext<{ siteName: string }>;
|
|
98
|
+
* // => { readonly global: Readonly<{ siteName: string }> }
|
|
99
|
+
*
|
|
100
|
+
* // Used in plugin spec:
|
|
101
|
+
* onStop: (ctx: StopCtx) => { console.log(`Stopping ${ctx.global.siteName}`); }
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
type TeardownContext<Config> = {
|
|
105
|
+
readonly global: Readonly<Config>;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Minimal context — teardown context plus plugin config.
|
|
109
|
+
* Used by: createState
|
|
110
|
+
*
|
|
111
|
+
* At this stage, not all plugins have been created yet. Communication methods
|
|
112
|
+
* (emit, require, has) are intentionally unavailable.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* type StateCtx = MinimalContext<{ siteName: string }, { basePath: string }>;
|
|
117
|
+
* // => { readonly global: ...; readonly config: Readonly<{ basePath: string }> }
|
|
118
|
+
*
|
|
119
|
+
* // Used in plugin spec:
|
|
120
|
+
* createState: (ctx: StateCtx) => ({ currentPath: ctx.config.basePath })
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
type MinimalContext<Config, C> = {
|
|
124
|
+
readonly global: Readonly<Config>;
|
|
125
|
+
readonly config: Readonly<C>;
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* Full plugin context — everything is live.
|
|
129
|
+
* Used by: api, onInit, onStart
|
|
130
|
+
*
|
|
131
|
+
* Provides global config, plugin config, mutable state, event emission,
|
|
132
|
+
* and inter-plugin communication.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* type Ctx = PluginContext<
|
|
137
|
+
* { siteName: string },
|
|
138
|
+
* { "page:view": { path: string } },
|
|
139
|
+
* { basePath: string },
|
|
140
|
+
* { count: number }
|
|
141
|
+
* >;
|
|
142
|
+
* // => { global, config, state, emit, require, has }
|
|
143
|
+
*
|
|
144
|
+
* // Used in plugin spec:
|
|
145
|
+
* api: (ctx: Ctx) => ({
|
|
146
|
+
* navigate: (path: string) => { ctx.state.count += 1; ctx.emit("page:view", { path }); }
|
|
147
|
+
* })
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
type PluginContext<Config, Events extends Record<string, unknown>, C, S> = {
|
|
151
|
+
readonly global: Readonly<Config>;
|
|
152
|
+
readonly config: Readonly<C>;
|
|
153
|
+
state: S;
|
|
154
|
+
emit: EmitFunction$1<Events>;
|
|
155
|
+
require: RequireFunction;
|
|
156
|
+
has: HasFunction;
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Strictly typed emit function (kernel-layer generic signature).
|
|
160
|
+
* Only known event names are accepted, with matching payload required.
|
|
161
|
+
*
|
|
162
|
+
* This is the compile-time generic signature used by PluginContext and App.
|
|
163
|
+
* app.ts defines a separate runtime-layer `EmitFunction` alias without generics
|
|
164
|
+
* for dynamically typed dispatch — they are intentionally different.
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```ts
|
|
168
|
+
* type Emit = EmitFunction<{ "page:view": { path: string }; "auth:login": { userId: string } }>;
|
|
169
|
+
*
|
|
170
|
+
* declare const emit: Emit;
|
|
171
|
+
* emit("page:view", { path: "/" }); // OK — known event with correct payload
|
|
172
|
+
* // emit("page:view", { url: "/" }); // Error — wrong payload shape
|
|
173
|
+
* // emit("unknown", {}); // Error — unknown event name
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
type EmitFunction$1<Events extends Record<string, unknown>> = <K extends string & keyof Events>(name: K, payload: Events[K]) => void;
|
|
177
|
+
/**
|
|
178
|
+
* Get a dependency plugin's API by instance reference.
|
|
179
|
+
* Accepts only PluginInstance values (not strings). Returns the fully typed API
|
|
180
|
+
* extracted from the phantom type, or throws at runtime if not registered.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```ts
|
|
184
|
+
* declare const require: RequireFunction;
|
|
185
|
+
* const api = require(routerPlugin); // => typeof routerPlugin's API
|
|
186
|
+
* // api.navigate("/about"); // fully typed — method comes from phantom type
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
type RequireFunction = <P extends PluginInstance<string, any, any, any, any>>(plugin: P) => ExtractApi<P>;
|
|
190
|
+
/**
|
|
191
|
+
* Check if a plugin is registered by name. String-based boolean check.
|
|
192
|
+
* Unlike require, this accepts a plain string and returns a boolean instead of throwing.
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* declare const has: HasFunction;
|
|
197
|
+
* if (has("analytics")) {
|
|
198
|
+
* // analytics plugin is registered — safe to require
|
|
199
|
+
* }
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
type HasFunction = (name: string) => boolean;
|
|
203
|
+
/**
|
|
204
|
+
* Plugin specification — the shape passed to createPlugin.
|
|
205
|
+
*
|
|
206
|
+
* All generics (N, C, S, A) are inferred from the spec object values.
|
|
207
|
+
* Config and Events flow from the createCoreConfig closure.
|
|
208
|
+
* PluginEvents is the only explicit generic (defines new events).
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```ts
|
|
212
|
+
* // Rarely written explicitly — inferred from createPlugin spec object.
|
|
213
|
+
* // The kernel uses this type internally to constrain spec shapes:
|
|
214
|
+
* type RouterSpec = PluginSpec<SiteConfig, SiteEvents, RouterEvents,
|
|
215
|
+
* { basePath: string }, { currentPath: string },
|
|
216
|
+
* { navigate(path: string): void }, readonly []>;
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
type PluginSpec<Config, Events extends Record<string, unknown>, PluginEvents extends Record<string, unknown>, C, S, A extends Record<string, any>, Deps extends ReadonlyArray<PluginInstance<string, any, any, any, any>>> = {
|
|
220
|
+
config?: C;
|
|
221
|
+
depends?: Deps;
|
|
222
|
+
createState?: (context: MinimalContext<Config, C>) => S;
|
|
223
|
+
api?: (context: PluginContext<Config, Events & PluginEvents & DepsEvents<Deps>, C, S>) => A;
|
|
224
|
+
onInit?: (context: PluginContext<Config, Events & PluginEvents & DepsEvents<Deps>, C, S>) => void | Promise<void>;
|
|
225
|
+
onStart?: (context: PluginContext<Config, Events & PluginEvents & DepsEvents<Deps>, C, S>) => void | Promise<void>;
|
|
226
|
+
onStop?: (context: TeardownContext<Config>) => void | Promise<void>;
|
|
227
|
+
hooks?: (context: PluginContext<Config, Events & PluginEvents & DepsEvents<Deps>, C, S>) => { [K in string & keyof (Events & PluginEvents & DepsEvents<Deps>)]?: (payload: (Events & PluginEvents & DepsEvents<Deps>)[K]) => void | Promise<void> };
|
|
228
|
+
};
|
|
229
|
+
/**
|
|
230
|
+
* Plugin instance — the return value of createPlugin.
|
|
231
|
+
*
|
|
232
|
+
* Carries phantom types for compile-time type inference. The _phantom field
|
|
233
|
+
* is never read at runtime (it is `{} as { ... }`).
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* ```ts
|
|
237
|
+
* // Created via createPlugin — types are inferred, not written:
|
|
238
|
+
* const router = createPlugin("router", {
|
|
239
|
+
* config: { basePath: "/" },
|
|
240
|
+
* api: ctx => ({ navigate: (path: string) => path }),
|
|
241
|
+
* });
|
|
242
|
+
* // router: PluginInstance<"router", { basePath: string }, ..., { navigate(path: string): string }>
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
interface PluginInstance<N extends string = string, C = void, S = void, A extends Record<string, any> = Record<string, never>, PluginEvents extends Record<string, unknown> = {}> {
|
|
246
|
+
readonly name: N;
|
|
247
|
+
readonly spec: PluginSpec<any, any, any, C, S, A, any>;
|
|
248
|
+
readonly _phantom: {
|
|
249
|
+
config: C;
|
|
250
|
+
state: S;
|
|
251
|
+
api: A;
|
|
252
|
+
events: PluginEvents;
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Widened PluginInstance type for generic constraints on arrays.
|
|
257
|
+
* Used across multiple modules (utilities, core, app) for plugin list parameters.
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```ts
|
|
261
|
+
* function processPlugins(plugins: AnyPluginInstance[]): void { ... }
|
|
262
|
+
* ```
|
|
263
|
+
*/
|
|
264
|
+
type AnyPluginInstance = PluginInstance<string, any, any, any, any>;
|
|
265
|
+
/**
|
|
266
|
+
* Extract the API phantom type from a PluginInstance.
|
|
267
|
+
* Used by RequireFunction to return the correct API type from require(plugin).
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```ts
|
|
271
|
+
* type RouterApi = ExtractApi<typeof routerPlugin>; // { navigate(path: string): void }
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
type ExtractApi<P> = P extends PluginInstance<string, any, any, infer A, any> ? A : never;
|
|
275
|
+
/**
|
|
276
|
+
* Extract the events phantom type from a PluginInstance.
|
|
277
|
+
* Used by DepsEvents to merge event maps from dependency plugins.
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* ```ts
|
|
281
|
+
* type RouterEvents = ExtractEvents<typeof routerPlugin>;
|
|
282
|
+
* // { "router:navigate": { from: string; to: string } }
|
|
283
|
+
* ```
|
|
284
|
+
*/
|
|
285
|
+
type ExtractEvents<P> = P extends PluginInstance<string, any, any, any, infer E> ? E : never;
|
|
286
|
+
/**
|
|
287
|
+
* Extract the name literal type from a PluginInstance.
|
|
288
|
+
* Used by BuildPluginApis to key the app surface by plugin name.
|
|
289
|
+
*
|
|
290
|
+
* @example
|
|
291
|
+
* ```ts
|
|
292
|
+
* type Name = ExtractName<typeof routerPlugin>; // "router"
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
type ExtractName<P> = P extends PluginInstance<infer N, any, any, any, any> ? N : never;
|
|
296
|
+
/**
|
|
297
|
+
* Extract the config phantom type from a PluginInstance.
|
|
298
|
+
* Used by CreateAppOptions to type pluginConfigs keys.
|
|
299
|
+
*
|
|
300
|
+
* @example
|
|
301
|
+
* ```ts
|
|
302
|
+
* type RouterConfig = ExtractConfig<typeof routerPlugin>; // { basePath: string }
|
|
303
|
+
* ```
|
|
304
|
+
*/
|
|
305
|
+
type ExtractConfig<P> = P extends PluginInstance<string, infer C, any, any, any> ? C : never;
|
|
306
|
+
/**
|
|
307
|
+
* Intersection of all PluginEvents from a depends tuple.
|
|
308
|
+
* Merges events from [authPlugin, routerPlugin] into AuthEvents & RouterEvents.
|
|
309
|
+
* Falls back to `{}` (identity element) when the tuple is empty.
|
|
310
|
+
*
|
|
311
|
+
* @example
|
|
312
|
+
* ```ts
|
|
313
|
+
* type Combined = DepsEvents<readonly [typeof authPlugin, typeof routerPlugin]>;
|
|
314
|
+
* // => { "auth:login": { userId: string } } & { "router:navigate": { path: string } }
|
|
315
|
+
* ```
|
|
316
|
+
*/
|
|
317
|
+
type DepsEvents<Deps extends ReadonlyArray<PluginInstance<string, any, any, any, any>>> = Deps[number] extends never ? {} : UnionToIntersection<ExtractEvents<Deps[number]>>;
|
|
318
|
+
/**
|
|
319
|
+
* Map a plugin tuple to `{ [Name]: Api }` for the app surface.
|
|
320
|
+
* Plugins with empty API (Record<string, never>) are excluded.
|
|
321
|
+
* Plugins with non-literal name type (string) are excluded to prevent
|
|
322
|
+
* index signature pollution on the App type.
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* ```ts
|
|
326
|
+
* type Apis = BuildPluginApis<typeof routerPlugin | typeof authPlugin>;
|
|
327
|
+
* // => { router: { navigate(path: string): void }; auth: { login(): void } }
|
|
328
|
+
* ```
|
|
329
|
+
*/
|
|
330
|
+
type BuildPluginApis<P extends PluginInstance<string, any, any, any, any>> = { [K in P as ExtractApi<K> extends Record<string, never> ? never : IsLiteralString<ExtractName<K>> extends true ? ExtractName<K> : never]: ExtractApi<K> };
|
|
331
|
+
/**
|
|
332
|
+
* Typed App object returned by createApp.
|
|
333
|
+
* Combines base methods (start, stop, emit, require, has) with plugin APIs
|
|
334
|
+
* mapped by name via BuildPluginApis.
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* ```ts
|
|
338
|
+
* type MyApp = App<SiteConfig, SiteEvents, typeof routerPlugin | typeof authPlugin>;
|
|
339
|
+
* // => { start, stop, emit, require, has, router: RouterApi, auth: AuthApi }
|
|
340
|
+
*
|
|
341
|
+
* declare const app: MyApp;
|
|
342
|
+
* await app.start();
|
|
343
|
+
* app.router.navigate("/about");
|
|
344
|
+
* ```
|
|
345
|
+
*/
|
|
346
|
+
type App<_Config extends Record<string, unknown>, Events extends Record<string, unknown>, P extends PluginInstance<string, any, any, any, any>> = {
|
|
347
|
+
readonly start: () => Promise<void>;
|
|
348
|
+
readonly stop: () => Promise<void>;
|
|
349
|
+
readonly emit: EmitFunction$1<Events>;
|
|
350
|
+
readonly require: RequireFunction;
|
|
351
|
+
readonly has: HasFunction;
|
|
352
|
+
} & BuildPluginApis<P>;
|
|
353
|
+
/**
|
|
354
|
+
* Context passed to consumer lifecycle callbacks (onReady, onStart, onStop).
|
|
355
|
+
* Includes frozen config, event emission, plugin lookup, and mounted plugin APIs.
|
|
356
|
+
*
|
|
357
|
+
* @example
|
|
358
|
+
* ```ts
|
|
359
|
+
* type Ctx = AppCallbackContext<SiteConfig, SiteEvents, typeof routerPlugin>;
|
|
360
|
+
* // => { config, emit, require, has, router: RouterApi }
|
|
361
|
+
*
|
|
362
|
+
* // Used in createApp options:
|
|
363
|
+
* onReady: (ctx: Ctx) => { ctx.router.navigate("/home"); }
|
|
364
|
+
* ```
|
|
365
|
+
*/
|
|
366
|
+
type AppCallbackContext<Config extends Record<string, unknown>, Events extends Record<string, unknown>, P extends PluginInstance<string, any, any, any, any>> = {
|
|
367
|
+
readonly config: Readonly<Config>;
|
|
368
|
+
readonly emit: EmitFunction$1<Events>;
|
|
369
|
+
readonly require: RequireFunction;
|
|
370
|
+
readonly has: HasFunction;
|
|
371
|
+
} & BuildPluginApis<P>;
|
|
372
|
+
/**
|
|
373
|
+
* Options for createApp (Step 3). Structured namespaces replace flat key discrimination:
|
|
374
|
+
* - `plugins`: extra consumer plugins
|
|
375
|
+
* - `config`: global config overrides (shallow-merged with framework defaults)
|
|
376
|
+
* - `pluginConfigs`: per-plugin config overrides keyed by plugin name
|
|
377
|
+
* - `onReady/onError/onStart/onStop`: consumer lifecycle callbacks
|
|
378
|
+
*
|
|
379
|
+
* @example
|
|
380
|
+
* ```ts
|
|
381
|
+
* const app = await createApp({
|
|
382
|
+
* config: { siteName: "My Blog" },
|
|
383
|
+
* pluginConfigs: { router: { basePath: "/blog" } },
|
|
384
|
+
* onReady: ctx => { console.log("App ready:", ctx.config.siteName); },
|
|
385
|
+
* });
|
|
386
|
+
* ```
|
|
387
|
+
*/
|
|
388
|
+
type CreateAppOptions<Config extends Record<string, unknown>, Events extends Record<string, unknown>, P extends PluginInstance<string, any, any, any, any>, ExtraPlugins extends readonly PluginInstance<string, any, any, any, any>[]> = {
|
|
389
|
+
plugins?: ExtraPlugins;
|
|
390
|
+
config?: { [K in keyof Config]?: Config[K] };
|
|
391
|
+
pluginConfigs?: { [K in P as ExtractConfig<K> extends Record<string, never> ? never : IsLiteralString<ExtractName<K>> extends true ? ExtractName<K> : never]?: Partial<ExtractConfig<K>> };
|
|
392
|
+
onReady?: (context: AppCallbackContext<Config, Events, P>) => void | Promise<void>;
|
|
393
|
+
onError?: (error: Error, context: AppCallbackContext<Config, Events, P>) => void;
|
|
394
|
+
onStart?: (context: AppCallbackContext<Config, Events, P>) => void | Promise<void>;
|
|
395
|
+
onStop?: (context: AppCallbackContext<Config, Events, P>) => void | Promise<void>;
|
|
396
|
+
};
|
|
397
|
+
/**
|
|
398
|
+
* Domain context type for extracted plugin files (api.ts, handlers.ts, etc.).
|
|
399
|
+
*
|
|
400
|
+
* Provides `config`, `state`, and strictly typed `emit` with overloaded call
|
|
401
|
+
* signatures auto-generated from the event map `E`. Replaces manual overload
|
|
402
|
+
* declarations that must be kept in sync with the event type.
|
|
403
|
+
*
|
|
404
|
+
* For plugins without events, omit `E` — emit becomes uncallable.
|
|
405
|
+
* For advanced composition (e.g., adding `require`), use `EmitFn<E>` directly.
|
|
406
|
+
*
|
|
407
|
+
* @example
|
|
408
|
+
* ```ts
|
|
409
|
+
* import type { PluginCtx } from "@moku-labs/core";
|
|
410
|
+
*
|
|
411
|
+
* type CmsEvents = {
|
|
412
|
+
* "cms:publish": { contentId: string; path: string };
|
|
413
|
+
* "cms:draft": { contentId: string };
|
|
414
|
+
* };
|
|
415
|
+
*
|
|
416
|
+
* // One line replaces manual emit overloads:
|
|
417
|
+
* type CmsCtx = PluginCtx<CmsConfig, CmsState, CmsEvents>;
|
|
418
|
+
*
|
|
419
|
+
* // Use in domain factories:
|
|
420
|
+
* export const createCmsApi = (ctx: CmsCtx) => ({
|
|
421
|
+
* publish: (id: string, path: string) => {
|
|
422
|
+
* ctx.emit("cms:publish", { contentId: id, path }); // strictly typed
|
|
423
|
+
* },
|
|
424
|
+
* });
|
|
425
|
+
* ```
|
|
426
|
+
*/
|
|
427
|
+
type PluginContext_<C, S, E extends Record<string, unknown> = Record<never, never>> = {
|
|
428
|
+
readonly config: Readonly<C>;
|
|
429
|
+
state: S;
|
|
430
|
+
emit: EmitFunction<E>;
|
|
431
|
+
};
|
|
432
|
+
//#endregion
|
|
433
|
+
//#region src/plugin.d.ts
|
|
434
|
+
/**
|
|
435
|
+
* Structural plugin shape used for generic constraints without variance issues.
|
|
436
|
+
*
|
|
437
|
+
* @example
|
|
438
|
+
* ```ts
|
|
439
|
+
* const pluginRef: PluginLike = somePlugin;
|
|
440
|
+
* ```
|
|
441
|
+
*/
|
|
442
|
+
type PluginLike = {
|
|
443
|
+
readonly name: string;
|
|
444
|
+
readonly spec: unknown;
|
|
445
|
+
readonly _phantom: {
|
|
446
|
+
readonly config: unknown;
|
|
447
|
+
readonly state: unknown;
|
|
448
|
+
readonly api: unknown;
|
|
449
|
+
readonly events: Record<string, unknown>;
|
|
450
|
+
};
|
|
451
|
+
};
|
|
452
|
+
/**
|
|
453
|
+
* Readonly dependency tuple accepted by `depends`.
|
|
454
|
+
*
|
|
455
|
+
* @example
|
|
456
|
+
* ```ts
|
|
457
|
+
* const deps: DependencyPluginTuple = [];
|
|
458
|
+
* ```
|
|
459
|
+
*/
|
|
460
|
+
type DependencyPluginTuple = readonly PluginLike[];
|
|
461
|
+
/**
|
|
462
|
+
* Extracts API type from a plugin-like value.
|
|
463
|
+
*
|
|
464
|
+
* @example
|
|
465
|
+
* ```ts
|
|
466
|
+
* type Api = ExtractPluginApi<typeof somePlugin>;
|
|
467
|
+
* ```
|
|
468
|
+
*/
|
|
469
|
+
type ExtractPluginApi<PluginCandidate> = PluginCandidate extends {
|
|
470
|
+
readonly _phantom: {
|
|
471
|
+
readonly api: infer PluginApi;
|
|
472
|
+
};
|
|
473
|
+
} ? PluginApi : never;
|
|
474
|
+
/**
|
|
475
|
+
* Extracts event map type from a plugin-like value.
|
|
476
|
+
*
|
|
477
|
+
* @example
|
|
478
|
+
* ```ts
|
|
479
|
+
* type Events = ExtractPluginEvents<typeof somePlugin>;
|
|
480
|
+
* ```
|
|
481
|
+
*/
|
|
482
|
+
type ExtractPluginEvents<PluginCandidate> = PluginCandidate extends {
|
|
483
|
+
readonly _phantom: {
|
|
484
|
+
readonly events: infer PluginEvents;
|
|
485
|
+
};
|
|
486
|
+
} ? PluginEvents extends Record<string, unknown> ? PluginEvents : EmptyPluginEventMap : EmptyPluginEventMap;
|
|
487
|
+
/**
|
|
488
|
+
* Event map contributed by all dependency plugins.
|
|
489
|
+
*
|
|
490
|
+
* @example
|
|
491
|
+
* ```ts
|
|
492
|
+
* type DepEvents = DependencyEvents<readonly [authPlugin, routerPlugin]>;
|
|
493
|
+
* ```
|
|
494
|
+
*/
|
|
495
|
+
type DependencyEvents<DependencyPlugins extends DependencyPluginTuple> = DependencyPlugins[number] extends never ? EmptyPluginEventMap : UnionToIntersection<ExtractPluginEvents<DependencyPlugins[number]>>;
|
|
496
|
+
/**
|
|
497
|
+
* Intersection of framework events, plugin events, and dependency events.
|
|
498
|
+
*
|
|
499
|
+
* @example
|
|
500
|
+
* ```ts
|
|
501
|
+
* type AllEvents = MergedPluginEvents<SiteConfigEvents, RouterEvents, readonly [authPlugin]>;
|
|
502
|
+
* ```
|
|
503
|
+
*/
|
|
504
|
+
type MergedPluginEvents<GlobalEventMap extends FrameworkEventMap, PluginEventMap extends Record<string, unknown>, DependencyPlugins extends DependencyPluginTuple> = GlobalEventMap & PluginEventMap & DependencyEvents<DependencyPlugins>;
|
|
505
|
+
/**
|
|
506
|
+
* Runtime context for `api`, `onInit`, and `onStart`.
|
|
507
|
+
*
|
|
508
|
+
* Generic parameters:
|
|
509
|
+
* - `GlobalConfig`: frozen app-wide config from `createCoreConfig`
|
|
510
|
+
* - `AllEvents`: merged events the plugin can emit
|
|
511
|
+
* - `PluginConfig`: this plugin's config slice
|
|
512
|
+
* - `PluginState`: mutable plugin state
|
|
513
|
+
*
|
|
514
|
+
* @example
|
|
515
|
+
* ```ts
|
|
516
|
+
* type Ctx = PluginExecutionContext<
|
|
517
|
+
* SiteConfig,
|
|
518
|
+
* SiteEvents,
|
|
519
|
+
* { basePath: string },
|
|
520
|
+
* { currentPath: string }
|
|
521
|
+
* >;
|
|
522
|
+
* ```
|
|
523
|
+
*/
|
|
524
|
+
type PluginExecutionContext<GlobalConfig extends FrameworkConfig, AllEvents extends Record<string, unknown>, PluginConfig, PluginState> = {
|
|
525
|
+
/**
|
|
526
|
+
* Frozen app-wide config from `createCoreConfig`. Shared by all plugins.
|
|
527
|
+
*
|
|
528
|
+
* @example
|
|
529
|
+
* ```ts
|
|
530
|
+
* const siteUrl = ctx.global.siteUrl;
|
|
531
|
+
* ```
|
|
532
|
+
*/
|
|
533
|
+
readonly global: Readonly<GlobalConfig>;
|
|
534
|
+
/**
|
|
535
|
+
* Frozen plugin-specific config. Defaults from the plugin spec, shallow-merged
|
|
536
|
+
* with consumer overrides.
|
|
537
|
+
*
|
|
538
|
+
* @example
|
|
539
|
+
* ```ts
|
|
540
|
+
* const base = ctx.config.basePath; // from plugin defaults or consumer override
|
|
541
|
+
* ```
|
|
542
|
+
*/
|
|
543
|
+
readonly config: Readonly<PluginConfig>;
|
|
544
|
+
/**
|
|
545
|
+
* Mutable plugin state created by `createState`. The only mutable store in the system.
|
|
546
|
+
*
|
|
547
|
+
* @example
|
|
548
|
+
* ```ts
|
|
549
|
+
* ctx.state.count += 1;
|
|
550
|
+
* ctx.state.cache.set(key, value);
|
|
551
|
+
* ```
|
|
552
|
+
*/
|
|
553
|
+
state: PluginState;
|
|
554
|
+
/**
|
|
555
|
+
* Dispatch a typed event. Only known event names are accepted (no escape hatch).
|
|
556
|
+
*
|
|
557
|
+
* @example
|
|
558
|
+
* ```ts
|
|
559
|
+
* ctx.emit("auth:login", { userId: "123" });
|
|
560
|
+
* ```
|
|
561
|
+
*/
|
|
562
|
+
emit: EmitFunction$1<AllEvents>;
|
|
563
|
+
/**
|
|
564
|
+
* Get a registered plugin's API by instance reference. Throws if the plugin is not registered.
|
|
565
|
+
* Accepts any plugin instance, not just declared dependencies (not strings).
|
|
566
|
+
*
|
|
567
|
+
* @example
|
|
568
|
+
* ```ts
|
|
569
|
+
* const http = ctx.require(httpPlugin);
|
|
570
|
+
* http.use(authMiddleware);
|
|
571
|
+
* ```
|
|
572
|
+
*/
|
|
573
|
+
require: <PluginCandidate extends PluginLike>(plugin: PluginCandidate) => ExtractPluginApi<PluginCandidate>;
|
|
574
|
+
/**
|
|
575
|
+
* Check if a plugin is registered by name. String-based (boolean check only).
|
|
576
|
+
*
|
|
577
|
+
* @example
|
|
578
|
+
* ```ts
|
|
579
|
+
* if (ctx.has("analytics")) { ... }
|
|
580
|
+
* ```
|
|
581
|
+
*/
|
|
582
|
+
has: (name: string) => boolean;
|
|
583
|
+
};
|
|
584
|
+
/**
|
|
585
|
+
* Descriptor returned by register(). Carries the payload type.
|
|
586
|
+
* and an optional description string for runtime event catalogs.
|
|
587
|
+
*/
|
|
588
|
+
type EventDescriptor<PayloadType = unknown> = {
|
|
589
|
+
readonly description: string; /** Phantom field — carries `PayloadType` for inference. Never set at runtime. */
|
|
590
|
+
readonly _type?: PayloadType;
|
|
591
|
+
};
|
|
592
|
+
/**
|
|
593
|
+
* The register function passed to the events callback.
|
|
594
|
+
* Two usage modes:
|
|
595
|
+
* - `register<T>(description?)` — register a single event with payload type `T`.
|
|
596
|
+
* - `register.map<EventMap>(descriptions?)` — bulk-register all events from a
|
|
597
|
+
* pre-declared event map type. Eliminates per-event `register<Events["name"]>()` calls.
|
|
598
|
+
*
|
|
599
|
+
* @example
|
|
600
|
+
* ```ts
|
|
601
|
+
* // Individual registration (inline event types):
|
|
602
|
+
* events: register => ({
|
|
603
|
+
* "auth:login": register<{ userId: string }>("Triggered after login"),
|
|
604
|
+
* })
|
|
605
|
+
*
|
|
606
|
+
* // Bulk registration (Standard+ plugins with separate XxxEvents type):
|
|
607
|
+
* events: register => register.map<AuthEvents>({
|
|
608
|
+
* "auth:login": "Triggered after login",
|
|
609
|
+
* "auth:logout": "Triggered after logout",
|
|
610
|
+
* })
|
|
611
|
+
* ```
|
|
612
|
+
*/
|
|
613
|
+
type RegisterFunction = {
|
|
614
|
+
<PayloadType>(description?: string): EventDescriptor<PayloadType>;
|
|
615
|
+
map: <EventMap extends Record<string, unknown>>(descriptions?: { [K in keyof EventMap]?: string }) => { [K in keyof EventMap]: EventDescriptor<EventMap[K]> };
|
|
616
|
+
};
|
|
617
|
+
/**
|
|
618
|
+
* Specification object passed to `createPlugin`.
|
|
619
|
+
*
|
|
620
|
+
* Generic parameters:
|
|
621
|
+
* - `GlobalConfig`: app-wide config object
|
|
622
|
+
* - `GlobalEventMap`: app-wide events from `createCoreConfig`
|
|
623
|
+
* - `PluginEventMap`: plugin-specific events declared by `events`
|
|
624
|
+
* - `PluginConfig`: plugin config shape from `config`
|
|
625
|
+
* - `PluginState`: mutable state returned by `createState`
|
|
626
|
+
* - `PluginApi`: API returned by `api`
|
|
627
|
+
* - `DependencyPlugins`: tuple from `depends`
|
|
628
|
+
*
|
|
629
|
+
* @example
|
|
630
|
+
* ```ts
|
|
631
|
+
* type RouterSpec = CreatePluginSpec<
|
|
632
|
+
* SiteConfig,
|
|
633
|
+
* SiteEvents,
|
|
634
|
+
* Record<never, never>,
|
|
635
|
+
* { basePath: string },
|
|
636
|
+
* { currentPath: string },
|
|
637
|
+
* { navigate(path: string): void },
|
|
638
|
+
* readonly []
|
|
639
|
+
* >;
|
|
640
|
+
* ```
|
|
641
|
+
*/
|
|
642
|
+
type CreatePluginSpec<GlobalConfig extends FrameworkConfig, GlobalEventMap extends FrameworkEventMap, PluginEventMap extends Record<string, unknown>, PluginConfig extends Record<string, unknown>, PluginState, PluginApi extends Record<string, unknown>, DependencyPlugins extends DependencyPluginTuple> = {
|
|
643
|
+
/**
|
|
644
|
+
* Declare plugin-specific events via a register callback.
|
|
645
|
+
* Used for compile-time type inference only — the kernel does not call this at runtime.
|
|
646
|
+
*
|
|
647
|
+
* @example
|
|
648
|
+
* ```ts
|
|
649
|
+
* events: (register) => ({
|
|
650
|
+
* "auth:login": register<{ userId: string }>("Triggered after user login"),
|
|
651
|
+
* "auth:logout": register<{ userId: string }>("Triggered after user logout"),
|
|
652
|
+
* })
|
|
653
|
+
* ```
|
|
654
|
+
*/
|
|
655
|
+
events?: (register: RegisterFunction) => { [EventName in keyof PluginEventMap]: EventDescriptor<PluginEventMap[EventName]> };
|
|
656
|
+
/**
|
|
657
|
+
* Default config values for this plugin. Consumers can override via `pluginName: { ... }`.
|
|
658
|
+
* Shallow-merged at startup, then frozen.
|
|
659
|
+
*
|
|
660
|
+
* @example
|
|
661
|
+
* ```ts
|
|
662
|
+
* config: { basePath: "/", trailing: false }
|
|
663
|
+
* ```
|
|
664
|
+
*/
|
|
665
|
+
config?: PluginConfig;
|
|
666
|
+
/**
|
|
667
|
+
* Plugins this plugin depends on. Dependency APIs are available via `ctx.require()`.
|
|
668
|
+
* Dependencies must appear earlier in the plugins array (no topological sort).
|
|
669
|
+
*
|
|
670
|
+
* @example
|
|
671
|
+
* ```ts
|
|
672
|
+
* depends: [authPlugin, httpPlugin]
|
|
673
|
+
* ```
|
|
674
|
+
*/
|
|
675
|
+
depends?: DependencyPlugins;
|
|
676
|
+
/**
|
|
677
|
+
* Factory for mutable plugin state. Called once at startup with a minimal context
|
|
678
|
+
* (global config + plugin config). The returned object is the only mutable store.
|
|
679
|
+
*
|
|
680
|
+
* @example
|
|
681
|
+
* ```ts
|
|
682
|
+
* createState: (ctx) => ({ count: 0, cache: new Map() })
|
|
683
|
+
* ```
|
|
684
|
+
*/
|
|
685
|
+
createState?: (context: MinimalContext<GlobalConfig, PluginConfig>) => PluginState;
|
|
686
|
+
/**
|
|
687
|
+
* Public API factory. Receives full plugin context; returns the API object
|
|
688
|
+
* other plugins access via `ctx.require(thisPlugin)`.
|
|
689
|
+
* Must be synchronous and side-effect-free — do not call `ctx.emit()` from this factory.
|
|
690
|
+
*
|
|
691
|
+
* @example
|
|
692
|
+
* ```ts
|
|
693
|
+
* api: (ctx) => ({
|
|
694
|
+
* navigate: (path: string) => { ctx.state.currentPath = path; },
|
|
695
|
+
* getCurrentPath: () => ctx.state.currentPath,
|
|
696
|
+
* })
|
|
697
|
+
* ```
|
|
698
|
+
*/
|
|
699
|
+
api?: (context: PluginExecutionContext<GlobalConfig, MergedPluginEvents<GlobalEventMap, PluginEventMap, DependencyPlugins>, PluginConfig, PluginState>) => PluginApi;
|
|
700
|
+
/**
|
|
701
|
+
* Called after all plugins are registered and APIs are built. Runs in forward
|
|
702
|
+
* plugin order, sequentially awaited. Use for setup that depends on other plugins.
|
|
703
|
+
*
|
|
704
|
+
* @example
|
|
705
|
+
* ```ts
|
|
706
|
+
* onInit: (ctx) => {
|
|
707
|
+
* const http = ctx.require(httpPlugin);
|
|
708
|
+
* http.use(authMiddleware);
|
|
709
|
+
* }
|
|
710
|
+
* ```
|
|
711
|
+
*/
|
|
712
|
+
onInit?: (context: PluginExecutionContext<GlobalConfig, MergedPluginEvents<GlobalEventMap, PluginEventMap, DependencyPlugins>, PluginConfig, PluginState>) => void | Promise<void>;
|
|
713
|
+
/**
|
|
714
|
+
* Called when the app starts. Runs in forward plugin order, sequentially awaited.
|
|
715
|
+
* Use for runtime startup (open connections, start listeners).
|
|
716
|
+
*
|
|
717
|
+
* @example
|
|
718
|
+
* ```ts
|
|
719
|
+
* onStart: async (ctx) => {
|
|
720
|
+
* await ctx.state.db.connect(ctx.config.connectionString);
|
|
721
|
+
* }
|
|
722
|
+
* ```
|
|
723
|
+
*/
|
|
724
|
+
onStart?: (context: PluginExecutionContext<GlobalConfig, MergedPluginEvents<GlobalEventMap, PluginEventMap, DependencyPlugins>, PluginConfig, PluginState>) => void | Promise<void>;
|
|
725
|
+
/**
|
|
726
|
+
* Called when the app stops. Runs in **reverse** plugin order, sequentially awaited.
|
|
727
|
+
* Use for teardown (close connections, flush buffers). Receives only global config.
|
|
728
|
+
*
|
|
729
|
+
* @example
|
|
730
|
+
* ```ts
|
|
731
|
+
* onStop: async (ctx) => {
|
|
732
|
+
* await db.disconnect();
|
|
733
|
+
* }
|
|
734
|
+
* ```
|
|
735
|
+
*/
|
|
736
|
+
onStop?: (context: TeardownContext<GlobalConfig>) => void | Promise<void>;
|
|
737
|
+
/**
|
|
738
|
+
* Event subscription factory. Receives full plugin context; returns a map of
|
|
739
|
+
* event handlers. Same closure pattern as `api`. Handlers can access `ctx.state`,
|
|
740
|
+
* `ctx.emit`, `ctx.require`, etc.
|
|
741
|
+
*
|
|
742
|
+
* @example
|
|
743
|
+
* ```ts
|
|
744
|
+
* hooks: (ctx) => ({
|
|
745
|
+
* "auth:login": ({ userId }) => {
|
|
746
|
+
* ctx.state.lastLogin = userId;
|
|
747
|
+
* ctx.emit("page:render", { path: "/dashboard", html: "<div>Welcome</div>" });
|
|
748
|
+
* },
|
|
749
|
+
* })
|
|
750
|
+
* ```
|
|
751
|
+
*/
|
|
752
|
+
hooks?: (context: PluginExecutionContext<GlobalConfig, MergedPluginEvents<GlobalEventMap, PluginEventMap, DependencyPlugins>, PluginConfig, PluginState>) => { [EventName in string & keyof MergedPluginEvents<GlobalEventMap, PluginEventMap, DependencyPlugins>]?: (payload: MergedPluginEvents<GlobalEventMap, PluginEventMap, DependencyPlugins>[EventName]) => void | Promise<void> };
|
|
753
|
+
};
|
|
754
|
+
/**
|
|
755
|
+
* Bound createPlugin function type, parameterized by the framework's Config and Events.
|
|
756
|
+
*
|
|
757
|
+
* All type parameters are inferred from the spec object — no explicit generics needed.
|
|
758
|
+
* Per-plugin events use the register callback pattern instead of explicit type arguments.
|
|
759
|
+
*
|
|
760
|
+
* @example
|
|
761
|
+
* ```ts
|
|
762
|
+
* const { createPlugin } = createCoreConfig<MyConfig, MyEvents>("my-app", { config: defaults });
|
|
763
|
+
* const router = createPlugin("router", { config: { basePath: "/" } });
|
|
764
|
+
* ```
|
|
765
|
+
*/
|
|
766
|
+
type BoundCreatePluginFunction<GlobalConfig extends FrameworkConfig, GlobalEventMap extends FrameworkEventMap> = {
|
|
767
|
+
<const PluginName extends string = string, PluginConfig extends Record<string, unknown> = Record<string, never>, PluginState = Record<string, never>, PluginApi extends Record<string, unknown> = Record<string, never>, DependencyPlugins extends DependencyPluginTuple = readonly [], PluginEventMap extends Record<string, unknown> = EmptyPluginEventMap, HookHandlerMap extends Record<string, any> = Record<never, never>>(name: PluginName, spec: Omit<CreatePluginSpec<GlobalConfig, GlobalEventMap, PluginEventMap, PluginConfig, PluginState, PluginApi, DependencyPlugins>, "hooks"> & {
|
|
768
|
+
hooks?: (context: PluginExecutionContext<GlobalConfig, MergedPluginEvents<GlobalEventMap, PluginEventMap, DependencyPlugins>, PluginConfig, PluginState>) => { [K in keyof HookHandlerMap]: K extends string & keyof MergedPluginEvents<GlobalEventMap, PluginEventMap, DependencyPlugins> ? (payload: MergedPluginEvents<GlobalEventMap, PluginEventMap, DependencyPlugins>[K & keyof MergedPluginEvents<GlobalEventMap, PluginEventMap, DependencyPlugins>]) => void | Promise<void> : never };
|
|
769
|
+
}): PluginInstance<PluginName, PluginConfig, PluginState, PluginApi, PluginEventMap>;
|
|
770
|
+
};
|
|
771
|
+
/**
|
|
772
|
+
* Creates a bound `createPlugin` function that captures framework generics.
|
|
773
|
+
*
|
|
774
|
+
* Generic parameters:
|
|
775
|
+
* - `GlobalConfig`: app-wide config from `createCoreConfig`
|
|
776
|
+
* - `GlobalEventMap`: app-wide events from `createCoreConfig`
|
|
777
|
+
*
|
|
778
|
+
* @param frameworkId - The framework identifier for error messages.
|
|
779
|
+
* @returns A createPlugin function bound to the framework's Config and Events types.
|
|
780
|
+
* @example
|
|
781
|
+
* ```ts
|
|
782
|
+
* const createPlugin = createPluginFactory<MyConfig, MyEvents>("my-app");
|
|
783
|
+
* const plugin = createPlugin("router", { config: { basePath: "/" } });
|
|
784
|
+
* ```
|
|
785
|
+
*/
|
|
786
|
+
//#endregion
|
|
787
|
+
//#region src/core.d.ts
|
|
788
|
+
/**
|
|
789
|
+
* Options for createCore (Step 2).
|
|
790
|
+
*
|
|
791
|
+
* @example
|
|
792
|
+
* ```ts
|
|
793
|
+
* createCore(coreConfig, { plugins: [routerPlugin], onReady: (ctx) => console.log(ctx.config) });
|
|
794
|
+
* ```
|
|
795
|
+
*/
|
|
796
|
+
interface CreateCoreOptions<Config> {
|
|
797
|
+
/** Framework default plugins. */
|
|
798
|
+
readonly plugins: AnyPluginInstance[];
|
|
799
|
+
/** Framework-level plugin config overrides keyed by plugin name. */
|
|
800
|
+
readonly pluginConfigs?: Record<string, unknown>;
|
|
801
|
+
/** Called after all plugins are initialized. */
|
|
802
|
+
readonly onReady?: (context: {
|
|
803
|
+
config: Readonly<Config>;
|
|
804
|
+
}) => void | Promise<void>;
|
|
805
|
+
/** Error handler for hook dispatch and teardown failures. */
|
|
806
|
+
readonly onError?: (error: Error) => void;
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Return type of createCore (Step 2).
|
|
810
|
+
* Carries the Plugins type parameter to thread type information through createApp.
|
|
811
|
+
*
|
|
812
|
+
* @example
|
|
813
|
+
* ```ts
|
|
814
|
+
* const { createApp, createPlugin } = createCore(coreConfig, { plugins: [...] });
|
|
815
|
+
* ```
|
|
816
|
+
*/
|
|
817
|
+
interface CreateCoreResult<Config extends Record<string, unknown>, Events extends Record<string, unknown>, Plugins extends readonly AnyPluginInstance[] = readonly AnyPluginInstance[]> {
|
|
818
|
+
/**
|
|
819
|
+
* Step 3: Creates and initializes the application.
|
|
820
|
+
* Generic over ExtraPlugins to merge consumer plugins into the return type.
|
|
821
|
+
*
|
|
822
|
+
* @param options - Consumer-level config overrides, plugin configs, extra plugins.
|
|
823
|
+
* @returns A promise that resolves to the frozen, fully typed App object.
|
|
824
|
+
*/
|
|
825
|
+
readonly createApp: <const ExtraPlugins extends readonly AnyPluginInstance[] = readonly []>(options?: CreateAppOptions<Config, Events, Plugins[number] | ExtraPlugins[number], [...ExtraPlugins]>) => Promise<App<Config, Events, Plugins[number] | ExtraPlugins[number]>>;
|
|
826
|
+
/** Re-exported createPlugin for consumer convenience. */
|
|
827
|
+
readonly createPlugin: BoundCreatePluginFunction<Config, Events>;
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Bound createCore function type. Generic method captures the Plugins tuple
|
|
831
|
+
* from options.plugins to thread type information into CreateCoreResult.
|
|
832
|
+
*
|
|
833
|
+
* @example
|
|
834
|
+
* ```ts
|
|
835
|
+
* const framework = createCore(coreConfig, { plugins: [routerPlugin] });
|
|
836
|
+
* ```
|
|
837
|
+
*/
|
|
838
|
+
type BoundCreateCoreFunction<Config extends Record<string, unknown>, Events extends Record<string, unknown>> = <const Plugins extends readonly AnyPluginInstance[]>(coreConfig: {
|
|
839
|
+
readonly createPlugin: BoundCreatePluginFunction<Config, Events>;
|
|
840
|
+
}, options: CreateCoreOptions<Config> & {
|
|
841
|
+
readonly plugins: [...Plugins];
|
|
842
|
+
}) => CreateCoreResult<Config, Events, Plugins>;
|
|
843
|
+
/**
|
|
844
|
+
* Creates a bound `createCore` function that captures framework context.
|
|
845
|
+
*
|
|
846
|
+
* Generic parameters:
|
|
847
|
+
* - `Config`: app-wide config from `createCoreConfig`
|
|
848
|
+
* - `Events`: app-wide events from `createCoreConfig`
|
|
849
|
+
*
|
|
850
|
+
* @param frameworkId - The framework identifier for error messages.
|
|
851
|
+
* @param configDefaults - Default config values captured from Step 1.
|
|
852
|
+
* @param createPlugin - Bound createPlugin function from Step 1.
|
|
853
|
+
* @returns A createCore function bound to the framework's Config and Events types.
|
|
854
|
+
* @example
|
|
855
|
+
* ```ts
|
|
856
|
+
* const createCore = createCoreFactory<MyConfig, MyEvents>(
|
|
857
|
+
* "my-app", configDefaults, createPlugin
|
|
858
|
+
* );
|
|
859
|
+
* ```
|
|
860
|
+
*/
|
|
861
|
+
//#endregion
|
|
862
|
+
//#region src/config.d.ts
|
|
863
|
+
/**
|
|
864
|
+
* Return type of createCoreConfig. Named type required for isolatedDeclarations.
|
|
865
|
+
*
|
|
866
|
+
* @example
|
|
867
|
+
* ```ts
|
|
868
|
+
* const result: CoreConfigResult<SiteConfig, SiteEvents> = createCoreConfig("app", { config: defaults });
|
|
869
|
+
* ```
|
|
870
|
+
*/
|
|
871
|
+
interface CoreConfigResult<Config extends Record<string, unknown>, Events extends Record<string, unknown>> {
|
|
872
|
+
/** Creates a plugin instance with all types inferred from the spec object. */
|
|
873
|
+
readonly createPlugin: BoundCreatePluginFunction<Config, Events>;
|
|
874
|
+
/** Step 2 factory: captures default plugins and returns createApp + createPlugin. */
|
|
875
|
+
readonly createCore: BoundCreateCoreFunction<Config, Events>;
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Step 1 of the 3-step factory chain. Captures Config and Events generics
|
|
879
|
+
* in a closure and returns { createPlugin, createCore }.
|
|
880
|
+
*
|
|
881
|
+
* This is the ONLY export from `@moku-labs/core`. All downstream types flow from
|
|
882
|
+
* the generics captured here.
|
|
883
|
+
*
|
|
884
|
+
* @param id - Framework identifier used in error messages.
|
|
885
|
+
* @param options - Configuration options containing the default config values.
|
|
886
|
+
* @param options.config - Default configuration values for the framework.
|
|
887
|
+
* @returns An object with createPlugin (bound to Config/Events) and createCore.
|
|
888
|
+
* @example
|
|
889
|
+
* ```ts
|
|
890
|
+
* const coreConfig = createCoreConfig<SiteConfig, SiteEvents>("my-site", {
|
|
891
|
+
* config: { siteName: "Untitled", mode: "development" }
|
|
892
|
+
* });
|
|
893
|
+
* const { createPlugin, createCore } = coreConfig;
|
|
894
|
+
* ```
|
|
895
|
+
*/
|
|
896
|
+
declare function createCoreConfig<Config extends Record<string, unknown>, Events extends Record<string, unknown> = Record<string, never>>(id: string, options: {
|
|
897
|
+
config: Config;
|
|
898
|
+
}): CoreConfigResult<Config, Events>;
|
|
899
|
+
//#endregion
|
|
900
|
+
export { type EmitFunction as EmitFn, type PluginContext_ as PluginCtx, createCoreConfig };
|