===============
LIBRARY RULES
===============
From library maintainers:
- Never mutate the kernel during render: kernel.load/unload and scope.provide/contribute belong in event handlers, effects, or bootstrap.
- Tokens are imported symbols from one contract module, never string literals at call sites.
- Prefer over useService(Token)! so removal unmounts the dependent subtree.
- After an await inside setup, check scope.active before provide or contribute.
### Run example todo app
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/README.md
Start the example todo app in development mode. See the example's README for details.
```bash
pnpm --filter yatoi-example-todo dev
```
--------------------------------
### Install kernel app-wide with app.use(yatoi, { kernel })
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Registers the kernel for the entire Vue app. Prefer this over provideKernel in a component's setup() because Vue's provide() doesn't reach the providing instance itself.
```ts
import { createApp } from 'vue'
import { yatoi } from '@yatoi/vue'
import { kernel } from './kernel'
import App from './App.vue'
createApp(App).use(yatoi, { kernel }).mount('#app')
```
--------------------------------
### Install @yatoi/kernel and @yatoi/slots
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/packages/slots/README.md
Install the required packages using npm.
```bash
npm i @yatoi/kernel @yatoi/slots
```
--------------------------------
### Define a plugin with inject and provides
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/concepts.md
Defines a plugin named 'sync' that activates only when Clock and Storage are present, provides SyncStatus, and uses the scope to get injected services, defer cleanup, and provide a service. The setup function is typed and the plugin is plain data until loaded by a kernel.
```ts
const syncPlugin = definePlugin({
name: 'sync',
inject: [Clock, Storage], // activates only when both are present
provides: [SyncStatus], // may provide only what it declares
setup(scope) {
const clock = scope.get(Clock) // typed non-null: it's injected
scope.defer(subscribe(clock)) // reversible effect
scope.provide(SyncStatus, status) // published; removed on dispose
},
})
```
--------------------------------
### Install @yatoi/vue and vue
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/packages/vue/README.md
Install the package and its peer dependency using npm.
```bash
npm i @yatoi/vue vue
```
--------------------------------
### Run preflight checks
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/releasing.md
Run the complete preflight before publishing: frozen-lockfile install, tests, typecheck, and pack:check. pack:check fails if a tarball contains source maps, TypeScript build metadata, tests, examples, or unresolved workspace: dependencies.
```bash
pnpm install --frozen-lockfile
pnpm test
pnpm typecheck
pnpm pack:check
```
--------------------------------
### Provide a nested kernel with provideKernel
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Use provideKernel in a component's setup() to provide a different kernel to a subtree. This is the right tool for a nested or scoped kernel, unlike the app-wide installation.
```ts
import { provideKernel } from '@yatoi/vue'
export default defineComponent({
setup() {
provideKernel(nestedKernel)
// ...
},
})
```
--------------------------------
### Install @yatoi/kernel via npm
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/packages/kernel/README.md
Install the package using npm.
```bash
npm i @yatoi/kernel
```
--------------------------------
### Setup @yatoi/vue with createApp
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/packages/vue/README.md
Create a Vue app with a kernel, define a service and plugin, and use the yatoi plugin with the kernel.
```ts
import { createApp, defineComponent, h } from 'vue'
import { createKernel, definePlugin, defineService } from '@yatoi/kernel'
import { Requires, yatoi } from '@yatoi/vue'
import { Slot } from '@yatoi/vue/slots'
declare module '@yatoi/slots' { interface Slots { 'sidebar.item': Record } }
const Clock = defineService<{ now(): number }>('clock')
const clock = definePlugin({ name: 'clock', provides: [Clock], setup(scope) {
scope.provide(Clock, { now: () => Date.now() })
scope.defer(() => console.log('clock stopped'))
} })
const kernel = createKernel()
kernel.load(clock)
const App = defineComponent(() => () => h(Requires, { of: [Clock] }, {
default: ([value]) => [h('time', value.now()), h(Slot, { name: 'sidebar.item' })],
}))
createApp(App).use(yatoi, { kernel }).mount('#app')
```
--------------------------------
### Install @yatoi/react
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/packages/react/README.md
Install the React bindings package along with React and React DOM. Requires React 18 or 19.
```bash
npm i @yatoi/react react react-dom
```
--------------------------------
### Async setup with scope.active check (TypeScript)
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Use this pattern when setup is async. After awaiting, check scope.active before performing side effects. If the scope is inactive, close the socket and return to avoid leaks. Defer cleanup before the await when possible.
```TypeScript
async setup(scope) {
const socket = await connect()
if (!scope.active) { socket.close(); return } // deps vanished mid-await
scope.defer(() => socket.close())
scope.provide(Socket, socket)
}
```
--------------------------------
### Install dependencies with pnpm
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/README.md
Install all project dependencies using pnpm. Requires Node ≥ 22.22.2 and pnpm 9.
```bash
pnpm install
```
--------------------------------
### Define a plugin that provides a service (TypeScript)
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Use this to create a plugin that provides a service. The setup function creates a store, subscribes to changes, and provides the service. The kernel enforces that provided tokens must be listed in 'provides', otherwise it throws. Deferred functions run on dispose in LIFO order.
```TypeScript
import { definePlugin } from '@yatoi/kernel'
export const todosPlugin = definePlugin({
name: 'todos',
provides: [Todos],
setup(scope) {
const store = createStore(loadFromLocalStorage())
const stop = store.subscribe(() => saveToLocalStorage(store.getAll()))
scope.defer(stop) // undone on dispose, LIFO
scope.provide(Todos, store) // must be listed in `provides`
},
})
```
--------------------------------
### TypeScript example: create kernel, define plugin and service
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/packages/kernel/README.md
Import createKernel, definePlugin, defineService from '@yatoi/kernel'. Define a Clock service, a plugin providing the service and deferring a stop, a reader plugin injecting the Clock, and load both plugins.
```ts
import { createKernel, definePlugin, defineService } from '@yatoi/kernel'
const Clock = defineService<{ now(): number }>('clock')
const clock = definePlugin({ name: 'clock', provides: [Clock], setup(scope) {
const timer = { stop() {} }
scope.provide(Clock, { now: () => Date.now() })
scope.defer(() => timer.stop())
} })
const reader = definePlugin({ name: 'reader', inject: [Clock], setup(scope) {
console.log(scope.get(Clock).now())
} })
const kernel = createKernel()
kernel.load(clock, reader)
```
--------------------------------
### Install @yatoi/vue dependencies with pnpm
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Install the kernel, Vue binding, and Vue itself. Required before using any @yatoi/vue features.
```bash
pnpm add @yatoi/kernel @yatoi/vue vue
```
--------------------------------
### Install Yatoi packages with pnpm
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Install the core Yatoi packages and React dependencies. @yatoi/kernel has no dependencies, @yatoi/slots depends only on @yatoi/kernel, and @yatoi/react peer-depends on react ^18 || ^19.
```bash
pnpm add @yatoi/kernel @yatoi/react react react-dom
```
--------------------------------
### Define a plugin that depends on a service (TypeScript)
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Use this to create a plugin that depends on other services. The injected tokens are typed non-null and snapshotted at activation. The setup function gets the services and sets up a periodic task, deferring cleanup. If the underlying service changes, the scope is disposed and re-run.
```TypeScript
export const syncPlugin = definePlugin({
name: 'sync',
inject: [Todos, Clock],
setup(scope) {
const todos = scope.get(Todos) // TodoStore, not TodoStore | undefined
const clock = scope.get(Clock)
const id = setInterval(() => push(todos.getAll(), clock.now()), 5000)
scope.defer(() => clearInterval(id))
},
})
```
--------------------------------
### Closure over setup-time service for a slot renderer
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/pitfalls.md
A slot renderer's signature is fixed by the `Slots` contract, so it cannot accept a service parameter. Use a closure to capture the service obtained via `scope.get` during setup. The renderer is then contributed with `contribute(scope, 'todo.item.extra', ...)`.
```tsx
function makeDueDateField(todos: TodoStore) {
return function DueDateField({ todo }: SlotRendererProps<'todo.item.extra'>) { … }
}
contribute(scope, 'todo.item.extra', makeDueDateField(scope.get(Todos)))
```
--------------------------------
### Register and unload plugins with kernel.load and kernel.unload
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/concepts.md
Shows the derived activation and cascade unload behavior. A plugin runs setup when all its inject tokens are present and disposes when any goes away. Load order does not matter; unloading a thread disposes dependents in reverse dependency order, and reloading re-runs setup with a fresh scope.
```javascript
kernel.load(syncPlugin) // registered, inactive — Clock isn't there yet
kernel.load(clockPlugin) // clock activates; sync activates right after
kernel.unload(clockPlugin) // sync disposes first, then clock
kernel.load(clockPlugin) // both come back, sync with a fresh scope
```
--------------------------------
### Kernel plugin setup with temporal and spatial scoping
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/README.md
Defines a Clock service and two plugins: clockPlugin uses scope.defer for temporal cleanup and scope.provide for a reversible value; syncPlugin injects Clock and subscribes spatially, activating only while Clock is present. Loading order doesn't matter; unloading disposes sync first, and reloading gives sync a fresh scope.
```ts
import { createKernel, defineService, definePlugin } from '@yatoi/kernel'
const Clock = defineService<{ now(): number }>('clock') // a real symbol, not a string
const clockPlugin = definePlugin({
name: 'clock',
provides: [Clock],
setup(scope) {
const id = setInterval(tick, 1000)
scope.defer(() => clearInterval(id)) // temporal: undone on unload
scope.provide(Clock, { now: () => Date.now() }) // temporal: removed on unload
},
})
const syncPlugin = definePlugin({
name: 'sync',
inject: [Clock], // spatial: runs only while Clock is present
setup(scope) { scope.defer(subscribe(scope.get(Clock))) }, // typed non-null
})
const kernel = createKernel()
kernel.load(syncPlugin, clockPlugin) // order doesn't matter; sync activates when Clock appears
kernel.unload(clockPlugin) // sync disposes first, cleanups in reverse
kernel.load(clockPlugin) // both come back, sync with a fresh scope
```
--------------------------------
### Declare and use a slot with @yatoi/slots
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/packages/slots/README.md
Shows how to declare a slot type, contribute to it in a plugin, and list contributions. The example declares a 'task.badge' slot with a taskId property, contributes a badge renderer, and logs the number of contributions.
```ts
import { createKernel, definePlugin } from '@yatoi/kernel'
import { slot } from '@yatoi/slots'
declare module '@yatoi/slots' {
interface Slots { 'task.badge': { taskId: string } }
}
const badges = definePlugin({ name: 'badges', setup(scope) {
scope.contribute(slot('task.badge'), 'badge renderer')
} })
const kernel = createKernel()
kernel.load(badges)
console.log(kernel.list(slot('task.badge')).length)
```
--------------------------------
### Define a service with a key (third-party case)
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
When a plugin cannot depend on the contract package, use a types-only package and augment the Services interface. Use service('key') to get a token by key.
```ts
import { service } from '@yatoi/kernel'
import type { TodoStore } from '@todo-app/types'
declare module '@yatoi/kernel' {
interface Services {
todos: TodoStore
}
}
const todos = service('todos') // ServiceToken
```
--------------------------------
### Install and uninstall plugins from an event handler
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Loads a plugin asynchronously from a module map and unloads it. Unloading cascades to dependents and is a no-op if the plugin is not loaded. The plugin object is the identity; loading the same object twice throws.
```ts
// an install button
async function install(id: string) {
const mod = await pluginModules[id]()
kernel.load(mod.default)
}
function uninstall(plugin: AnyPlugin) {
kernel.unload(plugin) // dependents cascade; no-op if not loaded
}
```
--------------------------------
### Distinguish absent from starting up with useServiceState
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Returns a discriminated union indicating whether the service is present, absent, or loading. Use this when you need to differentiate between a service that is not yet available and one that is starting up.
```tsx
const s = useServiceState(Clock) // { status: 'present', value } | { status: 'absent' } | { status: 'loading' }
```
--------------------------------
### Factory pattern for closing over services (makeCalendarView)
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Creates a renderer component that closes over a service obtained from setup. Inline arrow functions do this naturally; for larger components, a factory keeps things readable. This is the idiomatic way to hand a setup-time dependency to a component React will call later, after setup has returned.
```tsx
function makeCalendarView(todos: TodoStore) {
return function CalendarView() {
const list = useTodos(todos)
…
}
}
scope.contribute(Views, { id: 'calendar', label: 'Calendar', View: makeCalendarView(todos) })
```
--------------------------------
### Apply changesets and install dependencies
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/releasing.md
Apply pending changesets and update the lockfile. Review changelogs, versions, and internal dependency ranges. On a patch, unrelated packages keep their versions; on a minor or major, all four land on the same x.y.0.
```bash
pnpm version-packages
pnpm install
```
--------------------------------
### Registry-based plugin loader (TypeScript)
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
The default loader: a registry maps plugin IDs to dynamic imports. It validates the default export's shape (name and setup) and throws an error if invalid. The kernel then loads the plugin. This is bundled with the app, so Vite code-splits it like any other dynamic import.
```ts
// the "loader": id → code. The default shape — bundled with the app, so
// Vite code-splits this like any other dynamic import.
const registry: Record Promise> = {
calendar: () => import('./plugins/calendar/index.js'),
}
const mod = await registry[entry.id]?.()
if (typeof mod?.default?.name !== 'string' || typeof mod?.default?.setup !== 'function') {
throw new Error(`plugin "${entry.id}" has no valid default export`)
}
kernel.load(mod.default)
```
--------------------------------
### useService hook to get a service
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/design.md
Retrieves a service from the kernel. The return type is `Clock | undefined` because the service may not be available.
```tsx
const clock = useService(Clock) // Clock | undefined
```
--------------------------------
### Kernel observation API usage
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Use these kernel methods to inspect plugin states, retrieve contributions, subscribe to changes, and get the version. The version property is useful for a cheap useSyncExternalStore snapshot.
```TypeScript
kernel.pluginState(plugin) // 'inactive' | 'starting' | 'active' | 'disposing' | 'failed'
kernel.state(Clock) // { status: 'present', value } | { status: 'absent' } | { status: 'loading' }
kernel.get(Clock) // Clock | undefined
kernel.list(Views) // readonly Contribution[]
kernel.subscribe(listener) // fires once per batch of changes
kernel.version // bumps on every change — a cheap useSyncExternalStore snapshot
```
--------------------------------
### Use useContributionValues to get contribution values
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Returns a Readonly>. Use this instead of useContributions when you only need the values, avoiding the double .value indirection.
```ts
const views = useContributionValues(Views) // Readonly>
```
--------------------------------
### Use KernelProvider, Requires, and Slot with @yatoi/react
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/packages/react/README.md
Shows how to create a kernel, define a service and plugin, load the plugin, and render a React component using KernelProvider, Requires, and Slot. The plugin provides a Clock service and defers a cleanup log. The kernel is created outside the component tree and React observes it without owning it.
```tsx
import { createKernel, definePlugin, defineService } from '@yatoi/kernel'
import { KernelProvider, Requires } from '@yatoi/react'
import { Slot } from '@yatoi/react/slots'
declare module '@yatoi/slots' { interface Slots { 'sidebar.item': Record } }
const Clock = defineService<{ now(): number }>('clock')
const clock = definePlugin({ name: 'clock', provides: [Clock], setup(scope) {
scope.provide(Clock, { now: () => Date.now() })
scope.defer(() => console.log('clock stopped'))
} })
const kernel = createKernel()
kernel.load(clock)
export function App() { return {(value) => } }
```
--------------------------------
### Build all packages
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/README.md
Build all packages using tsc -b, producing dist/ in each package.
```bash
pnpm build
```
--------------------------------
### Run tests for all packages
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/README.md
Run the test suite for all packages. Kernel and slots tests run in Node, while react and vue tests run in jsdom.
```bash
pnpm test
```
--------------------------------
### Define and load plugins with @yatoi/kernel
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/design.md
Shows the core kernel API: defining services, plugins, and loading them. The 'inject' declaration drives the lifecycle graph; unload disposes dependents first and deferred cleanups in reverse. The 'provides' array must be declared, otherwise provide() throws.
```typescript
import { createKernel, defineService, defineCollection, definePlugin } from '@yatoi/kernel'
// A token is a real imported symbol, so go-to-definition works.
const Clock = defineService<{ now(): number }>('clock')
const clockPlugin = definePlugin({
name: 'clock',
provides: [Clock], // must be declared — provide() of anything else throws
setup(scope) {
const id = setInterval(tick, 1000)
scope.defer(() => clearInterval(id)) // reversible effect; disposers run LIFO
scope.provide(Clock, { now: () => Date.now() })
},
})
const syncPlugin = definePlugin({
name: 'sync',
inject: [Clock, Storage], // drives the lifecycle graph
setup(scope) {
const clock = scope.get(Clock) // typed non-null, because declared
scope.defer(subscribe(clock))
},
})
const kernel = createKernel()
kernel.load(clockPlugin, syncPlugin) // registration; activation is derived
```
--------------------------------
### Create a changeset
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/releasing.md
Run pnpm changeset to add a changeset file for a user-visible package change. Choose the semantic-version impact and explain the behavior. For a patch, name only the changed packages; for a minor or major (e.g., 0.2), name all four published packages.
```bash
pnpm changeset
```
--------------------------------
### Subscribe to a service's own state with useSyncExternalStore
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
A service is just a value; if it has state, subscribe to it like any external store. Keep `getAll()` returning the same array reference until a mutation to keep the subscription cheap.
```ts
export function useTodos(store: TodoStore) {
return useSyncExternalStore(store.subscribe, store.getAll)
}
```
--------------------------------
### Run pnpm release to publish packages
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/releasing.md
Publishes the packages to npm. Must be run only from the exact reviewed release commit while authenticated as an npm publisher.
```bash
pnpm release
```
--------------------------------
### Check npm package availability for yatoi
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/design.md
Run before committing to a name to verify availability on npm. `npm view` exiting with 404 means the package is available. The scoped package `@yatoi/kernel` is the binding constraint and should be checked first.
```bash
npm view yatoi # bare package
npm view @yatoi/kernel # scope — the binding constraint, check this first
```
--------------------------------
### Create a kernel (kernel.ts)
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Create a kernel at module scope, outside any component. Attach an error handler to log plugin failures.
```ts
// kernel.ts
import { createKernel } from '@yatoi/kernel'
export const kernel = createKernel()
kernel.on('error', (error, plugin) => {
console.error(`plugin "${plugin.name}" failed:`, error)
})
```
--------------------------------
### Define plugin with isolated token and load twice
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/proposals/0001-namespace-isolation.md
Shows a plugin that isolates the Todos token, provides it, and loads a calendar plugin. Loading the same plugin twice currently errors but is intended to create separate namespaces.
```typescript
const listPlugin = definePlugin({
name: 'todo-list',
isolate: [Todos], // this subtree gets its own Todos
provides: [Todos],
setup(scope) {
scope.provide(Todos, createStore(scope.id))
scope.load(calendarPlugin) // resolves Todos → this scope's
},
})
kernel.load(listPlugin) // list A
kernel.load(listPlugin) // list B — currently an error (§5.4); see open question 1
```
--------------------------------
### Kernel testing without React
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Use this snippet to test the kernel in a plain Node environment. It loads plugins, checks plugin states, and waits for async operations to settle.
```TypeScript
const kernel = createKernel()
kernel.load(clockPlugin, syncPlugin)
expect(kernel.pluginState(syncPlugin)).toBe('active')
kernel.unload(clockPlugin)
expect(kernel.pluginState(syncPlugin)).toBe('inactive')
await kernel.settle() // wait for any async setup/disposal
await kernel.dispose() // unload everything and settle
```
--------------------------------
### Load a plugin at component scope with usePlugin
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Loads a plugin when the component mounts and unloads it on unmount. The plugin must be defined at module scope for a stable identity. Under StrictMode, the kernel handles load → unload → load, resulting in exactly one active instance. Two mounted components using the same plugin object is an error.
```tsx
const spellcheckPlugin = definePlugin({ name: 'spellcheck', setup(scope) { … } })
function Editor() {
usePlugin(spellcheckPlugin) // loaded on mount, unloaded on unmount
return
}
```
--------------------------------
### Run typecheck
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/README.md
Run TypeScript type checking across the project.
```bash
pnpm typecheck
```
--------------------------------
### Load plugins at kernel scope in bootstrap.ts
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Loads plugins once before the first render. The order of plugins does not matter. Loading the same plugin object twice at top level throws, so keep a Map if loading from a manifest.
```ts
// bootstrap.ts — runs once before the first render
kernel.load(todosPlugin, clockPlugin, syncPlugin) // order doesn't matter
```
--------------------------------
### React projection with and
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/README.md
Shows yatoi's render-tree composability in React. makes a subtree depend on a service, unmounting when it goes away; lets a plugin contribute UI without the host importing it. The comment contrasts with a stale context read that can crash.
```tsx
// React today: a read that can go stale
const clock = useContext(ClockContext) // Clock | undefined once the provider is gone
clock!.now() // and here is the crash
// yatoi: a requirement the subtree cannot exist without
}>
{(clock) => } {/* non-null; unmounts when Clock goes */}
// yatoi: UI a plugin pushes into a surface it doesn't own
{/* host never imports the contributors */}
```
--------------------------------
### Host declares slot and renders it
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/concepts.md
The host declares a slot with a props contract using module augmentation and renders it with the Slot component. Props are type-checked on both sides.
```tsx
// host
declare module '@yatoi/slots' {
interface Slots { 'todo.item.extra': { todo: Todo } }
}
```
--------------------------------
### Use useService for optional service access
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
For the cheap case where absence is genuinely fine to render. Returns the service value or undefined. Use optional chaining to handle the absence.
```tsx
const clock = useService(Clock) // Clock | undefined
return {clock ? clock.now() : '—'}
```
--------------------------------
### Check package tarballs
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/README.md
Build and audit the four npm tarballs without publishing them.
```bash
pnpm pack:check
```
--------------------------------
### Four-layer architecture diagram
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/concepts.md
ASCII diagram showing the four layers of yatoi, from React-specific bindings down to the framework-agnostic kernel. Each layer lists its exports and dependencies.
```text
@yatoi/react/slots contribute(), for React React
@yatoi/react KernelProvider, useService, React
@yatoi/slots Slots interface, SlotName, slot() no React, no Vue
@yatoi/kernel plugins, services, cascade unload no React, no DOM
```
--------------------------------
### Define and use a collection with useContributions
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Defines a ViewDescriptor interface and a Views collection, then uses useContributions in a Shell component to render navigation and the active view. The contribution object includes value, priority, mode, and owner.
```tsx
// host
export interface ViewDescriptor { id: string; label: string; View: ComponentType }
export const Views = defineCollection('views')
function Shell() {
const views = useContributions(Views) // readonly Contribution[]
const active = views.find((c) => c.value.id === activeId)
return (
<>
{active ? : }
>
)
}
```
--------------------------------
### URL-based plugin loader (TypeScript)
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Alternative loader that resolves an id to a URL and dynamically imports it, as for a CDN-hosted plugin. Uses @vite-ignore to prevent Vite from analyzing the URL. The same validation and kernel.load call apply. A missing file becomes a load failure the UI can show without crashing.
```ts
const url = `${pluginBase}/${entry.id}.js`
const mod = await import(/* @vite-ignore */ url)
// same validation, same kernel.load(mod.default)
```
--------------------------------
### Set repository metadata for yatoi
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/design.md
Repository metadata to set on day one for the yatoi project. The Description field and Topics list are used by GitHub and npm search for discovery.
```text
Description: Plugin kernel for React. Reversible effects, service discovery,
and reactive unload — build fully plugin-based frontends.
Topics: react, plugin-architecture, plugin-system, extension-points,
dependency-injection, micro-kernel, slots, lifecycle
```
--------------------------------
### Plugin manifest JSON (public/plugins.json)
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
A JSON array describing a plugin's metadata (id, name, version, description). The marketplace UI renders cards from this data alone. An id described here but missing from the registry shows as unavailable.
```json
// public/plugins.json
[{
"id": "calendar",
"name": "Calendar",
"version": "0.1.0",
"description": "…"
}]
```
--------------------------------
### Consume a service with render prop
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
The recommended way to consume a service in React. The render prop's arguments are non-null, and the subtree unmounts when a service disappears. `of` accepts any number of tokens; arguments arrive in the same order.
```tsx
Loading…
}>
{(todos) => }
```
--------------------------------
### Plugin wraps its own lazy component in Suspense
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/pitfalls.md
A plugin contributes a lazy component wrapped in its own `` boundary so it controls its own placeholder. `` does not add a Suspense boundary, so without this the host must provide one. The composed renderer is memoised on the contributions array, keeping the lazy component's identity stable across host re-renders.
```tsx
contribute(scope, 'sidebar.item', () => (
}>
))
```
--------------------------------
### Use with a default slot and fallback
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Renders the default slot with the provided values as a tuple when all contributions are available, otherwise the fallback. Use explicit to avoid a Vue compiler crash with the shorthand.
```html
```
--------------------------------
### Render slots with (list and single modes)
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Renders a slot with the given name and props. In list mode (default), every append contribution is rendered in priority order, with fallback shown when none exist. In single mode, the children component is the host default, with replace and wrap contributions folded over it, highest priority outermost.
```tsx
// list mode (default)
{DefaultCard}
```
--------------------------------
### KernelProvider wrapping the app
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/design.md
Wraps the application with the kernel provider to make the kernel available to the component tree.
```tsx
```
--------------------------------
### Render a slot
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/design.md
Renders the 'task.card' slot with a task prop and mode 'single'. Props are checked against the `Slots` type on both sides.
```tsx
```
--------------------------------
### Contribute a view from a plugin
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Shows how a plugin contributes a view to the Views collection using scope.contribute, passing the collection token and the contribution value.
```tsx
// plugin
scope.contribute(Views, { id: 'calendar', label: 'Calendar', View })
```
--------------------------------
### Define a service token with defineService
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/concepts.md
Creates a service token named 'clock' with a phantom type. The string is only for debugging; the token is a real TypeScript value enabling IDE features.
```ts
const Clock = defineService<{ now(): number }>('clock')
```
--------------------------------
### Contribute to a slot with priority
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/design.md
Contributes a renderer for the 'sidebar.item' slot with a priority of 10. The `contribute` function is imported from `@yatoi/react/slots`.
```tsx
import { contribute } from '@yatoi/react/slots'
contribute(scope, 'sidebar.item', ({ collapsed }) =>
, { priority: 10 })
```
--------------------------------
### Contribute to slots with contribute() (plugin side)
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Registers a contribution for a slot using the contribute function. The renderer receives the slot's props plus Default (the renderer beneath it; a no-op in list mode). Props are type-checked against Slots[name], so wrong shapes cause compile errors. Removal on unload is automatic. If the renderer is React.lazy, wrap it in your own Suspense.
```tsx
import { contribute } from '@yatoi/react/slots'
setup(scope) {
const todos = scope.get(Todos)
contribute(scope, 'todo.item.extra', ({ todo }) => (
todos.patch(todo.id, { dueDate: e.target.value })} />
))
contribute(scope, 'task.card', ({ task, Default }) => (
), { mode: 'wrap', priority: 10 })
}
```
--------------------------------
### Declare slot types with module augmentation (slots.ts)
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Declares the shape of custom slots by augmenting the '@yatoi/slots' module. Import this file from your entry point so the augmentation is included in the program. The interface maps slot names to their props, enabling type checking for contributions.
```ts
// slots.ts — import this from your entry point so the augmentation is in the program
import type { Todo } from './core/todos'
declare module '@yatoi/slots' {
interface Slots {
'todo.item.extra': { todo: Todo }
'task.card': { task: Task }
}
}
```
--------------------------------
### Guard against HMR replacing kernel-scoped plugin (Vite)
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/pitfalls.md
Use import.meta.hot.dispose to unload the plugin when its module is hot-replaced, preventing a duplicate provider error for kernel-scoped plugins loaded at bootstrap. Dev-only; without this guard, accept a full reload instead.
```typescript
export const todosPlugin = definePlugin({ … })
if (import.meta.hot) {
import.meta.hot.dispose(() => kernel.unload(todosPlugin))
}
```
--------------------------------
### usePlugin for component-scoped plugins
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/design.md
Registers a plugin scoped to the component's lifetime. The plugin is active only while the component is mounted.
```tsx
function Editor() {
usePlugin(spellcheckPlugin) // scope tied to this component's lifetime
return
}
```
--------------------------------
### KernelImpl.mutate algorithm
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/architecture.md
Pseudo-code for the mutate function that funnels all mutations. It runs the function immediately if already inside a mutation, otherwise batches the mutation, runs passes until no changes, and prunes.
```plaintext
mutate(fn):
if already inside a mutation: run fn, flag needsPass, return
else:
registry.batch(() => {
run fn
loop: needsPass = false; pass() // until a pass changes nothing
prune()
}) // batch end → one notification
```
--------------------------------
### Define a service token (services.ts)
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Define a service token using defineService, providing an interface and a unique string identifier. The string is used for runtime identity and debugging.
```ts
import { defineService } from '@yatoi/kernel'
export interface TodoStore {
getAll(): readonly Todo[]
add(title: string): void
subscribe(listener: () => void): () => void
}
export const Todos = defineService('todos')
```
--------------------------------
### Use with a named slot and attrs
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Renders a slot by name, passing its props as plain attrs. The slot's own props (e.g., task) are read from attrs because Vue can't spread typed props like JSX.
```html
```
--------------------------------
### Wrong vs. right: module-evaluation side effects in lazy-loaded modules
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/pitfalls.md
Shows the incorrect pattern of calling `kernel.load` at module evaluation time in a module loaded via `React.lazy`, which runs during render and causes kernel notifications to change snapshots mid-render. The correct pattern is to export a plugin definition and let a handler or effect load it.
```ts
// wrong — runs during React.lazy's render-time import
kernel.load(spellcheckPlugin)
export default SpellcheckPanel
```
```ts
// right — the module exports the plugin, a handler or effect loads it
export default definePlugin({ name: 'spellcheck', setup(scope) { … } })
```
--------------------------------
### Check scope.active after await and defer cleanup early
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/pitfalls.md
After an async await, check scope.active to avoid resource leaks; close the socket and return if the scope is disposed. Better to defer cleanup before the await when possible.
```typescript
const socket = await connect()
if (!scope.active) { socket.close(); return }
scope.defer(() => socket.close())
```
--------------------------------
### Avoid useService with non-null assertion
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/pitfalls.md
Shows the anti-pattern of using the non-null assertion operator on the result of useService. The service can disappear while the component is mounted, causing a crash on the next render. Use instead, which unmounts the subtree cleanly and provides a genuinely non-null argument.
```tsx
const clock = useService(Clock)! // don't
```
--------------------------------
### Override a built-in slot by wrapping
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/design.md
Overrides the 'task.card' slot by wrapping the default renderer with a `Highlighted` component. Uses `mode: 'wrap'` to preserve the default.
```tsx
contribute(scope, 'task.card', ({ task, Default }) =>
, { mode: 'wrap' })
```
--------------------------------
### Capture kernel errors
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/guide.md
Use this snippet to capture kernel errors instead of letting them hit the console. It subscribes to the 'error' event and pushes errors into an array.
```TypeScript
const errors: unknown[] = []
kernel.on('error', (e) => errors.push(e))
```
--------------------------------
### Plugin contributes renderer to slot
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/concepts.md
The plugin contributes a renderer to the slot using the contribute function. Because contribute is a scope effect, uninstalling the plugin removes its UI automatically.
```tsx
// plugin
contribute(scope, 'todo.item.extra', ({ todo }) => )
```
--------------------------------
### Requires component for absence boundary
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/design.md
Uses the `Requires` component to provide non-null service instances to its render prop, with a fallback when services are unavailable. If a service goes away, the subtree unmounts and runs its own effect cleanups.
```tsx
}>
{(clock, storage) => }
```
--------------------------------
### Slot type declaration
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/pitfalls.md
Declares the Slots interface in a module augmentation. Must be in the program for both the host and every plugin that contributes; import the file from your entry point.
```typescript
declare module '@yatoi/slots' {
interface Slots { … }
}
```
--------------------------------
### Slot type augmentation
Source: https://github.com/yatoi-dev/yatoi-js/blob/main/docs/design.md
Declares the slot contract by augmenting the `@yatoi/slots` module. This is done once regardless of which binding (React or Vue) renders the slots.
```ts
declare module '@yatoi/slots' {
interface Slots {
'sidebar.item': { collapsed: boolean }
'task.card': { task: Task }
}
}
```
=== COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.