-
-
Notifications
You must be signed in to change notification settings - Fork 0
Server Request Schemas
Effuse provides a framework-owned schema vocabulary for server params, query, headers, JSON, and form data. Application code does not import Effect or install a second schema package.
import {
defineLayer,
defineServerRequest,
serverSchema,
type ServerSchemaInput,
type ServerSchemaOutput,
} from '@effuse/core/server';
const SearchQuery = serverSchema.object({
filter: serverSchema.string,
page: serverSchema.optional(serverSchema.numberFromString, 1),
});
type SearchQueryInput = ServerSchemaInput<typeof SearchQuery>;
// { filter: string; page?: string }
type SearchQueryOutput = ServerSchemaOutput<typeof SearchQuery>;
// { filter: string; page: number }Input represents values accepted at the boundary. Output represents decoded values delivered to the handler.
const SearchLayer = defineLayer({
name: 'search',
server: {
api: {
'/api/search': ({ validate }) => {
const query = validate.query(SearchQuery);
return { filter: query.filter, page: query.page };
},
},
},
});The same schema works with validate.params, validate.query,
validate.headers, validate.json, validate.formData, and validate.value.
The body helpers are asynchronous because they consume the request body.
defineServerRequest() groups reusable request boundaries without replacing the
handler context or its layer-service inference:
const UpdateUserRequest = defineServerRequest({
params: serverSchema.object({ id: serverSchema.string }),
query: serverSchema.object({
notify: serverSchema.optional(serverSchema.booleanFromString, false),
}),
json: serverSchema.object({ name: serverSchema.string }),
});
const UserLayer = defineLayer({
name: 'users',
server: {
api: {
'/api/users/[id]': {
PATCH: async (context) => {
const input = await UpdateUserRequest.parse(context);
return { id: input.params.id, ...input.json };
},
},
},
},
});A contract may declare json or formData, never both. This is enforced by
TypeScript and at runtime so parsing cannot consume a request body twice. The
frozen schemas property remains inspectable for future route-manifest and
client generation work.
Current combinators are string, number, boolean, unknown,
numberFromString, booleanFromString, dateFromString, literal, union,
array, object, and optional.
optional(schema, defaultValue) makes the encoded field optional while making
the decoded field required. optional(schema) produces an optional encoded
field and a decoded value that may be undefined.
Invalid input returns HTTP 400 with a stable body:
{
"error": {
"code": "EFFUSE_VALIDATION_FAILED",
"issues": [{ "message": "...", "path": "page" }],
"message": "Request validation failed.",
"source": "query"
}
}Do not expose raw parser causes to clients. Log internal causes through server observability hooks when diagnostics are required.
Native schemas, manual validate.* calls, and reusable defineServerRequest()
contracts are current and tested.
Declarative route request/response/error contracts and generated client result
types remain tracked in
#250.