defineCapability()
Declares a typed slot. The returned Capability has .use(), .tryUse(), .provide(), and .install() methods. install(impl) returns a disposer — call it to remove that installation; there is no uninstall.
Signature
defineCapability<T>(name: string, options?: DefineCapabilityOptions): Capability<T>
Basic
import { defineCapability } from '@place/capability'
interface Logger {
info(msg: string): void
warn(msg: string): void
}
export const LoggerCap = defineCapability<Logger>('Logger')
clientOnly
export const NoteStoreCap = defineCapability<NoteStore>('NoteStore', {
clientOnly: true, // touching during SSR throws ClientOnlyAbort;
// component machinery catches it and emits a
// placeholder span instead of crashing.
})
Marks the cap as browser-only. SSR .use() calls throw a special ClientOnlyAbort that the component machinery intercepts — the component renders as a <span data-place-auto> placeholder, and the real body mounts at hydration. No typeof window guards needed in consumer code.
use() and tryUse()
// In a component body:
const logger = LoggerCap.use() // throws if unprovisioned
const logger = LoggerCap.tryUse() // returns null if unprovisioned
install() — at the app level
// app() install:
caps: [[LoggerCap, () => console]]
// Or imperatively (rare): install(impl) RETURNS a disposer.
// Call the disposer to remove that specific installation —
// there is no .uninstall() method.
const dispose = LoggerCap.install(myLogger)
// ...later, to tear it down:
dispose()
Each install(impl) call is tracked by a unique token, so the returned disposer removes exactly that installation regardless of stack order. Disposing twice is a no-op.
Scoped provision
import { withCapability } from '@place/component'
// Provision for a single subtree:
withCapability(LoggerCap, scopedLogger, <Subtree />)
Limits the provision to a subtree. Outside the subtree, .use() falls back to whatever's installed at the app level (or throws).