Move docs/ package mirror into document-navigator plugin

Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,101 @@
# options
## Purpose
The Zod schema for `createEventsPlugin`'s options bundle. Three
fields: `basePath` (mount prefix, default `/events`),
`enableNotifications` (reserved for a future
`@suaveplan/notification-channel` binding, default `false`),
`maxInvitesPerRequest` (runtime cap on the `/invite` endpoint,
default `50`, range `1..100`).
The schema is the boundary between caller input and the resolved
config that flows through every lifecycle hook. The plugin factory
calls `EventsPluginOptionsSchema.safeParse(options)` at construction
time and raises `EventsPluginError(INVALID_CONFIG)` on failure so an
invalid `basePath` or out-of-range `maxInvitesPerRequest` fails
before the descriptor leaves the factory and never reaches the
registry.
## Features
- Every field has a default — `EventsPluginOptionsSchema.parse({})`
produces the canonical resolved config.
- `basePath` is a non-empty string.
- `maxInvitesPerRequest` is an integer in `[1, 100]`.
- `EventsPluginConfigInput` (the `z.input` type) is partial-by-default
for the caller; `EventsPluginConfig` (the `z.infer` type) is
fully-populated for the runtime.
## Usage: Basic Example
```ts
import {
EventsPluginOptionsSchema,
type EventsPluginConfig,
} from "@suaveplan/api-plugin-events";
const config: EventsPluginConfig = EventsPluginOptionsSchema.parse({});
console.log(config.basePath); // "/events"
```
## Usage: Advanced Example
```ts
import {
EventsPluginOptionsSchema,
type EventsPluginConfigInput,
} from "@suaveplan/api-plugin-events";
function deriveConfig(env: Record<string, string | undefined>) {
const input: EventsPluginConfigInput = {
basePath: env.EVENTS_BASE_PATH ?? "/events",
enableNotifications: env.EVENTS_NOTIFY === "true",
maxInvitesPerRequest: env.EVENTS_INVITE_CAP
? Number.parseInt(env.EVENTS_INVITE_CAP, 10)
: 50,
};
const result = EventsPluginOptionsSchema.safeParse(input);
if (!result.success) {
throw new Error(
`events plugin misconfigured: ${result.error.message}`
);
}
return result.data;
}
```
## API Reference
### `EventsPluginOptionsSchema: z.ZodObject`
- `basePath: z.ZodDefault<z.ZodString>` — default `/events`.
- `enableNotifications: z.ZodDefault<z.ZodBoolean>` — default `false`.
- `maxInvitesPerRequest: z.ZodDefault<z.ZodNumber>` — int `[1, 100]`,
default `50`.
### `type EventsPluginConfig`
`z.infer<typeof EventsPluginOptionsSchema>` — the resolved, defaults-
applied shape passed to lifecycle hooks.
### `type EventsPluginConfigInput`
`z.input<typeof EventsPluginOptionsSchema>` — the caller-facing
partial shape accepted by `createEventsPlugin`.
## Implementation Notes
`enableNotifications` is reserved for the future
`@suaveplan/notification-channel` binding (see `## Out of scope` in
the proposal). The flag is validated and threaded through the
resolved config today so consumers can opt-in without a breaking
change later. The schema enforces the type contract; the binding
itself lands when the notification-channel package migrates.
`maxInvitesPerRequest` is intentionally capped at the schema layer
(100) below the body schema's hard cap (`InviteUsersSchema.user_ids`
max 100) so the route handler can return a structured
`INVITE_LIMIT_EXCEEDED` error instead of letting Zod raise a more
opaque `Array must contain at most 100 element(s)` validation
failure. The two caps coexist by design.

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.