Releases: moku-labs/core
v0.1.4
What's Changed
- fix(types): PluginLike admits core-plugin instances by @AlexTiTanium in #13
- chore(release): v0.1.4 by @AlexTiTanium in #14
Full Changelog: v0.1.3...v0.1.4
v0.1.3
What changed
Maintenance release: Node 24 toolchain and a spec docs-truth pass.
Node 24 (#9)
Both workflows now use Node 24-ready, SHA-pinned actions (checkout v6.0.2, setup-node v6.4.0, setup-bun v2.2.0, cache v5.0.5), ahead of GitHub's June 16 force-switch of JS actions off Node 20.
engines.node is now >=24.0.0 (was >=22.0.0), aligning with the rest of the moku family. Consumers on Node 22/23 with strict engines enforcement will be blocked from installing this version — stay on 0.1.2 or upgrade Node.
Spec docs-truth fixes (#10, #11)
Finishes what v0.1.2's #7 started — the specification no longer contradicts itself or the implementation:
11-INVARIANTS.md§1.4 rewritten ("Config Shape Checking"): everypluginConfigsentry is optional; overrides are shape-checked asPartial<C>; genuinely consumer-required values use the sentinel-default + runtime-check pattern. The matching stale rule in12-PLUGIN-PATTERNS.md's cheat-sheet was fixed too.- Stale claims that
createAppis async removed;01-ARCHITECTURE.mdrequired-config claims aligned with the optionalPartial<C>semantics.
No runtime code changes; npm package behavior is identical to 0.1.2.
Full Changelog: v0.1.2...v0.1.3
v0.1.2
What changed
Hardening release: four runtime/type fixes, the sandbox suite wired into CI, and a docs-truth pass — every item found by a multi-agent audit of the moku family and shipped as an individually reviewed PR.
Event bus survives throwing error handlers (#3)
A throwing onError handler no longer aborts hook dispatch or leaks an unhandled rejection from the fire-and-forget emit (which crashed the process on Node >= 22). Remaining hooks always run, and the consumer onError is still invoked when the framework handler throws. Specs 07/13 updated to document the guarded semantics.
Omitted Events generic keeps hook names strict (#6)
createCoreConfig's Events default was Record<string, never>, whose keyof is string — so frameworks that omitted global events silently lost hook-name checking and typo'd hooks compiled but never fired. The default is now Record<never, never>: typo'd dependency-event hooks are a compile error, matching the behavior with an explicit Events map.
createCore accepts readonly / as const plugin tuples (#4)
CreateCoreOptions.plugins is now readonly AnyPluginInstance[], consistent with the generic constraint. Note: widening the exported option to readonly is technically a breaking type change for code that read the property as a mutable array.
require() honors its type for api-less plugins (#5)
Requiring a registered plugin that exposes no api returned a misleading "x" is not registered error while has("x") was true. It now returns a frozen empty object — exactly what the ExtractApi type promises — at all three call sites (plugin ctx, callback ctx, app.require).
Sandbox suite runs in CI (#2)
The sandbox vitest project (500+ runtime tests over the exemplar plugins) now runs in CI and the pre-commit hook; previously it ran nowhere automated.
Docs truth pass (#7)
README/spec/CLAUDE.md/llms corrections: two runtime exports (createCoreConfig + createCorePlugin), real bundle size, reserved-name list now includes global/state (matching what the kernel actually rejects), removed the unimplemented "required configs are a compile-time guarantee" claim, and fixed the onError JSDoc that promised teardown-failure handling.
Full Changelog: v0.1.0-alpha.6...v0.1.2
v0.1.0-alpha.6
What changed
Export CorePluginContext and RegisterFunction from public API
Two types were defined internally but missing from the public API, forcing framework-layer packages to duplicate them locally:
CorePluginContext<C, S> — the minimal context shape for core plugin authors. Contains only config and state — no emit, require, or has. Core plugins are self-contained infrastructure (log, storage, env) that get injected as APIs onto regular plugin contexts.
RegisterFunction — the typed callback passed to the events factory in plugin specs. Supports two modes:
- Individual:
register<PayloadType>(description?) - Bulk:
register.map<EventMap>(descriptions?)
Both types are now exported directly from @moku-labs/core so framework authors can import them instead of maintaining local copies.
Why
Framework packages building on @moku-labs/core (e.g. a spa framework layer) need these types to write correctly typed core plugins and event declaration files. Without them being exported, each downstream package had to copy-paste the definitions locally — a maintenance hazard and a spec deviation.
v0.1.0-alpha.5
What's New
Plugin Helpers — static factory/builder functions that live on the plugin instance and are available before createApp. Consumers get typed, autocomplete-friendly APIs for building config values.
import { createPlugin, createApp } from "my-framework";
type Route = { path: string; component: string };
// Framework author defines helpers on the plugin spec:
export const routerPlugin = createPlugin("router", {
config: { routes: [] as Route[] },
api: ctx => ({
navigate: (path: string) => { /* ... */ },
}),
helpers: {
route: (path: string, component: string): Route => ({ path, component }),
},
});
// Consumer uses helpers BEFORE createApp — fully typed:
const home = routerPlugin.route("/home", "HomePage");
const about = routerPlugin.route("/about", "AboutPage");
const app = createApp({
pluginConfigs: { router: { routes: [home, about] } },
});
// Destructuring also works — types preserved:
const { route } = routerPlugin;
const contact = route("/contact", "ContactPage");Features
helpersfield on plugin spec — plain object of functions, each value must be a function. Optional field, defaults to empty.- Intersection return type —
createPluginreturnsPluginInstance<...> & Helpers. No 6th generic added toPluginInstance— the intersection widens away naturally in constraint positions (AnyPluginInstance,depends,pluginsarrays). - Zero breaking changes — existing plugins without helpers work identically.
& Record<never, never>is the identity intersection. - Runtime validation — helpers must be a plain object of functions. Helper names must not collide with
PluginInstancefields (name,spec,_phantom). Clear error messages on violation. - Full type inference — helper parameter types and return types are inferred from the spec. Destructured helpers preserve their signatures.
- Static pure functions — helpers have no access to
ctx, no lifecycle, no side effects. They run before the kernel exists.
Tests
- 722 tests passing (169 unit, 41 integration, 512 sandbox)
- New unit tests for helpers validation (valid helpers, null/non-object rejection, non-function values, name collisions, empty helpers)
- New type-level tests for helpers inference, intersection behavior, destructuring, and constraint assignability
Spec Updates
Updated specifications: 03 (Plugin System), 12 (Plugin Patterns), 13 (Kernel Pseudocode), 15 (Plugin Structure), README index, and CLAUDE.md.
v0.1.0-alpha.4
What's New
Core Plugins — a new plugin tier for self-contained infrastructure services (logging, storage, environment) whose APIs are injected directly onto every regular plugin's context.
import { createCoreConfig, createCorePlugin } from "@moku-labs/core";
const logPlugin = createCorePlugin("log", {
config: { level: "info" },
createState: () => ({ entries: [] as string[] }),
api: ctx => ({
info: (msg: string) => { ctx.state.entries.push(msg); },
error: (msg: string) => { ctx.state.entries.push(`[ERROR] ${msg}`); },
}),
});
const { createPlugin, createCore } = createCoreConfig("my-app", {
config: { siteName: "My Site" },
plugins: [logPlugin],
pluginConfigs: { log: { level: "debug" } },
});
// Every regular plugin gets typed core APIs on context — no require() needed
const router = createPlugin("router", {
api: ctx => ({
navigate: (path: string) => {
ctx.log.info(`navigating to ${path}`); // fully typed
}
}),
});Features
createCorePlugin(name, spec)— new factory for core plugin instances. Supportsconfig,createState,api,onInit,onStart, andonStop.- Context injection — core plugin APIs are spread directly onto every regular plugin's context (
ctx.log,ctx.env, etc.) with full type inference. - 4-level config cascade — core plugin configs merge through all factory chain levels: spec defaults →
createCoreConfig→createCore→createApp. - Lifecycle ordering — core plugins initialize and start before regular plugins; stop after regular plugins.
- Self-contained by design — core plugins have no
depends,events, orhooks. Their context is limited to{ config, state }. Attempting to use forbidden fields is a compile error and a runtime error. pluginConfigsoncreateCoreConfig— framework authors can override core plugin defaults at registration time.
Type System
- New exported types:
CorePluginInstance,AnyCorePluginInstance,CoreApisFromTuple CoreApisgeneric parameter threaded throughPluginContext,App,AppCallbackContext,CreateAppOptions, andBoundCreatePluginFunction(defaults to{}— fully backward compatible)- Phantom types on
CorePluginInstancecarryconfig,state, andapishapes at compile time
Constraints
Core plugins are intentionally limited:
| Feature | Core Plugin | Regular Plugin |
|---|---|---|
config / createState / api |
✅ | ✅ |
onInit / onStart / onStop |
✅ | ✅ |
depends |
❌ | ✅ |
events |
❌ | ✅ |
hooks |
❌ | ✅ |
ctx.global / ctx.emit / ctx.require / ctx.has |
❌ | ✅ |
Breaking Changes
None. The CoreApis = {} default is the identity element for type intersection — all existing code compiles and behaves identically.
v0.1.0-alpha.3
Add Very Complex plugin spec and integration tests. Auto-detect npm dist-tag for pre-releases.
v0.1.0-alpha.2
What's Changed
- Export framework-facing types for declaration emit — Added 8 type-only re-exports (
CoreConfigResult,BoundCreateCoreFunction,CreateCoreOptions,CreateCoreResult,BoundCreatePluginFunction,PluginInstance,AnyPluginInstance,App,CreateAppOptions) required by downstream framework packages to generate.d.tsfiles without TS4023.
These types appear in return-type positions of createCoreConfig, createCore, and createApp. Without them exported, downstream bun run build (tsdown with dts: true) fails because TypeScript cannot reference unnamed types in generated declaration files.
Type-only exports — zero runtime/bundle cost.
v0.1.0-alpha.1
First published alpha release
v1-alpha
Full Changelog: https://github.com/moku-labs/core/commits/v1-alpha