@moku-labs/core 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Micro-kernel plugin framework for TypeScript. Three layers of isolation. Built for LLM-scale development.**
4
4
 
5
- One runtime export. Bundle < 5KB. Zero dependencies. The type system does the heavy lifting.
5
+ Two runtime exports. Bundle < 8KB gzipped. Zero dependencies. The type system does the heavy lifting.
6
6
 
7
7
  ```
8
8
  bun add @moku-labs/core
@@ -33,7 +33,7 @@ Moku enforces a 3-layer architecture where each layer physically constrains the
33
33
  │ Cannot: modify the kernel │
34
34
  ├─────────────────────────────────────────────────────────┤
35
35
  │ Layer 1: @moku-labs/core │
36
- One function. Zero domain knowledge. Pure machinery.
36
+ Two functions. Zero domain knowledge. Pure machinery.
37
37
  └─────────────────────────────────────────────────────────┘
38
38
  ```
39
39
 
@@ -122,7 +122,7 @@ app.blog.listPosts(); // fully typed
122
122
  await app.stop();
123
123
  ```
124
124
 
125
- **That's the entire API.** `createCoreConfig` → `createCore` → `createApp`. Three functions, three layers.
125
+ **That's the entire factory chain.** `createCoreConfig` → `createCore` → `createApp`. Three functions, three layers. (A second runtime export, `createCorePlugin`, builds self-contained infrastructure plugins — see the API reference below.)
126
126
 
127
127
  ---
128
128
 
@@ -171,7 +171,7 @@ Moku is designed for a world where LLMs write plugins and CI enforces quality. T
171
171
 
172
172
  - **Strict emit** — only known event names compile. Wrong payloads are type errors. No `any`, no escape hatch.
173
173
  - **Phantom types** — plugin APIs, configs, and events flow through the type system without runtime cost.
174
- - **Required configs** — if a plugin needs config and you don't provide it, TypeScript tells you.
174
+ - **Typed plugin configs** — `pluginConfigs` overrides are checked against each plugin's declared config shape. Wrong keys or value types don't compile. Overrides are optional — plugin defaults fill anything you omit.
175
175
  - **Context tiers** — `createState` can't call `emit` (other plugins don't exist yet). `onStop` can't access other plugins (they may already be stopped). The type system prevents temporal bugs.
176
176
 
177
177
  ### Runtime guarantees
@@ -248,7 +248,7 @@ Every field is optional. A plugin with only `api` works. A plugin with only `hoo
248
248
 
