Skip to main content

Surfaces and Wiring

A surface is a transport medium through which entrypoints are reached. The wiring section of your spec maps each surface's native concepts to your typed entrypoints.

Why this exists

Without a formal surface model, business logic leaks into transport handlers. Each surface re-implements its own input binding, access enforcement, and error translation. Adding a new surface requires duplicating handler code.

In Gooi, your domain, queries, and mutations know nothing about surfaces. The wiring section is the only place where surfaces exist. Adding a new surface requires adding to wiring and nothing else.

The four runtime surfaces

Gooi supports four runtime surfaces. Each surface delivers invocations to runtime.invoke() by mapping its native concepts to typed entrypoint inputs.

SurfaceWhat it mapsTypical use
httpHTTP method + path + query/body paramsREST APIs
webBrowser events and state pathsBrowser applications
cliCommand paths and flagsDeveloper tooling
webhookHTTP POST payload fieldsEvent receivers

Wiring an HTTP surface

wiring:
surfaces:
http:
queries:
list_messages:
method: GET
path: /messages
bind:
q: query.q
sort_by: query.sort_by
sort_order: query.sort_order
page: query.page
page_size: query.page_size
mutations:
submit_message:
method: POST
path: /messages
bind:
message: body.message
delete_message:
method: DELETE
path: /messages/:id
bind:
id: path.id

The bind block maps transport buckets (query.*, body.*, path.*) to typed entrypoint input fields. This is input binding only. Access enforcement and business logic are not declared here.

Wiring a CLI surface

wiring:
surfaces:
cli:
queries:
list_messages:
command:
path: messages list
bind:
q: args.query
sort_by: flags.sort-by
page: flags.page
list_messages_with_authors:
command:
path: messages list
when:
flags:
enriched: true
bind:
q: args.query
mutations:
submit_message:
command:
path: messages send
bind:
message: args.message

CLI commands use explicit path and optional when.flags conditions to distinguish overlapping command shapes. Avoid sharing a command path across two queries without a when condition.

Wiring a web surface

wiring:
surfaces:
web:
routes:
view_home:
bind:
search_query: query.q
sort_by: query.sort_by
page: query.page
mutations:
submit_message:
bind:
message: body.message

Web surface bindings map URL query parameters and request body fields to entrypoint inputs. View nodes reference queries by name for data binding; that binding is declared in views, not here.

What wiring cannot do

These constraints are intentional.

  • Wiring cannot reference providers by name. Supabase, Redis, S3: none of these appear in wiring. Provider selection is a deploy-time concern committed to the lockfile.
  • Wiring cannot implement business logic. The bind block maps transport fields to entrypoint inputs. It does not filter, transform, or compute values.
  • Wiring cannot override access rules. Access is declared on each entrypoint in queries and mutations. The wiring section has no authority over access.
warning

A spec with provider names or resource identifiers inside wiring is malformed. The compiler emits a DEPLOY_CONCERN_IN_SPEC error for this.

What's next