@brickkit/core/client
The typed HTTP client a frontend uses to call a Brick application, inferred from its contract.
@brickkit/core/client exports 14 names: 3 functions, 2 classes, 3 interfaces, 6 types.
import { BrickServerError, BrickTransportError, createBrickClient } from "@brickkit/core/client";Functions
| Function | Description |
|---|---|
createBrickClient | Create an Eden/tRPC-style typed client for the application contract TApp. |
isBrickServerError | Whether an error from the typed client is a BrickServerError: the server answered with a 5xx that is not one of the contract's declared errors, and the error… |
isBrickTransportError | Whether an error from the typed client is a BrickTransportError: the request got no meaningful answer from the server (a network failure, a body that was not… |
createBrickClient
function createBrickClient<TApp extends Record<string, any>>(
options: BrickClientOptions & { contract: TApp },
): BrickClient<TApp>
function createBrickClient<TApp extends Record<string, any>>(
options: BrickClientOptions,
): BrickClient<TApp>Create an Eden/tRPC-style typed client for the application contract TApp.
TApp is a type-only parameter: import type { AppServer } keeps backend
implementations out of the frontend bundle.
Parameters
| Name | Type | Description |
|---|---|---|
options | BrickClientOptions & { contract: TApp } |
Returns BrickClient<TApp>
Source: packages/core/src/client.ts:572
isBrickServerError
function isBrickServerError(err: unknown): err is BrickServerErrorWhether an error from the typed client is a BrickServerError: the server
answered with a 5xx that is not one of the contract's declared errors, and the
error carries its status, url, and body.
Parameters
| Name | Type | Description |
|---|---|---|
err | unknown | What a client call threw. |
Returns err is BrickServerError — true for a server error, narrowing its type.
Source: packages/core/src/client.ts:187
isBrickTransportError
function isBrickTransportError(err: unknown): err is BrickTransportErrorWhether an error from the typed client is a BrickTransportError: the request
got no meaningful answer from the server (a network failure, a body that was
not JSON, or a 4xx outside the declared contract).
Parameters
| Name | Type | Description |
|---|---|---|
err | unknown | What a client call threw. |
Returns err is BrickTransportError — true for a transport error, narrowing its type.
Source: packages/core/src/client.ts:173
Classes
BrickServerError
class BrickServerError extends ErrorThe server answered 5xx without a declared code / ValidationError shape,
i.e. an unexpected failure rather than a modelled domain error.
Constructor
| Name | Type | Description |
|---|---|---|
constructor | new (message: string, opts: { status: number; url: string; body: unknown }) |
Properties
| Name | Type | Description |
|---|---|---|
readonly kind | "server" | |
readonly status | number | |
readonly url | string | |
readonly body | unknown |
Source: packages/core/src/client.ts:147
BrickTransportError
class BrickTransportError extends ErrorThe request never received a meaningful server answer: DNS / connection
failure, a malformed body, or a 4xx response outside the declared contract
(no code and no ValidationError shape).
Constructor
| Name | Type | Description |
|---|---|---|
constructor | new ( message: string, opts: { status?: number; url: string; body?: unknown; cause?: unknown }, ) |
Properties
| Name | Type | Description |
|---|---|---|
readonly kind | "transport" | |
readonly status? | number | |
readonly url | string | |
readonly body? | unknown | |
readonly cause? | unknown |
Source: packages/core/src/client.ts:123
Interfaces
BrickCallOptions
interface BrickCallOptionsProperties
| Name | Type | Description |
|---|---|---|
headers? | Record<string, string> | Extra headers merged over the client defaults for this call only. |
signal? | AbortSignal | Abort signal for this call only. Never wrapped: aborts reject with AbortError. |
timeoutMs? | number | Per-call timeout in milliseconds. Implemented with AbortSignal.timeout. |
Source: packages/core/src/client.ts:109
BrickClientAction
interface BrickClientAction<TInput = unknown, TOutput = unknown, TErrors = {}>Browser-safe action metadata generated by the Brick Vite plugin.
Properties
| Name | Type | Description |
|---|---|---|
kind | "action" | |
name | string | |
serviceName | string | |
plugin? | string | Owning plugin descriptor name for plugin actions (e.g. @brickkit/crud). |
resource? | string | Owning resource name for resource-plugin actions (e.g. page). |
config | { path: string; method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" } | |
readonly __types? | { input: TInput; output: TOutput; errors: TErrors } | Type information only; never emitted at runtime. |
Source: packages/core/src/client.ts:92
BrickClientOptions
interface BrickClientOptionsEden/tRPC-style typed HTTP client inferred from Brick-TS application contracts.
The frontend imports only the contract type — never a backend implementation:
import { createBrickClient } from "@brickkit/core/client";
import type { AppServer } from "./server"; // `import type`: erased at build
const api = createBrickClient<AppServer>({ baseUrl: "http://localhost:4000" });
const pages = await api.page.list({ limit: 10, sort: "-createdAt" });
const created = await api.page.create({ title: "Hello", content: "World" });Build the contract from individually-typed actions (each defineAction call
preserves its input/output/error literal types):
// server.ts (backend)
export const listPages = pagesService.action({ name: "page.list", ... });
export const createPage = pagesService.action({ name: "page.create", ... });
export const contract = { page: { list: listPages, create: createPage } };
export type AppServer = typeof contract;Result / error convention
The client uses a throw-based convention that mirrors local action.execute
semantics, because TypeScript promises cannot encode thrown exception types:
- Success → the promise resolves with the action output.
- Declared domain failures (your
errors: { SLUG_EXISTS: ... }) and framework guards (UNAUTHORIZED/FORBIDDEN/NOT_FOUND/...) → rejected withActionExecutionErrorcarrying the declaredcode,status,message, anddetails. Narrow withinstanceofand readerr.code. - Input/output validation failures → rejected with
ValidationError, exactly as local execution throws. - Network failures, unparsable bodies, and non-OK responses outside the
declared contract →
BrickTransportError(4xx) orBrickServerError(5xx). - Cancellation via
AbortSignal→ the nativeAbortErrorpropagates unwrapped soerr.name === "AbortError"keeps working.
URL mapping
A client path is a namespace walk ending in an action call. The leaf segment
decides the route, matching the server compiler (packages/cli/src/compiler):
| Client call | HTTP request |
|---|---|
| Generated browser contracts carry each action's HTTP method and path, | |
| including plugin routes and consumer overrides. Without runtime metadata, | |
callers use POST /api/<service>/<action> with dotted action names. | |
| GET/DELETE input is serialized as a query string; other methods send JSON. |
Properties
| Name | Type | Description |
|---|---|---|
baseUrl | string | Server origin, e.g. "http://localhost:4000". No trailing slash required. |
prefix? | string | Route prefix the server was mounted with (default "/api"). Must match the prefix passed to brick({ prefix }). |
headers? | | Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>) | Default headers sent with every call (e.g. an Authorization bearer). A thunk is re-evaluated per call, so rotated tokens stay fresh. |
fetch? | (input: RequestInfo | URL, init?: RequestInit) => Promise<Response> | fetch implementation. Defaults to globalThis.fetch. |
signal? | AbortSignal | Default signal applied to every call (per-call signal wins). |
timeoutMs? | number | Default per-call timeout in milliseconds (per-call timeoutMs wins). |
contract? | Record<string, any> | Browser-safe route metadata generated by the Brick Vite plugin. Required for custom methods/paths and plugin routes. Without a contract, calls use POST and service/action names inferred from namespace keys. |
Source: packages/core/src/client.ts:62
Types
BrickActionCaller
type BrickActionCaller<A> =
undefined extends InferActionInput<A>
? (
input?: InferActionInput<A>,
options?: BrickCallOptions,
) => Promise<InferActionOutput<A>>
: (
input: InferActionInput<A>,
options?: BrickCallOptions,
) => Promise<InferActionOutput<A>>;Typed caller for one action. Required inputs stay required; actions whose
input includes undefined (no schema / all-optional) can be called bare.
Source: packages/core/src/client.ts:259
BrickClient
type BrickClient<T> = T extends
| BrickClientAction<any, any, any>
| Action<any, any, any, any>
? BrickActionCaller<T>
: T extends (...args: any[]) => any
? BrickActionCaller<T>
: T extends object
? { [K in keyof T]: BrickClient<T[K]> }
: T;Recursively maps action leaves and their namespaces to browser callers.
Source: packages/core/src/client.ts:271
InferActionErrorCodes
type InferActionErrorCodes<A> =
A extends BrickClientAction<any, any, infer E>
? Extract<keyof E, string>
: A extends {
config: { errors?: infer E };
}
? E extends Record<string, ActionErrorDefinition>
? Extract<keyof E, string>
: never
: never;Union of error codes declared in an action's errors map.
Source: packages/core/src/client.ts:220
InferActionFailure
type InferActionFailure<A> = ActionExecutionError & {
code:
| InferActionErrorCodes<A>
| "BAD_REQUEST"
| "UNAUTHORIZED"
| "FORBIDDEN"
| "NOT_FOUND"
| "CONFLICT"
| "INTERNAL_SERVER_ERROR"
| "VALIDATION_FAILED";
};The declared failure shape of an action for catch narrowing:
try { await api.page.create(input); }
catch (err) {
if (err instanceof ActionExecutionError) {
const code: InferActionErrorCodes<typeof createPage> | "UNAUTHORIZED" | ... = err.code;
}
}(Standard framework codes are always possible alongside declared ones.)
Source: packages/core/src/client.ts:243
InferActionInput
type InferActionInput<A> =
A extends BrickClientAction<infer I, any, any>
? I
: A extends {
config: { input?: infer S extends TSchema };
}
? Static<S>
: A extends {
execute(params: { input?: infer I; ctx?: any }): any;
}
? I
: A extends { run(input?: infer I, ctx?: any): any }
? I
: A extends (params: { input?: infer I; ctx?: any }) => any
? I
: unknown;Static input type from the schema, with a fallback for structural actions.
Source: packages/core/src/client.ts:192
InferActionOutput
type InferActionOutput<A> =
A extends BrickClientAction<any, infer O, any>
? O
: A extends (...args: any[]) => Promise<infer O>
? O
: A extends { execute(params: any): Promise<infer O> }
? O
: unknown;Resolved output type of an action.
Source: packages/core/src/client.ts:210