02Guides
Read and write models
readOnly and writeOnly properties: separate request and response models.
Many APIs describe one schema for both directions and mark the differences: readOnly properties are set by the server (an id, a createdAt), writeOnly ones are only sent (a password). A single TypeScript type for both is wrong both ways: requests would demand the id the server assigns, and responses would promise a password they never contain.
With types.readWrite: 'split' (the default), swaggerz generates two types where the specification says they differ:
import { createAccount, type Account, type AccountInput } from './generated/api';
const input: AccountInput = { name: 'Ada', password: 'correct horse battery staple' };const created: Account = await createAccount({ body: input });console.log(created.id); // set by the server; `password` is not part of the response type- The response type (
Account) keepsreadOnlyproperties, asreadonly(seetypes.readonlyProperties), and leaves outwriteOnlyones. - The request type (
AccountInput, named withnaming.inputSuffix) leaves outreadOnlyproperties and keepswriteOnlyones.
When a variant is created
Section titled “When a variant is created”A model gets an …Input variant only when it has readOnly or writeOnly properties, directly or through the models it contains, and something sends it in a request. Models without such properties, and models that only travel in one direction, are generated once, so most specifications see no extra types.
Webhook and callback payloads use the inverted directions: the API sends the payload, and your handler’s response goes back.
Turning it off
Section titled “Turning it off”types.readWrite: 'merged' generates one type per schema, as most generators do. Use it when the specification marks properties inconsistently and the split types get in the way.
Limitations
Section titled “Limitations”- The setting applies to the whole client; a single model cannot opt out.
- Validation schemas are split the same way (
AccountInputSchema), so a request body is validated without itsreadOnlyproperties.
What's generated
openapi: 3.1.0info: title: Accounts version: '1.0'paths: /accounts: post: tags: [accounts] operationId: createAccount requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Account' responses: '201': description: The new account. content: application/json: schema: $ref: '#/components/schemas/Account'components: schemas: Account: type: object required: [id, name, password] properties: id: type: string readOnly: true name: type: string password: type: string writeOnly: truemodels/account.ts
/* eslint-disable */// @generated by swaggerz. Do not edit: this file is overwritten on the next generation.
export type Account = { readonly id: string; name: string;};models/account-input.ts
/* eslint-disable */// @generated by swaggerz. Do not edit: this file is overwritten on the next generation.
export type AccountInput = { name: string; password: string;};accounts/create-account.ts
/* eslint-disable */// @generated by swaggerz. Do not edit: this file is overwritten on the next generation.
import { type RequestOptions, client } from '../client.js';import type { AccountInput } from '../models/account-input.js';import type { Account } from '../models/account.js';import { type ApiError, type RequestError, isApiError } from '../runtime/errors.js';import type { HttpStatus } from '../runtime/types.js';
/** Request body of {@link createAccount} (`application/json`). */export type CreateAccountBody = AccountInput;
/** Parameters of {@link createAccount}. */export type CreateAccountParams = { /** Request body. */ body: CreateAccountBody;};
/** Response data of {@link createAccount}. */export type CreateAccountResponse = Account;
/** Body of an error response of {@link createAccount}. */export type CreateAccountErrorBody = unknown;
/** * The `ApiError`s {@link createAccount} rejects with: one per documented status, so checking `status` narrows `body`. * * The last member covers statuses the specification does not document. */export type CreateAccountApiError = ApiError<unknown, HttpStatus<1 | 3 | 4 | 5>>;
/** Everything {@link createAccount} rejects with: an `ApiError` for an error status, or a `RequestError` when no usable response arrived. */export type CreateAccountError = CreateAccountApiError | RequestError;
/** * Calls `POST /accounts`. * * @remarks `POST /accounts` (operationId `createAccount`) * @param params - Request body. * @param options - Per-request options: `signal`, `timeout`, `headers`, `client`. * @returns `201` The new account * @throws {@link ApiError} When the server responds with a non-2xx status; narrow it with {@link isCreateAccountApiError}. * @throws {@link RequestError} When no usable response is received (network failure, timeout or invalid body). */export function createAccount(params: CreateAccountParams, options?: RequestOptions): Promise<CreateAccountResponse> { return (options?.client ?? client).request<CreateAccountResponse>( { operationId: 'createAccount', method: 'POST', path: '/accounts', body: params.body, mediaType: 'application/json', accept: 'application/json', }, options, );}
/** Whether `error` is an `ApiError` rejected by {@link createAccount}. It narrows to {@link CreateAccountApiError}, so checking `status` afterwards narrows `body` too. */export function isCreateAccountApiError(error: unknown): error is CreateAccountApiError { return ( isApiError(error) && error.operationId === 'createAccount' && error.method === 'POST' );}