249
249
  ```typescript
250
250
  // Runtime
251
- import { createCoreConfig } from '@moku-labs/core';
251
+ import { createCoreConfig, createCorePlugin } from '@moku-labs/core';
252
252
 
253
253
  // Type utilities for plugin authors
254
254
  import type { PluginCtx, EmitFn } from '@moku-labs/core';
@@ -258,6 +258,10 @@ import type { PluginCtx, EmitFn } from '@moku-labs/core';
258
258
 
259
259
  Creates a bound factory chain for a framework. Returns `{ createPlugin, createCore }`, both locked to `Config` and `Events`.
260
260
 
261
+ ### createCorePlugin(name, spec)
262
+
263
+ Creates a self-contained infrastructure plugin (logging, env, storage). Core plugins are passed to `createCoreConfig` via `plugins`, and their APIs are injected onto every regular plugin's context (`ctx.log.info(...)`). No `depends`, no `events`, no `hooks` — a minimal `{ config, state }` context only.
264
+
261
265
  ### createCore(coreConfig, options)
262
266
 
263
267
  Captures framework defaults: plugins, plugin configs, `onReady`, `onError`. Returns `{ createApp, createPlugin }`.
package/dist/index.cjs CHANGED
@@ -156,10 +156,16 @@ function checkCorePluginConflicts(id, plugins, corePluginNames) {
156
156
  function asRecord(value) {
157
157
  return isRecord(value) ? value : {};
158
158
  }
159
+ /** Shared frozen API for registered plugins that declare no api(). */
160
+ const EMPTY_API = Object.freeze({});
159
161
  /**
160
162
  * Create a require function that looks up a plugin API by instance reference.
161
163
  *
162
- * @param runtime - The kernel runtime containing the API map.
164
+ * Registered plugins without an api() resolve to a frozen empty object —
165
+ * matching the type contract (ExtractApi = Record<string, never>) and
166
+ * agreeing with has(). Only genuinely unregistered plugins throw.
167
+ *
168
+ * @param runtime - The kernel runtime containing the API map and name set.
163
169
  * @param formatError - Formats the error message using the plugin instance name.
164
170
  * @returns A function that returns the API for a given plugin instance or throws.
165
171
  * @example
@@ -171,8 +177,9 @@ function asRecord(value) {
171
177
  function createRequire(runtime, formatError) {
172
178
  return (instance) => {
173
179
  const api = runtime.apis.get(instance.name);
174
- if (!api) throw new Error(formatError(instance.name));
175
- return api;
180
+ if (api) return api;
181
+ if (runtime.pluginNameSet.has(instance.name)) return EMPTY_API;
182
+ throw new Error(formatError(instance.name));
176
183
  };
177
184
  }
178
185
  /**
@@ -268,7 +275,9 @@ function buildEventBus(onError) {
268
275
  for (const handler of handlers) try {
269
276
  await handler(payload);
270
277
  } catch (error) {
271
- if (onError) onError(error);
278
+ try {
279
+ if (onError) onError(error);
280
+ } catch {}
272
281
  }
273
282
  }
274
283
  /**
@@ -552,7 +561,7 @@ function initCorePlugins(corePlugins, corePluginConfigs, frameworkPluginConfigs,
552
561
  * @returns The frozen app object.
553
562
  * @example
554
563
  * ```ts
555
- * const app = await kernel({ id: "my-app", configDefaults: {}, ... });
564
+ * const app = kernel({ id: "my-app", configDefaults: {}, ... });
556
565
  * ```
557
566
  */
558
567
  function kernel(parameters) {
@@ -566,7 +575,9 @@ function kernel(parameters) {
566
575
  const resolvedConfigs = resolvePluginConfigs(flatPlugins, frameworkPluginConfigs, consumerPluginConfigs);
567
576
  const states = createPluginStates(flatPlugins, globalConfig, resolvedConfigs);
568
577
  const { emit, registerHook } = buildEventBus(onError || consumer?.onError ? (error) => {
569
- if (onError) onError(error);
578
+ try {
579
+ if (onError) onError(error);
580
+ } catch {}
570
581
  if (consumer?.onError) consumer.onError(error, buildCallbackContext(runtime));
571
582
  } : void 0);
572
583
  const runtime = {
package/dist/index.d.cts CHANGED
@@ -953,14 +953,14 @@ type BoundCreatePluginFunction<GlobalConfig extends FrameworkConfig, GlobalEvent
953
953
  */
954
954
  interface CreateCoreOptions<Config> {
955
955
  /** Framework default plugins. */
956
- readonly plugins: AnyPluginInstance[];
956
+ readonly plugins: readonly AnyPluginInstance[];
957
957
  /** Framework-level plugin config overrides keyed by plugin name. */
958
958
  readonly pluginConfigs?: Record<string, unknown>;
959
959
  /** Called after all plugins are initialized. */
960
960
  readonly onReady?: (context: {
961
961
  config: Readonly<Config>;
962
962
  }) => void;
963
- /** Error handler for hook dispatch and teardown failures. */
963
+ /** Error handler for hook dispatch failures. Lifecycle errors from start()/stop() propagate to the caller instead. */
964
964
  readonly onError?: (error: Error) => void;
965
965
  }
966
966
  /**
@@ -996,7 +996,7 @@ interface CreateCoreResult<Config extends Record<string, unknown>, Events extend
996
996
  type BoundCreateCoreFunction<Config extends Record<string, unknown>, Events extends Record<string, unknown>, CorePlugins extends readonly AnyCorePluginInstance[] = readonly []> = <const Plugins extends readonly AnyPluginInstance[]>(coreConfig: {
997
997
  readonly createPlugin: BoundCreatePluginFunction<Config, Events, CoreApisFromTuple<CorePlugins>>;
998
998
  }, options: CreateCoreOptions<Config> & {
999
- readonly plugins: [...Plugins];
999
+ readonly plugins: readonly [...Plugins];
1000
1000
  }) => CreateCoreResult<Config, Events, Plugins, CorePlugins>;
1001
1001
  /**
1002
1002
  * Creates a bound `createCore` function that captures framework context.
@@ -1055,7 +1055,7 @@ interface CoreConfigResult<Config extends Record<string, unknown>, Events extend
1055
1055
  * const { createPlugin, createCore } = coreConfig;
1056
1056
  * ```
