Files
suaveplan/packages/api-stack/api/api-plugin-events/src/schemas/event-schemas.md
2026-05-27 23:29:53 +01:00

106 lines
3.7 KiB
Markdown

# event-schemas
## Purpose
The package-local Zod schemas for the `event` entity surface. Owns
every input / output shape touching the events routes and
repositories: the persisted `EventSchema`, the create / update
request bodies, the list-query parameters, the path param shape,
the `EventLocationSchema` value object, and the `EventStatusSchema`
0..2 integer encoding. The `@suaveplan/api-schemas` package
ships only event-bus envelope schemas (`DomainEvent`,
`EventEnvelope`, `EventMetadata`); the event-management entity
schemas are owned here because no other package needs them.
The schemas double as the type sources — every exported `*Schema`
has a matching `type Foo = z.infer<typeof FooSchema>` so consumers
get a single source of truth for both runtime validation and
compile-time narrowing. The `CreateEventSchema` and
`UpdateEventSchema` carry a `.refine()` constraint that asserts
`end_time > start_time` at the parser boundary so the repository
never sees a malformed time pair.
## Features
- 11 schemas spanning persistence, request, response, and value
objects.
- `CreateEventSchema` / `UpdateEventSchema` enforce `end_time > start_time`
via `.refine()`.
- `EventStatusSchema` constrains status to `0..2` (draft / active /
cancelled) matching the upstream encoding.
- `EventQuerySchema` coerces query-string `limit` / `offset` /
`upcoming` / `past` to typed numbers / booleans.
- `EventLocationSchema` validates `(lat, lng)` coordinates against
geographic bounds.
## Usage: Basic Example
```ts
import { CreateEventSchema, EventSchema } from "@suaveplan/api-plugin-events";
const parsed = CreateEventSchema.parse({
title: "Meetup",
start_time: "2026-06-01T18:00:00Z",
end_time: "2026-06-01T22:00:00Z",
});
console.log(parsed.timezone); // "UTC"
```
## Usage: Advanced Example
```ts
import {
EventQuerySchema,
EventListResponseSchema,
UpdateEventSchema,
} from "@suaveplan/api-plugin-events";
// Coerce from URL search params
const url = new URL("https://api.example.com/events?limit=10&past=true");
const query = EventQuerySchema.parse(Object.fromEntries(url.searchParams));
// Build a typed response payload
const body = EventListResponseSchema.parse({
data: [
/* events from repository */
],
total: 42,
});
// Forbid time inversion at the boundary
const result = UpdateEventSchema.safeParse({
start_time: "2026-06-01T22:00:00Z",
end_time: "2026-06-01T18:00:00Z",
});
if (!result.success) {
// result.error.issues[0].path === ["end_time"]
}
```
## API Reference
- `EventStatusSchema: z.ZodNumber` — integer 0..2.
- `EventLocationSchema: z.ZodObject` — `{ name, address?, coordinates? }`.
- `AttendeeCountSchema: z.ZodObject` — `{ going, maybe, invited }`.
- `EventSchema: z.ZodObject` — the persisted event row.
- `CreateEventSchema: z.ZodEffects<...>` — POST `/` body.
- `UpdateEventSchema: z.ZodEffects<...>` — PUT `/{id}` body.
- `EventQuerySchema: z.ZodObject` — GET `/` query params.
- `EventIdParamSchema: z.ZodObject` — `{ id: string }`.
- `EventListResponseSchema: z.ZodObject` — GET `/` response.
Every schema exports a sibling `type Foo = z.infer<typeof FooSchema>`.
## Implementation Notes
`status` is encoded as an integer (`0=draft`, `1=active`,
`2=cancelled`) rather than a string enum to match the upstream
SurrealDB schema and to keep label cardinality low when emitted as
OTel attributes. The two cross-field `.refine()` rules
(`end_time > start_time`) are duplicated on `CreateEventSchema` and
`UpdateEventSchema` rather than factored to a shared helper because
the `UpdateEventSchema` variant must tolerate a single-field update
(`{ title }`) without raising the refinement, which means the rule
fires only when both `start_time` and `end_time` appear in the same
payload.