Schema Composition

Schema composition in Typeweaver is mostly just Zod composition. The goal is not magic. The goal is to reuse useful pieces without making the operation contract harder to read.

Start with plain Zod schemas

const todoSchema = z.object({
  id: z.string(),
  title: z.string(),
  completed: z.boolean(),
  createdAt: z.string(),
  updatedAt: z.string(),
});

Once you have a base schema, you can derive request and response shapes from it.

Common composition patterns

.pick(...)

Use .pick(...) when an operation only needs a subset of fields.

const todoPreviewSchema = todoSchema.pick({
  id: true,
  title: true,
  completed: true,
});

.omit(...)

Use .omit(...) when some fields should not appear in a request body.

const createTodoBodySchema = todoSchema.omit({
  id: true,
  createdAt: true,
  updatedAt: true,
});

.extend(...)

Use .extend(...) when you want to add a few operation-specific fields.

const todoWithOwnerSchema = todoSchema.extend({
  ownerName: z.string(),
});

.merge(...)

Use .merge(...) when you have two object schemas that naturally belong together.

const auditSchema = z.object({
  createdBy: z.string(),
  updatedBy: z.string(),
});

const todoWithAuditSchema = todoSchema.merge(auditSchema);

...schema.shape

Sometimes spreading shape is the clearest option.

const createTodoBodySchema = z.object({
  ...todoSchema.omit({
    id: true,
    createdAt: true,
    updatedAt: true,
  }).shape,
  notifyAssignee: z.boolean().optional(),
});

Using composed schemas in operations

const CreateTodoDefinition = defineOperation({
  operationId: "CreateTodo",
  summary: "Create todo",
  method: HttpMethod.POST,
  path: "/todos",
  request: {
    body: createTodoBodySchema,
  },
  responses: [
    defineResponse({
      name: "CreateTodoSuccess",
      statusCode: HttpStatusCode.CREATED,
      description: "Todo created",
      body: todoSchema,
    }),
  ],
});

Typeweaver simply uses the final Zod schemas you pass in. Generated types and validators reflect those composed shapes.

Keep composition readable

Composition helps when it removes duplication. It hurts when readers have to jump through too many layers to understand one operation.

As a rule of thumb:

  • reuse stable domain shapes
  • keep operation-specific schemas near the operation
  • stop abstracting when the contract becomes harder to read

Was this page helpful?