3.9 KiB
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.
RsvpStatusSchemaconstrains status to the four documented values.InviteUsersSchemaenforces1 <= length <= 100onuser_idswith non-empty-string members.AttendeeSchemaextendsRsvpSchemawith optional sparse user fields for joined listing endpoints.AttendeeQuerySchemacoerces query-string parameters.RsvpResponseSchemacarries a nullablersvpfield for "no current RSVP" representations.
Usage: Basic Example
import { CreateRsvpSchema, RsvpStatusSchema } from "@suaveplan/api-plugin-events";
const body = CreateRsvpSchema.parse({ status: "going" });
const isValid = RsvpStatusSchema.safeParse("yes").success; // false
Usage: Advanced Example
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/rsvpbody.InviteUsersSchema: z.ZodObject—POST /:id/invitebody (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<typeof FooSchema>.
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.