DevelopersSDKdefineCapability()

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

MethodPurpose
.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

MethodDefaultPurpose
.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?)falseMarks the capability safe to retry on transient failure.
.idempotent(value?)falseMarks repeated invocations with the same input as side-effect free.

Wiring

MethodPurpose
.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