Files
Daniil 6e82165566
compute / deploy (push) Has been cancelled
chore: agentic upgrade
2026-05-17 02:12:18 +03:00

141 lines
7.0 KiB
Markdown

---
name: elysia-best-practices
description: Use when writing, reviewing, or refactoring Elysia server code in this repository, including route handlers, schemas, response statuses, lifecycle hooks, plugins, OpenAPI docs, health checks, render API endpoints, and queue-facing API behavior.
---
# Elysia Best Practices
## Overview
Apply current Elysia guidance to this Bun Remotion rendering service while
preserving its API contract: `/api/render` can enqueue async jobs, render
synchronously, report BullMQ status, cancel jobs, and expose health checks.
## Workflow
1. Fetch current Elysia docs first with Context7. This repo currently uses
`elysia` `1.4.27`, `@elysiajs/openapi` `1.4.14`, and
`@elysiajs/swagger` `1.3.1`; compare docs against those packages before
adopting examples that use newer `@elysia/openapi` imports.
2. Identify the layer being changed:
- HTTP entrypoint: `server/index.ts`
- Shared request schemas: `server/types/DocumentSchema.ts`,
`server/types/CaptionStyleSchema.ts`
- Queue behavior: `server/services/render_queue.ts`
- Rendering and cleanup: `server/services/render_video.ts`
- S3/webhooks/config: `server/services/s3.ts`,
`server/services/webhook.ts`, `server/config.ts`
3. Keep Elysia routes thin. Validate and shape HTTP inputs at the route, then
delegate rendering, queue, upload, and webhook work to services.
4. Verify with `bun run lint` and a focused smoke test through `bun run server`,
`/api/health`, and the affected `/api/render` path.
## Project Map
- Runtime: Bun with strict TypeScript and path aliases from `tsconfig.json`.
- Server root: `new Elysia({ prefix: "/api" })` in `server/index.ts`.
- Current endpoints:
- `POST /api/render`: async queue when `callbackUrl` is present; synchronous
render/upload fallback otherwise.
- `GET /api/render/:renderId`: BullMQ job state and progress.
- `DELETE /api/render/:renderId`: cancellation.
- `GET /api/health`: liveness response.
- Existing schemas use Elysia `t` and `Static`; reuse this style instead of
introducing Zod, Valibot, or ad hoc runtime checks.
## Route Contracts
- Define schemas for `body`, `params`, `query`, `headers`, and `response`.
Elysia infers handler types from schemas, so avoid duplicate TypeScript
interfaces unless they are exported from `Static<typeof Schema>`.
- For route responses with multiple statuses, define `response` by status code
and return `status(code, body)` from the handler. Prefer this for new code over
mutating `set.status`; current docs call `set.status` legacy and it cannot
validate response types as precisely.
- Preserve existing API payload field names unless intentionally migrating a
client contract: `renderId`, `status`, `progress_pct`, `output_path`,
`callback_delivered`, and `error`.
- Use literal unions for finite render states and style values. Keep captions
and transcription schemas reusable in `server/types/`, not embedded inside
handlers.
- Validate URL-like inputs that leave the service boundary. For new fields such
as callback or source URLs, prefer a schema-level constraint or a small service
validator before queueing work.
## Status And Errors
- Use the handler context `status()` function for expected outcomes:
`return status(202, { renderId, status: "queued" })` and
`return status(404, result)`.
- Use `onError` for cross-cutting logging and sanitized error responses, and
register it before routes it must affect. Do not leak S3 credentials, signed
URLs, local output paths, full Remotion CLI logs, or Redis connection details.
- Validation failures normally return 422. In production, Elysia omits detailed
validation internals by default; keep that behavior unless debugging explicitly
requires a temporary override.
- When adding typed domain errors, register classes with `.error()` and narrow in
`.onError()`. For one route only, use a local route `error` hook.
## Lifecycle And Plugins
- Hook order matters. Interceptor hooks apply only to routes registered after
them; place auth, tracing, error mapping, CORS, or request logging before the
routes they should cover.
- Elysia plugin lifecycles and schemas are encapsulated by default. Use `local`,
`scoped`, or `global` intentionally when extracting route groups or shared
guards, especially if a guard should affect parent `/api` routes.
- Prefer `resolve` for per-request values that depend on validated data, such as
a parsed render ID, normalized callback URL, or tenant/folder value. Use
`derive` for request-derived context before validation only when validation is
not needed first.
- Use `decorate` for stable shared services or helpers, not request-specific
mutable data. Avoid putting queue job state in Elysia `store`; BullMQ and Redis
are the source of truth.
- If route count grows, extract plugins by concern, for example `renderRoutes`,
`healthRoutes`, and `openApiPlugin`, then mount them under the existing
prefixed app.
## OpenAPI
- If exposing docs, prefer one OpenAPI integration and make it match installed
packages. This repo already has `@elysiajs/openapi` and `@elysiajs/swagger`;
do not add `@elysia/openapi` without an intentional dependency migration.
- Add `detail` metadata for public endpoints: tags, summary, description, and
response examples where helpful. Keep docs accurate by defining runtime
schemas for request and response shapes.
- Treat render endpoints as operational API, not demo routes. Document async
queue behavior, callback delivery, cancellation semantics, and sync fallback
separately.
## Service Boundaries
- Do not start extra BullMQ workers from route modules. The current server starts
one worker at process boot; keep worker lifecycle explicit and graceful.
- Keep cleanup in `finally` when route code creates local render outputs. Do not
leave generated videos or props files behind after uploads or failures.
- Avoid blocking request handlers with long synchronous work unless preserving
the existing no-`callbackUrl` sync fallback. Prefer queueing for new expensive
render operations.
- Keep env requirements in `server/config.ts` and `.env.example` together. Never
hardcode credentials, bucket names, Redis URLs, or public host assumptions in
Elysia handlers.
## Validation
- Always run `bun run lint` after TypeScript/Elysia changes.
- For schema or status changes, smoke invalid requests and expected non-200
responses, not only the happy path.
- For queue-facing changes, smoke `POST /api/render` with `callbackUrl`,
`GET /api/render/:renderId`, and `DELETE /api/render/:renderId`.
- For docs changes, verify the generated OpenAPI page/spec if the plugin is
mounted.
## Source Anchors
- Validation and response schemas: https://elysiajs.com/tutorial/getting-started/validation/
- Handler context and `status()`: https://elysiajs.com/essential/handler
- Lifecycle and hook ordering: https://elysiajs.com/essential/life-cycle
- Error handling: https://elysiajs.com/patterns/error-handling
- Encapsulation, scopes, and guards: https://elysiajs.com/tutorial/getting-started/encapsulation/
- OpenAPI patterns: https://elysiajs.com/patterns/openapi