Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
# 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<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.