{createProduct.error.message}
: null} {createProduct.data ?Created {createProduct.data.name}
: null}{result.data?.message}
; } ``` `api` resolves the current request and that app's registered routes at call time. It works in Farm server pages, queries, actions, and API handlers in development and the default universal production runtime. Constructing the pair at module scope is safe. Calling `api` in the browser or outside an active Farm request throws an actionable error; it never silently falls back to HTTP. The local caller uses the app's server API mount, including custom base paths. A public `baseURL` pointing at another origin affects `apiClient`, not local dispatch. The current request's `Cookie`, `Authorization`, and `Accept-Language` headers are inherited; shared options and per-call headers can override them. Other headers are not implicitly forwarded. Do not place private tokens in the shared module: read them in server code and pass them per call where needed. Local calls use the same route matching, params, input/output validation, endpoint middleware, body limits, and response decoding as HTTP routes. They still serialize request/response data; they avoid a network round trip, not all serialization. Request cancellation is inherited. Opt-in caches and in-flight requests are isolated by request and credentials, never shared between users. Dynamic `$params()` scopes work identically for both callers. ### Direct-call boundaries `api` is a direct endpoint caller, **not a replay of the full HTTP request pipeline**. It does not run path-level HTTP middleware, redirects/rewrites, plugin request/response lifecycle hooks, or deployment-layer checks. Put authorization and other rules required for both transports in the endpoint's `middleware`. Use `apiClient` when an operation must go through HTTP middleware. Response cookies and headers from a local endpoint do not automatically become headers on the outer page response. Post-response work scheduled by an endpoint uses the enclosing request's lifecycle. Integrations remain available under `api.integrations` and `apiClient.integrations`, using their existing integration-dispatch semantics. ### Existing factories `createAPIClient()` remains supported for an HTTP-only caller, including standalone scripts with an absolute `baseURL`. `createServerAPIClient({ hello: { get: GET } })` remains supported for explicit endpoint-function maps in server-only modules. It returns the supplied functions and their raw results; passing only `{ request }` does **not** discover routes. Prefer the paired factory for a shared module and a consistent `{ data, error, key }` app-route result. The HTTP-only factory retains the same route and integration inference: ```ts import { createAPIClient } from "@farm.js/core/client"; import type { APIRouter } from "./api.generated"; import type { AppIntegrations } from "./integrations"; const http = createAPIClient{{ error }}
Loading cart
``` Wrap server data access with `createServerOnlyFn` from `@farm.js/core/environment` so a module imported by a route that later hydrates cannot ship its implementation to the browser. `useServerQuery` and `useAction` from `@farm.js/vue/bindings` exist, but calling a server query or server function from browser code requires the server-function transform (`@farm.js/plugin/rsc` with `experimental.serverActions: true`). That path is not documented for Vue; prefer API routes plus `apiClient`. Do not import `@farm.js/core/server-query/client` in Vue code: it imports React. ## State | Nuxt | Farm | Status | | ------------------------------- | ---------------------------------------------------------------------------------------------------- | ----------- | | `useState(key, init)` | None. Browser-only shared state: a module-level `ref` in a composable used by hydrated routes | `none` | | server-to-client state transfer | None generic; pass client-safe values through middleware `context.data` or fetch after mount | `none` | | Pinia via `@pinia/nuxt` | `app.use(createPinia())` in the app setup module (`vue({ app })`); stores start fresh in the browser | `different` | | `provide`/`inject` (Vue) | `provide()` in a layout's `{{ message }}
Reload