defineCapability()
defineCapability() returns a fluent CapabilityBuilder you compose into
a runtime capability — the same shape the kernel resolves when a Surface
opts in to a behaviour like data fetching, routing, or input handling.
Every capability ships with a stable id, a Zod-typed input/output
contract, an executor function, and an optional config schema the
World owner fills in.
import { defineCapability } from '@pwfabric/sdk'
import { defineContract } from '@pwfabric/core'
import { z } from 'zod'
export const SearchCapability = defineCapability()
.id('app/search')
.version('1.0.0')
.name('Full-text search')
.description('Searches the indexed content for a query.')
.state('stable')
.contract(
defineContract({
input: z.object({ query: z.string().min(1) }),
output: z.object({ hits: z.array(z.string()) }),
}),
)
.execute(async (input, ctx) => {
const hits = await ctx.services.search.run(input.query)
return { hits }
})
.build()Signature
function defineCapability<TInput = unknown, TOutput = unknown>():
CapabilityBuilder<TInput, TOutput>CapabilityBuilder is a chainable builder; call .build() last to
produce a Capability ready to register with the runtime.
Builder methods
Required
| Method | Purpose |
|---|---|
.id(id: string) | Stable capability id (e.g. 'platform/persistence'). |
.name(name: string) | Human-readable label shown in the Studio and PhiCo. |
.contract(contract) | Zod input/output contract created with defineContract(). |
.execute((input, ctx) => Promise<output>) | The capability’s actual implementation. |
Calling .build() without an id, name, contract, or executor throws
immediately — those four are validated at build time.
Optional metadata
| Method | Default | Purpose |
|---|---|---|
.version(version: string) | '0.1.0' | Semver string — bump major for input/output shape changes. |
.description(text) | — | Markdown-friendly description shown in capability listings. |
.state(state) | 'draft' | Lifecycle marker — 'draft' | 'preview' | 'stable' | 'deprecated'. |
.domain(domain) | — | Domain id for grouping capabilities (e.g. 'platform', 'commerce'). |
.tags(tags) | — | Free-form tags for discovery. |
.timeout(ms) | — | Hard timeout the runtime enforces around execute. |
.retryable(value?) | false | Marks the capability safe to retry on transient failure. |
.idempotent(value?) | false | Marks repeated invocations with the same input as side-effect free. |
Wiring
| Method | Purpose |
|---|---|
.dependsOn(id) | Declare another capability this one calls into. The runtime resolves and orders dependencies. |
.configSchema(schema) | Per-World configuration the customer fills in (ADR-033). |
Validation
Both the input and the output go through the contract on every
invocation — validation errors short-circuit the executor and surface as
typed errors to the caller. Don’t re-validate the same shape inside
execute; let the contract do its job.
import { defineCapability } from '@pwfabric/sdk'
import { defineContract } from '@pwfabric/core'
import { z } from 'zod'
const QueryInput = z.object({
collection: z.string().min(1),
filter: z.record(z.unknown()).optional(),
limit: z.number().int().min(1).max(100).default(20),
})
const QueryOutput = z.object({
rows: z.array(z.record(z.unknown())),
totalCount: z.number().int().nonnegative(),
})
export const QueryCapability = defineCapability()
.id('app/query')
.version('1.0.0')
.name('Collection query')
.contract(defineContract({ input: QueryInput, output: QueryOutput }))
.execute(async (input, ctx) => {
return ctx.services.collections.query(input)
})
.build()Dependencies
Declare every capability you call into. The runtime resolves the dependency graph and rejects circular references:
defineCapability()
.id('app/checkout')
.name('Checkout flow')
.dependsOn('platform/persistence')
.dependsOn('platform/input')
.dependsOn('app/payments')
.contract(/* ... */)
.execute(/* ... */)
.build()Per-World configuration
Capabilities that need customer-supplied config (API keys, feature
flags, defaults) declare a configSchema:
import { defineCapability } from '@pwfabric/sdk'
import { defineConfigSchema } from '@pwfabric/core'
defineCapability()
.id('integrations/stripe')
.name('Stripe')
.configSchema(
defineConfigSchema()
.field('publishableKey', (f) => f.string().required().label('Publishable key'))
.field('webhookSecret', (f) => f.string().required().label('Webhook secret'))
.field('currency', (f) => f.string().default('USD').label('Currency'))
.build(),
)
.contract(/* ... */)
.execute(async (input, ctx) => {
// World-supplied config is resolved through the kernel — see the
// capabilities concept page for the full resolution model.
})
.build()Publishing the capability
Capabilities ship inside a pwpack alongside blocks and starters. Drop
your defineCapability(...) modules into the pack’s capabilities/
folder, then pwfabric pwpack publish delivers the bundle. After the
World installs the pack, the kernel registers the capability on first
use (5-minute LRU cache).
See also
- Capabilities concept — the canonical 5 platform capabilities and the resolution model
defineBlock()— author a single block typedefineFactory()— generate a composed block tree from configuration