@@ -0,0 +1,140 @@
|
||||
---
|
||||
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
|
||||
Reference in New Issue
Block a user