place

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

ts
defineCapability<T>(name: string, options?: DefineCapabilityOptions): Capability<T>

Basic

ts
import { defineCapability } from '@place/capability' interface Logger { info(msg: string): void warn(msg: string): void } export const LoggerCap = defineCapability<Logger>('Logger')

clientOnly

ts
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()

ts
// In a component body: const logger = LoggerCap.use() // throws if unprovisioned const logger = LoggerCap.tryUse() // returns null if unprovisioned

install() — at the app level

ts
// 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

ts
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).

See also