1057
1057
  */
1058
- declare function createCoreConfig<Config extends Record<string, unknown>, Events extends Record<string, unknown> = Record<string, never>, const CorePlugins extends readonly AnyCorePluginInstance[] = readonly []>(id: string, options: {
1058
+ declare function createCoreConfig<Config extends Record<string, unknown>, Events extends Record<string, unknown> = EmptyPluginEventMap, const CorePlugins extends readonly AnyCorePluginInstance[] = readonly []>(id: string, options: {
1059
1059
  config: Config;
1060
1060
  plugins?: [...CorePlugins];
1061
1061
  pluginConfigs?: { [K in CorePlugins[number] as ExtractCoreConfig<K> extends Record<string, never> ? never : IsLiteralString<ExtractCoreName<K>> extends true ? ExtractCoreName<K> : never]?: Partial<ExtractCoreConfig<K>> };
package/dist/index.d.mts CHANGED
@@ -953,14 +953,14 @@ type BoundCreatePluginFunction<GlobalConfig extends FrameworkConfig, GlobalEvent
953
953
  */
954
954
  interface CreateCoreOptions<Config> {
955
955
  /** Framework default plugins. */
956
- readonly plugins: AnyPluginInstance[];
956
+ readonly plugins: readonly AnyPluginInstance[];
957
957
  /** Framework-level plugin config overrides keyed by plugin name. */
958
958
  readonly pluginConfigs?: Record<string, unknown>;
959
959
  /** Called after all plugins are initialized. */
960
960
  readonly onReady?: (context: {
961
961
  config: Readonly<Config>;
962
962
  }) => void;
963
- /** Error handler for hook dispatch and teardown failures. */
963
+ /** Error handler for hook dispatch failures. Lifecycle errors from start()/stop() propagate to the caller instead. */
964
964
  readonly onError?: (error: Error) => void;
965
965
  }
966
966
  /**
@@ -996,7 +996,7 @@ interface CreateCoreResult<Config extends Record<string, unknown>, Events extend
996
996
  type BoundCreateCoreFunction<Config extends Record<string, unknown>, Events extends Record<string, unknown>, CorePlugins extends readonly AnyCorePluginInstance[] = readonly []> = <const Plugins extends readonly AnyPluginInstance[]>(coreConfig: {
997
997
  readonly createPlugin: BoundCreatePluginFunction<Config, Events, CoreApisFromTuple<CorePlugins>>;
998
998
  }, options: CreateCoreOptions<Config> & {
999
- readonly plugins: [...Plugins];
999
+ readonly plugins: readonly [...Plugins];
1000
1000
  }) => CreateCoreResult<Config, Events, Plugins, CorePlugins>;
1001
1001
  /**
1002
1002
  * Creates a bound `createCore` function that captures framework context.
@@ -1055,7 +1055,7 @@ interface CoreConfigResult<Config extends Record<string, unknown>, Events extend
1055
1055
  * const { createPlugin, createCore } = coreConfig;
1056
1056
  * ```
1057
1057
  */
1058
- declare function createCoreConfig<Config extends Record<string, unknown>, Events extends Record<string, unknown> = Record<string, never>, const CorePlugins extends readonly AnyCorePluginInstance[] = readonly []>(id: string, options: {
1058
+ declare function createCoreConfig<Config extends Record<string, unknown>, Events extends Record<string, unknown> = EmptyPluginEventMap, const CorePlugins extends readonly AnyCorePluginInstance[] = readonly []>(id: string, options: {
1059
1059
  config: Config;
1060
1060
  plugins?: [...CorePlugins];
1061
1061
  pluginConfigs?: { [K in CorePlugins[number] as ExtractCoreConfig<K> extends Record<string, never> ? never : IsLiteralString<ExtractCoreName<K>> extends true ? ExtractCoreName<K> : never]?: Partial<ExtractCoreConfig<K>> };
package/dist/index.mjs CHANGED
@@ -155,10 +155,16 @@ function checkCorePluginConflicts(id, plugins, corePluginNames) {
155
155
  function asRecord(value) {
156
156
  return isRecord(value) ? value : {};
157
157
  }
158
+ /** Shared frozen API for registered plugins that declare no api(). */
159
+ const EMPTY_API = Object.freeze({});
158
160
  /**
159
161
  * Create a require function that looks up a plugin API by instance reference.
160
162
  *
161
- * @param runtime - The kernel runtime containing the API map.
163
+ * Registered plugins without an api() resolve to a frozen empty object —
164
+ * matching the type contract (ExtractApi = Record<string, never>) and
165
+ * agreeing with has(). Only genuinely unregistered plugins throw.
166
+ *
167
+ * @param runtime - The kernel runtime containing the API map and name set.
162
168
  * @param formatError - Formats the error message using the plugin instance name.
163
169
  * @returns A function that returns the API for a given plugin instance or throws.
164
170
  * @example
@@ -170,8 +176,9 @@ function asRecord(value) {
170
176
  function createRequire(runtime, formatError) {
171
177
  return (instance) => {
172
178
  const api = runtime.apis.get(instance.name);
173
- if (!api) throw new Error(formatError(instance.name));
174
- return api;
179
+ if (api) return api;
180
+ if (runtime.pluginNameSet.has(instance.name)) return EMPTY_API;
181
+ throw new Error(formatError(instance.name));
175
182
  };
176
183
  }
177
184
  /**
@@ -267,7 +274,9 @@ function buildEventBus(onError) {
267
274
  for (const handler of handlers) try {
268
275
  await handler(payload);
269
276
  } catch (error) {
270
- if (onError) onError(error);
277
+ try {
278
+ if (onError) onError(error);
279
+ } catch {}
271
280
  }
272
281
  }
273
282
  /**
@@ -551,7 +560,7 @@ function initCorePlugins(corePlugins, corePluginConfigs, frameworkPluginConfigs,
551
560
  * @returns The frozen app object.
552
561
  * @example
553
562
  * ```ts
554
- * const app = await kernel({ id: "my-app", configDefaults: {}, ... });
563
+ * const app = kernel({ id: "my-app", configDefaults: {}, ... });
555
564
  * ```
556
565
  */
557
566
  function kernel(parameters) {
@@ -565,7 +574,9 @@ function kernel(parameters) {
565
574
  const resolvedConfigs = resolvePluginConfigs(flatPlugins, frameworkPluginConfigs, consumerPluginConfigs);
566
575
  const states = createPluginStates(flatPlugins, globalConfig, resolvedConfigs);
567
576
  const { emit, registerHook } = buildEventBus(onError || consumer?.onError ? (error) => {
568
- if (onError) onError(error);
577
+ try {
578
+ if (onError) onError(error);
579
+ } catch {}
569
580
  if (consumer?.onError) consumer.onError(error, buildCallbackContext(runtime));
570
581
  } : void 0);
571
582
  const runtime = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moku-labs/core",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "author": "Alex Kucherenko",
5
5
  "repository": {
6
6
  "type": "git",
@@ -46,7 +46,7 @@
46
46
  },
47
47
  "description": "Micro-kernel plugin framework for LLMs (TypeScript)",
48
48
  "engines": {
49
- "node": ">=22.0.0",
49
+ "node": ">=24.0.0",
50
50
  "bun": ">=1.3.8"
51
51
  },
52
52
  "files": [