# rsvp-schemas ## Purpose Local Zod schemas for the RSVP / invitation surface. Owns the persisted `RsvpSchema`, the `CreateRsvpSchema` request body for `POST /:id/rsvp`, the `InviteUsersSchema` body for `POST /:id/invite`, the `AttendeeQuerySchema` for the attendee listing endpoint, and the joined `AttendeeSchema` that combines the RSVP row with optional sparse user fields (`user_name`, `user_email`, `user_image`). The RSVP status is a four-value string enum (`invited`, `going`, `maybe`, `not_going`) — not the integer encoding used for event status — because RSVPs are read-heavy and the integer encoding gave no real-world benefit when the upstream surface was originally specced. The `InviteUsersSchema` enforces a 1..100 length range as the schema-level hard cap; the runtime cap (default 50) is enforced separately inside the `POST /:id/invite` handler so deployments can narrow without recompiling. ## Features - 9 schemas spanning persistence, request, response, and pagination. - `RsvpStatusSchema` constrains status to the four documented values. - `InviteUsersSchema` enforces `1 <= length <= 100` on `user_ids` with non-empty-string members. - `AttendeeSchema` extends `RsvpSchema` with optional sparse user fields for joined listing endpoints. - `AttendeeQuerySchema` coerces query-string parameters. - `RsvpResponseSchema` carries a nullable `rsvp` field for "no current RSVP" representations. ## Usage: Basic Example ```ts import { CreateRsvpSchema, RsvpStatusSchema } from "@suaveplan/api-plugin-events"; const body = CreateRsvpSchema.parse({ status: "going" }); const isValid = RsvpStatusSchema.safeParse("yes").success; // false ``` ## Usage: Advanced Example ```ts import { AttendeeListResponseSchema, AttendeeQuerySchema, InviteResponseSchema, InviteUsersSchema, } from "@suaveplan/api-plugin-events"; // Validate an invite body, then enforce the runtime cap separately const parsed = InviteUsersSchema.parse({ user_ids: ["user:1", "user:2"] }); if (parsed.user_ids.length > config.maxInvitesPerRequest) { return res.status(400).json({ error: "INVITE_LIMIT_EXCEEDED" }); } // Build a paginated attendee response const query = AttendeeQuerySchema.parse({ status: "going", limit: "100" }); const response = AttendeeListResponseSchema.parse({ data: [ /* joined attendees */ ], total: 42, }); ``` ## API Reference - `RsvpStatusSchema: z.ZodEnum<["invited", "going", "maybe", "not_going"]>`. - `RsvpSchema: z.ZodObject` — persisted RSVP row. - `CreateRsvpSchema: z.ZodObject` — `POST /:id/rsvp` body. - `InviteUsersSchema: z.ZodObject` — `POST /:id/invite` body (`user_ids: string[]`, length 1..100, non-empty members). - `AttendeeQuerySchema: z.ZodObject` — pagination + status filter. - `AttendeeSchema: z.ZodObject` — RSVP + sparse user fields. - `AttendeeListResponseSchema: z.ZodObject` — `{ data, total }`. - `RsvpResponseSchema: z.ZodObject` — `{ rsvp: RsvpSchema | null }`. - `InviteResponseSchema: z.ZodObject` — `{ invitations, total }`. Every schema exports a matching `type Foo = z.infer`. ## Implementation Notes The schema-level cap on `InviteUsersSchema.user_ids` is 100 and the runtime cap (`config.maxInvitesPerRequest`) defaults to 50. The two limits coexist because the schema-level cap protects against abusive payloads (10 000-element arrays that would still parse correctly but exhaust the server) and the runtime cap encodes a per-deployment policy. The route handler enforces the runtime cap after the schema parse so the response code is `400` with `EVENTS_PLUGIN.INVITE_LIMIT_EXCEEDED`, not `422`/`400` from Zod. `AttendeeSchema` extends `RsvpSchema` with optional joined columns because the suaveplan SurrealDB driver doesn't materialise FETCH expansions into nested objects by default — the route handler reads the raw projection from `findAttendees` which mixes RSVP fields with optional joined user fields.