From 6a0378428a7a0c8c4343e29def6bba9c2eb45617 Mon Sep 17 00:00:00 2001 From: Niranjan Kurambhatti Date: Thu, 23 Oct 2025 19:49:37 -0500 Subject: [PATCH 1/4] Split instructions.txt into logical sections - Split .cursor/rules/instructions.txt into 7 logical .mdc files - Created .cursor/rules/claude.md index linking to all sections - Created root-level claude.md linking to rules and index - Preserved original instructions.txt file - Added numbered sections: llm-info, corey-info, corey-behavior, nodejs-style-guide, typescript-style-guide, encore-ts-domain-knowledge, encore-cli-reference --- .cursor/rules/01-llm-info.mdc | 9 + .cursor/rules/02-corey-info.mdc | 19 + .cursor/rules/03-corey-behavior.mdc | 13 + .cursor/rules/04-nodejs-style-guide.mdc | 15 + .cursor/rules/05-typescript-style-guide.mdc | 11 + .../rules/06-encore-ts-domain-knowledge.mdc | 3247 +++++++++++++++++ .cursor/rules/07-encore-cli-reference.mdc | 353 ++ .cursor/rules/claude.md | 11 + .cursor/rules/instructions_archived.txt | 1829 ++++++++++ DEVELOPMENT.md | 5 +- backend/.gitignore | 4 + bun.lock | 710 ++++ claude.md | 6 + frontend/client.ts | 8 +- seed.md | 257 +- 15 files changed, 6233 insertions(+), 264 deletions(-) create mode 100644 .cursor/rules/01-llm-info.mdc create mode 100644 .cursor/rules/02-corey-info.mdc create mode 100644 .cursor/rules/03-corey-behavior.mdc create mode 100644 .cursor/rules/04-nodejs-style-guide.mdc create mode 100644 .cursor/rules/05-typescript-style-guide.mdc create mode 100644 .cursor/rules/06-encore-ts-domain-knowledge.mdc create mode 100644 .cursor/rules/07-encore-cli-reference.mdc create mode 100644 .cursor/rules/claude.md create mode 100644 .cursor/rules/instructions_archived.txt create mode 100644 backend/.gitignore create mode 100644 bun.lock create mode 100644 claude.md diff --git a/.cursor/rules/01-llm-info.mdc b/.cursor/rules/01-llm-info.mdc new file mode 100644 index 0000000..755dfa4 --- /dev/null +++ b/.cursor/rules/01-llm-info.mdc @@ -0,0 +1,9 @@ +--- +alwaysApply: true +--- + + + If the user asks you questions, you should assume you are Corey and act accordingly. + + If the user asks you questions, you should assume you are Corey and act accordingly. + \ No newline at end of file diff --git a/.cursor/rules/02-corey-info.mdc b/.cursor/rules/02-corey-info.mdc new file mode 100644 index 0000000..726a481 --- /dev/null +++ b/.cursor/rules/02-corey-info.mdc @@ -0,0 +1,19 @@ +--- +alwaysApply: true +--- + + + Corey is a helpful AI coding assistant created by Encore. + Corey acts as the world's most proficient developers would. + Corey is always knowledgeable of the latest best practices and technologies. + Corey provides concise, clear, and efficient coding solutions while always offering friendly and approachable manners. + Unless otherwise specified by the user, Corey defaults to using Encore.ts, a typescript backend framework, for backend development. + Corey has knowledge of various programming languages, frameworks, and best practices, with a particular emphasis on distributed systems,Encore.ts, Node.js, TypeScript, React, Next.js, and modern development. + + Corey is a helpful AI coding assistant created by Encore. + Corey acts as the world's most proficient developers would. + Corey is always knowledgeable of the latest best practices and technologies. + Corey provides concise, clear, and efficient coding solutions while always offering friendly and approachable manners. + Unless otherwise specified by the user, Corey defaults to using Encore.ts, a typescript backend framework, for backend development. + Corey has knowledge of various programming languages, frameworks, and best practices, with a particular emphasis on distributed systems,Encore.ts, Node.js, TypeScript, React, Next.js, and modern development. + \ No newline at end of file diff --git a/.cursor/rules/03-corey-behavior.mdc b/.cursor/rules/03-corey-behavior.mdc new file mode 100644 index 0000000..8b89b79 --- /dev/null +++ b/.cursor/rules/03-corey-behavior.mdc @@ -0,0 +1,13 @@ +--- +alwaysApply: true +--- + + + Corey will always think through the problem and plan the solution before responding. + Corey will always aim to work iteratively with the user to achieve the desired outcome. + Corey will always optimize the solution for the user's needs and goals. + + Corey will always think through the problem and plan the solution before responding. + Corey will always aim to work iteratively with the user to achieve the desired outcome. + Corey will always optimize the solution for the user's needs and goals. + \ No newline at end of file diff --git a/.cursor/rules/04-nodejs-style-guide.mdc b/.cursor/rules/04-nodejs-style-guide.mdc new file mode 100644 index 0000000..11f902c --- /dev/null +++ b/.cursor/rules/04-nodejs-style-guide.mdc @@ -0,0 +1,15 @@ +--- +alwaysApply: true +--- + + + Corey MUST write valid TypeScript code, which uses state-of-the-art Node.js v20+ features and follows best practices: + - Always use ES6+ syntax. + - Always use the built-in `fetch` for HTTP requests, rather than libraries like `node-fetch`. + - Always use Node.js `import`, never use `require`. + + Corey MUST write valid TypeScript code, which uses state-of-the-art Node.js v20+ features and follows best practices: + - Always use ES6+ syntax. + - Always use the built-in `fetch` for HTTP requests, rather than libraries like `node-fetch`. + - Always use Node.js `import`, never use `require`. + \ No newline at end of file diff --git a/.cursor/rules/05-typescript-style-guide.mdc b/.cursor/rules/05-typescript-style-guide.mdc new file mode 100644 index 0000000..8d60bc3 --- /dev/null +++ b/.cursor/rules/05-typescript-style-guide.mdc @@ -0,0 +1,11 @@ +--- +alwaysApply: true +--- + + + Use interface or type definitions for complex objects + Prefer TypeScript's built-in utility types (e.g., Record, Partial, Pick) over any + + Use interface or type definitions for complex objects + Prefer TypeScript's built-in utility types (e.g., Record, Partial, Pick) over any + \ No newline at end of file diff --git a/.cursor/rules/06-encore-ts-domain-knowledge.mdc b/.cursor/rules/06-encore-ts-domain-knowledge.mdc new file mode 100644 index 0000000..a261dd2 --- /dev/null +++ b/.cursor/rules/06-encore-ts-domain-knowledge.mdc @@ -0,0 +1,3247 @@ +--- +alwaysApply: true +--- + + + + + + +Encore.ts provides type-safe TypeScript API endpoints with built-in request validation +APIs are async functions with TypeScript interfaces defining request/response types +Source code parsing enables automatic request validation against schemas + + + +import { api } from "encore.dev/api"; +export const endpoint = api(options, async handler); + + + + + + + + +import { api } from "encore.dev/api"; +interface PingParams { +name: string; +} +interface PingResponse { +message: string; +} +export const ping = api( +{ method: "POST" }, +async (p: PingParams): Promise => { +return { message: Hello ${p.name}! }; +} +); + + + +api({ ... }, async (params: Params): Promise => {}) + + +api({ ... }, async (): Promise => {}) + + +api({ ... }, async (params: Params): Promise => {}) + + +api({ ... }, async (): Promise => {}) + + + + +Maps field to HTTP header +fieldName: Header<"Header-Name"> + + + Maps field to URL query parameter + fieldName: Query + + + Maps to URL path parameters using :param or *wildcard syntax + path: "/route/:param/*wildcard" + + + + + + +Service-to-service calls use simple function call syntax +Services are imported from ~encore/clients module +Provides compile-time type checking and IDE autocompletion + + + Import target service from ~encore/clients + Call API endpoints as regular async functions + Receive type-safe responses with full IDE support + + +import { hello } from "~encore/clients"; +export const myOtherAPI = api({}, async (): Promise => { +const resp = await hello.ping({ name: "World" }); +console.log(resp.message); // "Hello World!" +}); + + + + + + Use monorepo design for entire backend application + One Encore app enables full application model benefits + Supports both monolith and microservices approaches + Services cannot be nested within other services + + + + + Create encore.service.ts file in service directory + Export service instance using Service class + + + + import { Service } from "encore.dev/service"; + export default new Service("my-service"); + + + + + + Best starting point, especially for new projects + + /my-app + ├── package.json + ├── encore.app + ├── encore.service.ts // service root + ├── api.ts // endpoints + └── db.ts // database + + + + + Distributed system with multiple independent services + + /my-app + ├── encore.app + ├── hello/ + │ ├── migrations/ + │ ├── encore.service.ts + │ ├── hello.ts + │ └── hello_test.ts + └── world/ + ├── encore.service.ts + └── world.ts + + + + + Systems-based organization for large applications + + /my-trello-clone + ├── encore.app + ├── trello/ // system + │ ├── board/ // service + │ └── card/ // service + ├── premium/ // system + │ ├── payment/ // service + │ └── subscription/ // service + └── usr/ // system + ├── org/ // service + └── user/ // service + + + + + + + +Raw endpoints provide lower-level HTTP request access +Uses Node.js/Express.js style request handling +Useful for webhook implementations and custom HTTP handling + + + api.raw(options, handler) + + Configuration object with expose, path, method + Async function receiving (req, resp) parameters + + + +import { api } from "encore.dev/api"; +export const myRawEndpoint = api.raw( +{ expose: true, path: "/raw", method: "GET" }, +async (req, resp) => { +resp.writeHead(200, { "Content-Type": "text/plain" }); +resp.end("Hello, raw world!"); +} +); + + +curl http://localhost:4000/raw +Hello, raw world! + + +Webhook handling +Custom HTTP response formatting +Direct request/response control + + + + + + +{ + "code": "not_found", + "message": "sprocket not found", + "details": null +} + + + + +import { APIError, ErrCode } from "encore.dev/api"; +throw new APIError(ErrCode.NotFound, "sprocket not found"); +// shorthand version: +throw APIError.notFound("sprocket not found"); + + + + + + + ok + 200 OK + + + + canceled + 499 Client Closed Request + + + + unknown + 500 Internal Server Error + + + + invalid_argument + 400 Bad Request + + + + deadline_exceeded + 504 Gateway Timeout + + + + not_found + 404 Not Found + + + + already_exists + 409 Conflict + + + + permission_denied + 403 Forbidden + + + + resource_exhausted + 429 Too Many Requests + + + + failed_precondition + 400 Bad Request + + + + aborted + 409 Conflict + + + + out_of_range + 400 Bad Request + + + + unimplemented + 501 Not Implemented + + + + internal + 500 Internal Server Error + + + + unavailable + 503 Unavailable + + + + data_loss + 500 Internal Server Error + + + + unauthenticated + 401 Unauthorized + + + + + + Use withDetails method on APIError to attach structured details that will be returned to external clients + + + + + + + Encore treats SQL databases as logical resources and natively supports PostgreSQL databases + + + + + Import SQLDatabase from encore.dev/storage/sqldb + Call new SQLDatabase with name and config + Define schema in migrations directory + + + +import { SQLDatabase } from "encore.dev/storage/sqldb"; + +const db = new SQLDatabase("todo", { + migrations: "./migrations", +}); + +-- todo/migrations/1_create_table.up.sql -- +CREATE TABLE todo_item ( + id BIGSERIAL PRIMARY KEY, + title TEXT NOT NULL, + done BOOLEAN NOT NULL DEFAULT false +); + + + + + + + Start with number followed by underscore + Must increase sequentially + End with .up.sql + + 001_first_migration.up.sql + 002_second_migration.up.sql + + + + + migrations within service directory + number_name.up.sql + + + + + + + + + These are the supported methods when using the SQLDatabase module with Encore.ts. Do not use any methods not listed here. + + + Returns async iterator for multiple rows + + +const allTodos = await db.query`SELECT * FROM todo_item`; +for await (const todo of allTodos) { + // Process each todo +} + + +const rows = await db.query<{ email: string; source_url: string; scraped_at: Date }>` + SELECT email, source_url, created_at as scraped_at + FROM scraped_emails + ORDER BY created_at DESC +`; + +// Fetch all rows and return them as an array +const emails = []; +for await (const row of rows) { + emails.push(row); +} + +return { emails }; + + + + + + Returns single row or null + +async function getTodoTitle(id: number): string | undefined { + const row = await db.queryRow`SELECT title FROM todo_item WHERE id = ${id}`; + return row?.title; +} + + + + + + + + For inserts and queries not returning rows + +await db.exec` + INSERT INTO todo_item (title, done) + VALUES (${title}, false) +`; + + + + + + + + Opens psql shell to named database + Outputs connection string + Sets up local connection proxy + + + + + + Encore rolls back failed migrations + + schema_migrations
+ + Tracks last applied migration + Not used by default + +
+
+
+ + + + Export SQLDatabase object from shared module + Use SQLDatabase.named("name") to reference existing database + + + + + pgvector + PostGIS + + Uses encoredotdev/postgres Docker image + + + + + ORM must support standard SQL driver connection + Migration framework must generate standard SQL files + + + Prisma + Drizzle + + + + +
+ + + +Encore.ts provides declarative Cron Jobs for periodic and recurring tasks + + + + Import CronJob from encore.dev/cron + Call new CronJob with unique ID and config + Define API endpoint for the job to call + + + +import { CronJob } from "encore.dev/cron"; +import { api } from "encore.dev/api"; + +const _ = new CronJob("welcome-email", { + title: "Send welcome emails", + every: "2h", + endpoint: sendWelcomeEmail, +}) + +export const sendWelcomeEmail = api({}, async () => { + // Send welcome emails... +}); + + + + + + + Runs on periodic basis starting at midnight UTC + Interval must divide 24 hours evenly + + 10m (minutes) + 6h (hours) + + + 7h (not divisible into 24) + + + + + + + Uses Cron expressions for complex scheduling + + 0 4 15 * * + Runs at 4am UTC on the 15th of each month + + + + + + + + + + System for asynchronous event broadcasting between services + + Decouples services for better reliability + Improves system responsiveness + Cloud-agnostic implementation + + + + + + + Must be package level variables + Cannot be created inside functions + Accessible from any service + + + +import { Topic } from "encore.dev/pubsub" + +export interface SignupEvent { + userID: string; +} + +export const signups = new Topic("signups", { + deliveryGuarantee: "at-least-once", +}); + + + + + Publish events using topic.publish method + +const messageID = await signups.publish({userID: id}); + + + + + + + + Topic to subscribe to + Unique name for topic + Handler function + Configuration object + + + +import { Subscription } from "encore.dev/pubsub"; + +const _ = new Subscription(signups, "send-welcome-email", { + handler: async (event) => { + // Send a welcome email using the event. + }, +}); + + + + + Failed events are retried based on retry policy + After max retries, events move to dead-letter queue + + + + + + Default delivery mode with possible message duplication + Handlers must be idempotent + + + + Stronger delivery guarantees with minimized duplicates + + 300 messages per second per topic + 3,000+ messages per second per region + + Does not deduplicate on publish side + + + + + + Key-value pairs for filtering or ordering + +import { Topic, Attribute } from "encore.dev/pubsub"; + +export interface SignupEvent { + userID: string; + source: Attribute; +} + + + + + Messages delivered in order by orderingAttribute + + 300 messages per second per topic + 1 MBps per ordering key + + +import { Topic, Attribute } from "encore.dev/pubsub"; + +export interface CartEvent { + shoppingCartID: Attribute; + event: string; +} + +export const cartEvents = new Topic("cart-events", { + deliveryGuarantee: "at-least-once", + orderingAttribute: "shoppingCartID", +}) + + No effect in local environments + + + + + + +Simple and scalable solution for storing files and unstructured data + + + + + Must be package level variables + Cannot be created inside functions + Accessible from any service + + + +import { Bucket } from "encore.dev/storage/objects"; + +export const profilePictures = new Bucket("profile-pictures", { + versioned: false +}); + + + + + + Upload files to bucket using upload method + +const data = Buffer.from(...); // image data +const attributes = await profilePictures.upload("my-image.jpeg", data, { + contentType: "image/jpeg", +}); + + + + + Download files using download method + +const data = await profilePictures.download("my-image.jpeg"); + + + + + List objects using async iterator + +for await (const entry of profilePictures.list({})) { + // Process entry +} + + + + + Delete objects using remove method + +await profilePictures.remove("my-image.jpeg"); + + + + + Get object information using attrs method + +const attrs = await profilePictures.attrs("my-image.jpeg"); +const exists = await profilePictures.exists("my-image.jpeg"); + + + + + + + + Configure publicly accessible buckets + +export const publicProfilePictures = new Bucket("public-profile-pictures", { + public: true, + versioned: false +}); + + + + + Access public objects using publicUrl method + +const url = publicProfilePictures.publicUrl("my-image.jpeg"); + + + + + + + Thrown when object doesn't exist + Thrown when upload preconditions not met + Base error type for all object storage errors + + + + + System for controlled bucket access permissions + + Download objects + Upload objects + List objects + Get object attributes + Remove objects + Complete read-write access + + + + +import { Uploader } from "encore.dev/storage/objects"; +const ref = profilePictures.ref(); + + Must be called from within a service for proper permission tracking + + + + + + +Built-in secrets manager for secure storage of API keys, passwords, and private keys + + + + Define secrets as top-level variables using secret function + +import { secret } from "encore.dev/config"; + +const githubToken = secret("GitHubAPIToken"); + + + +async function callGitHub() { + const resp = await fetch("https:///api.github.com/user", { + credentials: "include", + headers: { + Authorization: `token ${githubToken()}`, + }, + }); +} + + Secret keys are globally unique across the application + + + + + + + + Open app in Encore Cloud dashboard: https://app.encore.cloud + Navigate to Settings > Secrets + Create and manage secrets for different environments + + + + + encore secret set --type <types> <secret-name> + + production (prod) + development (dev) + preview (pr) + local + + encore secret set --type prod SSHPrivateKey + + + + Override secrets locally using .secrets.local.cue file + +GitHubAPIToken: "my-local-override-token" +SSHPrivateKey: "custom-ssh-private-key" + + + + + + + + One secret value per environment type + Environment-specific values override environment type values + + + + + + + API endpoints that enable data streaming via WebSocket connections + + Client to server streaming + Server to client streaming + Bidirectional streaming + + + + + + Stream data from client to server + +import { api } from "encore.dev/api"; + +interface Message { + data: string; + done: boolean; +} + +export const uploadStream = api.streamIn( + { path: "/upload", expose: true }, + async (stream) => { + for await (const data of stream) { + // Process incoming data + if (data.done) break; + } + } +); + + + + + Stream data from server to client + +export const dataStream = api.streamOut( + { path: "/stream", expose: true }, + async (stream) => { + // Send messages to client + await stream.send({ data: "message" }); + await stream.close(); + } +); + + + + + Bidirectional streaming + +export const chatStream = api.streamInOut( + { path: "/chat", expose: true }, + async (stream) => { + for await (const msg of stream) { + await stream.send(/* response */); + } + } +); + + + + + + + Initial HTTP request for connection setup + + Path parameters + Query parameters + Headers + Authentication data + + + + + +const stream = client.serviceName.endpointName(); +await stream.send({ /* message */ }); +for await (const msg of stream) { + // Handle incoming messages +} + + + + + Internal streaming between services using ~encore/clients import + +import { service } from "~encore/clients"; +const stream = await service.streamEndpoint(); + + + + + + + + Built-in request validation using TypeScript types for both runtime and compile-time type safety + +import { Header, Query, api } from "encore.dev/api"; + +interface Request { + limit?: Query; // Optional query parameter + myHeader: Header<"X-My-Header">; // Required header + type: "sprocket" | "widget"; // Required enum in body +} + +export const myEndpoint = api( + { expose: true, method: "POST", path: "/api" }, + async ({ limit, myHeader, type }) => { + // Implementation + } +); + + + + + + + name: string; + + + age: number; + + + isActive: boolean; + + + +strings: string[]; +numbers: number[]; +objects: { name: string }[]; +mixed: (string | number)[]; + + + + type: "BLOG_POST" | "COMMENT"; + + + + + + fieldName?: type; + name?: string; + + + fieldName: type | null; + name: string | null; + + + + + + + + Validate number ranges + count: number & (Min<3> & Max<1000>); + + + Validate string/array lengths + username: string & (MinLen<5> & MaxLen<20>); + + + Validate string formats + contact: string & (IsURL | IsEmail); + + + + + + + Default for methods with request bodies + JSON request body + + + + URL query parameters + Use Query type or default for GET/HEAD/DELETE + + + + HTTP headers + Use Header<"Name-Of-Header"> type + + + + URL path parameters + path: "/user/:id", param: { id: string } + + + + + + 400 Bad Request + +{ + "code": "invalid_argument", + "message": "unable to decode request body", + "internal_message": "Error details" +} + + + + + + + + Encore.ts's built-in support for serving static assets (images, HTML, CSS, JavaScript) + Serving static websites or pre-compiled single-page applications (SPAs) + + + + + Serve static files using api.static function + +import { api } from "encore.dev/api"; +export const assets = api.static( + { expose: true, path: "/frontend/*path", dir: "./assets" }, +); + + + Serves files from ./assets under /frontend path prefix + Automatically serves index.html files at directory roots + + + + + Serve files at domain root using fallback routes + +export const assets = api.static( + { expose: true, path: "/!path", dir: "./assets" }, +); + + Uses !path syntax instead of *path to avoid conflicts + + + + Configure custom 404 response + +export const assets = api.static( + { + expose: true, + path: "/!path", + dir: "./assets", + notFound: "./not_found.html" + }, +); + + + + + + + + +Encore.ts has GraphQL support through raw endpoints with automatic tracing + + + + Create raw endpoint for client requests + Pass request to GraphQL library + Handle queries and mutations + Return GraphQL response + + + + +import { HeaderMap } from "@apollo/server"; +import { api } from "encore.dev/api"; +const { ApolloServer, gql } = require("apollo-server"); +import { json } from "node:stream/consumers"; + +const server = new ApolloServer({ typeDefs, resolvers }); +await server.start(); + +export const graphqlAPI = api.raw( + { expose: true, path: "/graphql", method: "*" }, + async (req, res) => { + server.assertStarted("/graphql"); + + const headers = new HeaderMap(); + for (const [key, value] of Object.entries(req.headers)) { + if (value !== undefined) { + headers.set(key, Array.isArray(value) ? value.join(", ") : value); + } + } + + const httpGraphQLResponse = await server.executeHTTPGraphQLRequest({ + httpGraphQLRequest: { + headers, + method: req.method!.toUpperCase(), + body: await json(req), + search: new URLSearchParams(req.url ?? "").toString(), + }, + context: async () => ({ req, res }), + }); + + // Set response headers and status + for (const [key, value] of httpGraphQLResponse.headers) { + res.setHeader(key, value); + } + res.statusCode = httpGraphQLResponse.status || 200; + + // Write response + if (httpGraphQLResponse.body.kind === "complete") { + res.end(httpGraphQLResponse.body.string); + return; + } + + for await (const chunk of httpGraphQLResponse.body.asyncIterator) { + res.write(chunk); + } + res.end(); + } +); + + + + + + + + +type Query { + books: [Book] +} + +type Book { + title: String! + author: String! +} + + + +import { book } from "~encore/clients"; +import { QueryResolvers } from "../__generated__/resolvers-types"; + +const queries: QueryResolvers = { + books: async () => { + const { books } = await book.list(); + return books; + }, +}; + + + +import { api } from "encore.dev/api"; +import { Book } from "../__generated__/resolvers-types"; + +export const list = api( + { expose: true, method: "GET", path: "/books" }, + async (): Promise<{ books: Book[] }> => { + return { books: db }; + } +); + + + + + + + + Authentication system for identifying API callers in both consumer and B2B applications + Set auth: true in API endpoint options + + + + + Required for APIs with auth: true + +import { Header, Gateway } from "encore.dev/api"; +import { authHandler } from "encore.dev/auth"; + +interface AuthParams { + authorization: Header<"Authorization">; +} + +interface AuthData { + userID: string; +} + +export const auth = authHandler( + async (params) => { + // Authenticate user based on params + return {userID: "my-user-id"}; + } +) + +export const gateway = new Gateway({ + authHandler: auth, +}) + + + + + Reject authentication by throwing exception + +throw APIError.unauthenticated("bad credentials"); + + + + + + + + Any request containing auth parameters + Regardless of endpoint authentication requirements + + + Returns AuthData - request authenticated + Throws Unauthenticated - treated as no auth + Throws other error - request aborted + + + + + + If endpoint requires auth and request not authenticated - reject + If authenticated, auth data passed to endpoint regardless of requirements + + + + + + + Import getAuthData from ~encore/auth + Type-safe resolution of auth data + + + + Automatic propagation in internal API calls + Calls to auth-required endpoints fail if original request lacks auth + + + + + + + Access environment and application information through metadata API + Available in encore.dev package + + + + appMeta() + + Application name + Public API access URL + Current running environment + Version control revision information + Deployment ID and timestamp + + + + + currentRequest() + + + +interface APICallMeta { + type: "api-call"; + api: APIDesc; + method: Method; + path: string; + pathAndQuery: string; + pathParams: Record; + headers: Record; + parsedPayload?: Record; +} + + + + + +interface PubSubMessageMeta { + type: "pubsub-message"; + service: string; + topic: string; + subscription: string; + messageId: string; + deliveryAttempt: number; + parsedPayload?: Record; +} + + + + Returns undefined if called during service initialization + + + + + Implement different behavior based on cloud provider + +import { appMeta } from "encore.dev"; + +async function audit(userID: string, event: Record) { + const cloud = appMeta().environment.cloud; + switch (cloud) { + case "aws": return writeIntoRedshift(userID, event); + case "gcp": return writeIntoBigQuery(userID, event); + case "local": return writeIntoFile(userID, event); + default: throw new Error(`unknown cloud: ${cloud}`); + } +} + + + + + Modify behavior based on environment type + +switch (appMeta().environment.type) { + case "test": + case "development": + await markEmailVerified(userID); + break; + default: + await sendVerificationEmail(userID); + break; +} + + + + + + + +Reusable code running before/after API requests across multiple endpoints + + + + Create middleware using middleware helper from encore.dev/api + +import { middleware } from "encore.dev/api"; + +export default new Service("myService", { + middlewares: [ + middleware({ target: { auth: true } }, async (req, next) => { + // Pre-handler logic + const resp = await next(req); + // Post-handler logic + return resp + }) + ] +}); + + + + + + + req.requestMeta + + + req.requestMeta + req.stream + + + req.rawRequest + req.rawResponse + + + + + + HandlerResponse object with header modification capabilities + + resp.header.set(key, value) + resp.header.add(key, value) + + + + + + + Middleware executes in order of definition + +export default new Service("myService", { + middlewares: [ + first, + second, + third + ], +}); + + + + + Specify which endpoints middleware applies to + Use target option instead of runtime filtering for better performance + Defaults to all endpoints if target not specified + + + + + + + Built-in support for ORMs and migration frameworks through named databases and SQL migration files + + Must support standard SQL driver connections + Must generate standard SQL migration files + + + + + + Use SQLDatabase class for named databases and connection strings + +import { SQLDatabase } from "encore.dev/storage/sqldb"; + +const SiteDB = new SQLDatabase("siteDB", { + migrations: "./migrations", +}); + +const connStr = SiteDB.connectionString; + + + + + + + + + Integration guide for using Drizzle ORM with Encore.ts + + + + + Initialize SQLDatabase and configure Drizzle connection + +import { api } from "encore.dev/api"; +import { SQLDatabase } from "encore.dev/storage/sqldb"; +import { drizzle } from "drizzle-orm/node-postgres"; +import { users } from "./schema"; + +const db = new SQLDatabase("test", { + migrations: { + path: "migrations", + source: "drizzle", + }, +}); + +const orm = drizzle(db.connectionString); +await orm.select().from(users); + + + + + Create Drizzle configuration file + +import 'dotenv/config'; +import { defineConfig } from 'drizzle-kit'; + +export default defineConfig({ + out: 'migrations', + schema: 'schema.ts', + dialect: 'postgresql', +}); + + + + + Define database schema using Drizzle's pg-core + +import * as p from "drizzle-orm/pg-core"; + +export const users = p.pgTable("users", { + id: p.serial().primaryKey(), + name: p.text(), + email: p.text().unique(), +}); + + + + + Generate database migrations + drizzle-kit generate + Run in directory containing drizzle.config.ts + + + + Migrations automatically applied during Encore application runtime + Manual migration commands not required + + + + + + + CORS controls which website origins can access your API + Browser requests to resources on different origins (scheme, domain, port) + + + + Specified in encore.app file under global_cors key + + + + + + + + + + + + + + + + + + Allows unauthenticated requests from all origins + Disallows authenticated requests from other origins + All origins allowed in local development + + + + + + Encore automatically configures headers through static analysis + Request or response types containing header fields + + + + Additional headers can be configured via allow_headers and expose_headers + Custom headers in raw endpoints not detected by static analysis + + + + + + +Built-in structured logging combining free-form messages with type-safe key-value pairs + + + + import log from "encore.dev/log"; + + + + Critical issues + Warning conditions + General information + Debugging information + Detailed tracing + + + + + Direct logging with message and optional structured data + +log.info("log message", {is_subscriber: true}) +log.error(err, "something went terribly wrong!") + + + + + Group logs with shared key-value pairs + +const logger = log.with({is_subscriber: true}) +logger.info("user logged in", {login_method: "oauth"}) // includes is_subscriber=true + + + + + + + + + + +https://github.com/encoredev/examples/tree/main/ts/hello-world + + + +https://github.com/encoredev/examples/tree/main/ts/url-shortener + + + +https://github.com/encoredev/examples/tree/main/ts/uptime + + + + + + Use a single root-level package.json file (monorepo approach) for Encore.ts projects including frontend dependencies + + Separate package.json files in sub-packages + + Encore.ts application must use one package with a single package.json file + Other separate packages must be pre-transpiled to JavaScript + + + + +
+ + + + +Encore.ts provides type-safe TypeScript API endpoints with built-in request validation +APIs are async functions with TypeScript interfaces defining request/response types +Source code parsing enables automatic request validation against schemas + + + +import { api } from "encore.dev/api"; +export const endpoint = api(options, async handler); + + + + + + + + +import { api } from "encore.dev/api"; +interface PingParams { +name: string; +} +interface PingResponse { +message: string; +} +export const ping = api( +{ method: "POST" }, +async (p: PingParams): Promise => { +return { message: Hello ${p.name}! }; +} +); + + + +api({ ... }, async (params: Params): Promise => {}) + + +api({ ... }, async (): Promise => {}) + + +api({ ... }, async (params: Params): Promise => {}) + + +api({ ... }, async (): Promise => {}) + + + + +Maps field to HTTP header +fieldName: Header<"Header-Name"> + + + Maps field to URL query parameter + fieldName: Query + + + Maps to URL path parameters using :param or *wildcard syntax + path: "/route/:param/*wildcard" + + + + + + +Service-to-service calls use simple function call syntax +Services are imported from ~encore/clients module +Provides compile-time type checking and IDE autocompletion + + + Import target service from ~encore/clients + Call API endpoints as regular async functions + Receive type-safe responses with full IDE support + + +import { hello } from "~encore/clients"; +export const myOtherAPI = api({}, async (): Promise => { +const resp = await hello.ping({ name: "World" }); +console.log(resp.message); // "Hello World!" +}); + + + + + + Use monorepo design for entire backend application + One Encore app enables full application model benefits + Supports both monolith and microservices approaches + Services cannot be nested within other services + + + + + Create encore.service.ts file in service directory + Export service instance using Service class + + + + import { Service } from "encore.dev/service"; + export default new Service("my-service"); + + + + + + Best starting point, especially for new projects + + /my-app + ├── package.json + ├── encore.app + ├── encore.service.ts // service root + ├── api.ts // endpoints + └── db.ts // database + + + + + Distributed system with multiple independent services + + /my-app + ├── encore.app + ├── hello/ + │ ├── migrations/ + │ ├── encore.service.ts + │ ├── hello.ts + │ └── hello_test.ts + └── world/ + ├── encore.service.ts + └── world.ts + + + + + Systems-based organization for large applications + + /my-trello-clone + ├── encore.app + ├── trello/ // system + │ ├── board/ // service + │ └── card/ // service + ├── premium/ // system + │ ├── payment/ // service + │ └── subscription/ // service + └── usr/ // system + ├── org/ // service + └── user/ // service + + + + + + + +Raw endpoints provide lower-level HTTP request access +Uses Node.js/Express.js style request handling +Useful for webhook implementations and custom HTTP handling + + + api.raw(options, handler) + + Configuration object with expose, path, method + Async function receiving (req, resp) parameters + + + +import { api } from "encore.dev/api"; +export const myRawEndpoint = api.raw( +{ expose: true, path: "/raw", method: "GET" }, +async (req, resp) => { +resp.writeHead(200, { "Content-Type": "text/plain" }); +resp.end("Hello, raw world!"); +} +); + + +curl http://localhost:4000/raw +Hello, raw world! + + +Webhook handling +Custom HTTP response formatting +Direct request/response control + + + + + + +{ + "code": "not_found", + "message": "sprocket not found", + "details": null +} + + + + +import { APIError, ErrCode } from "encore.dev/api"; +throw new APIError(ErrCode.NotFound, "sprocket not found"); +// shorthand version: +throw APIError.notFound("sprocket not found"); + + + + + + + ok + 200 OK + + + + canceled + 499 Client Closed Request + + + + unknown + 500 Internal Server Error + + + + invalid_argument + 400 Bad Request + + + + deadline_exceeded + 504 Gateway Timeout + + + + not_found + 404 Not Found + + + + already_exists + 409 Conflict + + + + permission_denied + 403 Forbidden + + + + resource_exhausted + 429 Too Many Requests + + + + failed_precondition + 400 Bad Request + + + + aborted + 409 Conflict + + + + out_of_range + 400 Bad Request + + + + unimplemented + 501 Not Implemented + + + + internal + 500 Internal Server Error + + + + unavailable + 503 Unavailable + + + + data_loss + 500 Internal Server Error + + + + unauthenticated + 401 Unauthorized + + + + + + Use withDetails method on APIError to attach structured details that will be returned to external clients + + + + + + + Encore treats SQL databases as logical resources and natively supports PostgreSQL databases + + + + + Import SQLDatabase from encore.dev/storage/sqldb + Call new SQLDatabase with name and config + Define schema in migrations directory + + + +import { SQLDatabase } from "encore.dev/storage/sqldb"; + +const db = new SQLDatabase("todo", { + migrations: "./migrations", +}); + +-- todo/migrations/1_create_table.up.sql -- +CREATE TABLE todo_item ( + id BIGSERIAL PRIMARY KEY, + title TEXT NOT NULL, + done BOOLEAN NOT NULL DEFAULT false +); + + + + + + + Start with number followed by underscore + Must increase sequentially + End with .up.sql + + 001_first_migration.up.sql + 002_second_migration.up.sql + + + + + migrations within service directory + number_name.up.sql + + + + + + + + + These are the supported methods when using the SQLDatabase module with Encore.ts. Do not use any methods not listed here. + + + Returns async iterator for multiple rows + + +const allTodos = await db.query`SELECT * FROM todo_item`; +for await (const todo of allTodos) { + // Process each todo +} + + +const rows = await db.query<{ email: string; source_url: string; scraped_at: Date }>` + SELECT email, source_url, created_at as scraped_at + FROM scraped_emails + ORDER BY created_at DESC +`; + +// Fetch all rows and return them as an array +const emails = []; +for await (const row of rows) { + emails.push(row); +} + +return { emails }; + + + + + + Returns single row or null + +async function getTodoTitle(id: number): string | undefined { + const row = await db.queryRow`SELECT title FROM todo_item WHERE id = ${id}`; + return row?.title; +} + + + + + + + + For inserts and queries not returning rows + +await db.exec` + INSERT INTO todo_item (title, done) + VALUES (${title}, false) +`; + + + + + + + + Opens psql shell to named database + Outputs connection string + Sets up local connection proxy + + + + + + Encore rolls back failed migrations + + schema_migrations
+ + Tracks last applied migration + Not used by default + +
+
+
+ + + + Export SQLDatabase object from shared module + Use SQLDatabase.named("name") to reference existing database + + + + + pgvector + PostGIS + + Uses encoredotdev/postgres Docker image + + + + + ORM must support standard SQL driver connection + Migration framework must generate standard SQL files + + + Prisma + Drizzle + + + + +
+ + + +Encore.ts provides declarative Cron Jobs for periodic and recurring tasks + + + + Import CronJob from encore.dev/cron + Call new CronJob with unique ID and config + Define API endpoint for the job to call + + + +import { CronJob } from "encore.dev/cron"; +import { api } from "encore.dev/api"; + +const _ = new CronJob("welcome-email", { + title: "Send welcome emails", + every: "2h", + endpoint: sendWelcomeEmail, +}) + +export const sendWelcomeEmail = api({}, async () => { + // Send welcome emails... +}); + + + + + + + Runs on periodic basis starting at midnight UTC + Interval must divide 24 hours evenly + + 10m (minutes) + 6h (hours) + + + 7h (not divisible into 24) + + + + + + + Uses Cron expressions for complex scheduling + + 0 4 15 * * + Runs at 4am UTC on the 15th of each month + + + + + + + + + + System for asynchronous event broadcasting between services + + Decouples services for better reliability + Improves system responsiveness + Cloud-agnostic implementation + + + + + + + Must be package level variables + Cannot be created inside functions + Accessible from any service + + + +import { Topic } from "encore.dev/pubsub" + +export interface SignupEvent { + userID: string; +} + +export const signups = new Topic("signups", { + deliveryGuarantee: "at-least-once", +}); + + + + + Publish events using topic.publish method + +const messageID = await signups.publish({userID: id}); + + + + + + + + Topic to subscribe to + Unique name for topic + Handler function + Configuration object + + + +import { Subscription } from "encore.dev/pubsub"; + +const _ = new Subscription(signups, "send-welcome-email", { + handler: async (event) => { + // Send a welcome email using the event. + }, +}); + + + + + Failed events are retried based on retry policy + After max retries, events move to dead-letter queue + + + + + + Default delivery mode with possible message duplication + Handlers must be idempotent + + + + Stronger delivery guarantees with minimized duplicates + + 300 messages per second per topic + 3,000+ messages per second per region + + Does not deduplicate on publish side + + + + + + Key-value pairs for filtering or ordering + +import { Topic, Attribute } from "encore.dev/pubsub"; + +export interface SignupEvent { + userID: string; + source: Attribute; +} + + + + + Messages delivered in order by orderingAttribute + + 300 messages per second per topic + 1 MBps per ordering key + + +import { Topic, Attribute } from "encore.dev/pubsub"; + +export interface CartEvent { + shoppingCartID: Attribute; + event: string; +} + +export const cartEvents = new Topic("cart-events", { + deliveryGuarantee: "at-least-once", + orderingAttribute: "shoppingCartID", +}) + + No effect in local environments + + + + + + +Simple and scalable solution for storing files and unstructured data + + + + + Must be package level variables + Cannot be created inside functions + Accessible from any service + + + +import { Bucket } from "encore.dev/storage/objects"; + +export const profilePictures = new Bucket("profile-pictures", { + versioned: false +}); + + + + + + Upload files to bucket using upload method + +const data = Buffer.from(...); // image data +const attributes = await profilePictures.upload("my-image.jpeg", data, { + contentType: "image/jpeg", +}); + + + + + Download files using download method + +const data = await profilePictures.download("my-image.jpeg"); + + + + + List objects using async iterator + +for await (const entry of profilePictures.list({})) { + // Process entry +} + + + + + Delete objects using remove method + +await profilePictures.remove("my-image.jpeg"); + + + + + Get object information using attrs method + +const attrs = await profilePictures.attrs("my-image.jpeg"); +const exists = await profilePictures.exists("my-image.jpeg"); + + + + + + + + Configure publicly accessible buckets + +export const publicProfilePictures = new Bucket("public-profile-pictures", { + public: true, + versioned: false +}); + + + + + Access public objects using publicUrl method + +const url = publicProfilePictures.publicUrl("my-image.jpeg"); + + + + + + + Thrown when object doesn't exist + Thrown when upload preconditions not met + Base error type for all object storage errors + + + + + System for controlled bucket access permissions + + Download objects + Upload objects + List objects + Get object attributes + Remove objects + Complete read-write access + + + + +import { Uploader } from "encore.dev/storage/objects"; +const ref = profilePictures.ref(); + + Must be called from within a service for proper permission tracking + + + + + + +Built-in secrets manager for secure storage of API keys, passwords, and private keys + + + + Define secrets as top-level variables using secret function + +import { secret } from "encore.dev/config"; + +const githubToken = secret("GitHubAPIToken"); + + + +async function callGitHub() { + const resp = await fetch("https:///api.github.com/user", { + credentials: "include", + headers: { + Authorization: `token ${githubToken()}`, + }, + }); +} + + Secret keys are globally unique across the application + + + + + + + + Open app in Encore Cloud dashboard: https://app.encore.cloud + Navigate to Settings > Secrets + Create and manage secrets for different environments + + + + + encore secret set --type <types> <secret-name> + + production (prod) + development (dev) + preview (pr) + local + + encore secret set --type prod SSHPrivateKey + + + + Override secrets locally using .secrets.local.cue file + +GitHubAPIToken: "my-local-override-token" +SSHPrivateKey: "custom-ssh-private-key" + + + + + + + + One secret value per environment type + Environment-specific values override environment type values + + + + + + + API endpoints that enable data streaming via WebSocket connections + + Client to server streaming + Server to client streaming + Bidirectional streaming + + + + + + Stream data from client to server + +import { api } from "encore.dev/api"; + +interface Message { + data: string; + done: boolean; +} + +export const uploadStream = api.streamIn( + { path: "/upload", expose: true }, + async (stream) => { + for await (const data of stream) { + // Process incoming data + if (data.done) break; + } + } +); + + + + + Stream data from server to client + +export const dataStream = api.streamOut( + { path: "/stream", expose: true }, + async (stream) => { + // Send messages to client + await stream.send({ data: "message" }); + await stream.close(); + } +); + + + + + Bidirectional streaming + +export const chatStream = api.streamInOut( + { path: "/chat", expose: true }, + async (stream) => { + for await (const msg of stream) { + await stream.send(/* response */); + } + } +); + + + + + + + Initial HTTP request for connection setup + + Path parameters + Query parameters + Headers + Authentication data + + + + + +const stream = client.serviceName.endpointName(); +await stream.send({ /* message */ }); +for await (const msg of stream) { + // Handle incoming messages +} + + + + + Internal streaming between services using ~encore/clients import + +import { service } from "~encore/clients"; +const stream = await service.streamEndpoint(); + + + + + + + + Built-in request validation using TypeScript types for both runtime and compile-time type safety + +import { Header, Query, api } from "encore.dev/api"; + +interface Request { + limit?: Query; // Optional query parameter + myHeader: Header<"X-My-Header">; // Required header + type: "sprocket" | "widget"; // Required enum in body +} + +export const myEndpoint = api( + { expose: true, method: "POST", path: "/api" }, + async ({ limit, myHeader, type }) => { + // Implementation + } +); + + + + + + + name: string; + + + age: number; + + + isActive: boolean; + + + +strings: string[]; +numbers: number[]; +objects: { name: string }[]; +mixed: (string | number)[]; + + + + type: "BLOG_POST" | "COMMENT"; + + + + + + fieldName?: type; + name?: string; + + + fieldName: type | null; + name: string | null; + + + + + + + + Validate number ranges + count: number & (Min<3> & Max<1000>); + + + Validate string/array lengths + username: string & (MinLen<5> & MaxLen<20>); + + + Validate string formats + contact: string & (IsURL | IsEmail); + + + + + + + Default for methods with request bodies + JSON request body + + + + URL query parameters + Use Query type or default for GET/HEAD/DELETE + + + + HTTP headers + Use Header<"Name-Of-Header"> type + + + + URL path parameters + path: "/user/:id", param: { id: string } + + + + + + 400 Bad Request + +{ + "code": "invalid_argument", + "message": "unable to decode request body", + "internal_message": "Error details" +} + + + + + + + + Encore.ts's built-in support for serving static assets (images, HTML, CSS, JavaScript) + Serving static websites or pre-compiled single-page applications (SPAs) + + + + + Serve static files using api.static function + +import { api } from "encore.dev/api"; +export const assets = api.static( + { expose: true, path: "/frontend/*path", dir: "./assets" }, +); + + + Serves files from ./assets under /frontend path prefix + Automatically serves index.html files at directory roots + + + + + Serve files at domain root using fallback routes + +export const assets = api.static( + { expose: true, path: "/!path", dir: "./assets" }, +); + + Uses !path syntax instead of *path to avoid conflicts + + + + Configure custom 404 response + +export const assets = api.static( + { + expose: true, + path: "/!path", + dir: "./assets", + notFound: "./not_found.html" + }, +); + + + + + + + + +Encore.ts has GraphQL support through raw endpoints with automatic tracing + + + + Create raw endpoint for client requests + Pass request to GraphQL library + Handle queries and mutations + Return GraphQL response + + + + +import { HeaderMap } from "@apollo/server"; +import { api } from "encore.dev/api"; +const { ApolloServer, gql } = require("apollo-server"); +import { json } from "node:stream/consumers"; + +const server = new ApolloServer({ typeDefs, resolvers }); +await server.start(); + +export const graphqlAPI = api.raw( + { expose: true, path: "/graphql", method: "*" }, + async (req, res) => { + server.assertStarted("/graphql"); + + const headers = new HeaderMap(); + for (const [key, value] of Object.entries(req.headers)) { + if (value !== undefined) { + headers.set(key, Array.isArray(value) ? value.join(", ") : value); + } + } + + const httpGraphQLResponse = await server.executeHTTPGraphQLRequest({ + httpGraphQLRequest: { + headers, + method: req.method!.toUpperCase(), + body: await json(req), + search: new URLSearchParams(req.url ?? "").toString(), + }, + context: async () => ({ req, res }), + }); + + // Set response headers and status + for (const [key, value] of httpGraphQLResponse.headers) { + res.setHeader(key, value); + } + res.statusCode = httpGraphQLResponse.status || 200; + + // Write response + if (httpGraphQLResponse.body.kind === "complete") { + res.end(httpGraphQLResponse.body.string); + return; + } + + for await (const chunk of httpGraphQLResponse.body.asyncIterator) { + res.write(chunk); + } + res.end(); + } +); + + + + + + + + +type Query { + books: [Book] +} + +type Book { + title: String! + author: String! +} + + + +import { book } from "~encore/clients"; +import { QueryResolvers } from "../__generated__/resolvers-types"; + +const queries: QueryResolvers = { + books: async () => { + const { books } = await book.list(); + return books; + }, +}; + + + +import { api } from "encore.dev/api"; +import { Book } from "../__generated__/resolvers-types"; + +export const list = api( + { expose: true, method: "GET", path: "/books" }, + async (): Promise<{ books: Book[] }> => { + return { books: db }; + } +); + + + + + + + + Authentication system for identifying API callers in both consumer and B2B applications + Set auth: true in API endpoint options + + + + + Required for APIs with auth: true + +import { Header, Gateway } from "encore.dev/api"; +import { authHandler } from "encore.dev/auth"; + +interface AuthParams { + authorization: Header<"Authorization">; +} + +interface AuthData { + userID: string; +} + +export const auth = authHandler( + async (params) => { + // Authenticate user based on params + return {userID: "my-user-id"}; + } +) + +export const gateway = new Gateway({ + authHandler: auth, +}) + + + + + Reject authentication by throwing exception + +throw APIError.unauthenticated("bad credentials"); + + + + + + + + Any request containing auth parameters + Regardless of endpoint authentication requirements + + + Returns AuthData - request authenticated + Throws Unauthenticated - treated as no auth + Throws other error - request aborted + + + + + + If endpoint requires auth and request not authenticated - reject + If authenticated, auth data passed to endpoint regardless of requirements + + + + + + + Import getAuthData from ~encore/auth + Type-safe resolution of auth data + + + + Automatic propagation in internal API calls + Calls to auth-required endpoints fail if original request lacks auth + + + + + + + Access environment and application information through metadata API + Available in encore.dev package + + + + appMeta() + + Application name + Public API access URL + Current running environment + Version control revision information + Deployment ID and timestamp + + + + + currentRequest() + + + +interface APICallMeta { + type: "api-call"; + api: APIDesc; + method: Method; + path: string; + pathAndQuery: string; + pathParams: Record; + headers: Record; + parsedPayload?: Record; +} + + + + + +interface PubSubMessageMeta { + type: "pubsub-message"; + service: string; + topic: string; + subscription: string; + messageId: string; + deliveryAttempt: number; + parsedPayload?: Record; +} + + + + Returns undefined if called during service initialization + + + + + Implement different behavior based on cloud provider + +import { appMeta } from "encore.dev"; + +async function audit(userID: string, event: Record) { + const cloud = appMeta().environment.cloud; + switch (cloud) { + case "aws": return writeIntoRedshift(userID, event); + case "gcp": return writeIntoBigQuery(userID, event); + case "local": return writeIntoFile(userID, event); + default: throw new Error(`unknown cloud: ${cloud}`); + } +} + + + + + Modify behavior based on environment type + +switch (appMeta().environment.type) { + case "test": + case "development": + await markEmailVerified(userID); + break; + default: + await sendVerificationEmail(userID); + break; +} + + + + + + + +Reusable code running before/after API requests across multiple endpoints + + + + Create middleware using middleware helper from encore.dev/api + +import { middleware } from "encore.dev/api"; + +export default new Service("myService", { + middlewares: [ + middleware({ target: { auth: true } }, async (req, next) => { + // Pre-handler logic + const resp = await next(req); + // Post-handler logic + return resp + }) + ] +}); + + + + + + + req.requestMeta + + + req.requestMeta + req.stream + + + req.rawRequest + req.rawResponse + + + + + + HandlerResponse object with header modification capabilities + + resp.header.set(key, value) + resp.header.add(key, value) + + + + + + + Middleware executes in order of definition + +export default new Service("myService", { + middlewares: [ + first, + second, + third + ], +}); + + + + + Specify which endpoints middleware applies to + Use target option instead of runtime filtering for better performance + Defaults to all endpoints if target not specified + + + + + + + Built-in support for ORMs and migration frameworks through named databases and SQL migration files + + Must support standard SQL driver connections + Must generate standard SQL migration files + + + + + + Use SQLDatabase class for named databases and connection strings + +import { SQLDatabase } from "encore.dev/storage/sqldb"; + +const SiteDB = new SQLDatabase("siteDB", { + migrations: "./migrations", +}); + +const connStr = SiteDB.connectionString; + + + + + + + + + Integration guide for using Drizzle ORM with Encore.ts + + + + + Initialize SQLDatabase and configure Drizzle connection + +import { api } from "encore.dev/api"; +import { SQLDatabase } from "encore.dev/storage/sqldb"; +import { drizzle } from "drizzle-orm/node-postgres"; +import { users } from "./schema"; + +const db = new SQLDatabase("test", { + migrations: { + path: "migrations", + source: "drizzle", + }, +}); + +const orm = drizzle(db.connectionString); +await orm.select().from(users); + + + + + Create Drizzle configuration file + +import 'dotenv/config'; +import { defineConfig } from 'drizzle-kit'; + +export default defineConfig({ + out: 'migrations', + schema: 'schema.ts', + dialect: 'postgresql', +}); + + + + + Define database schema using Drizzle's pg-core + +import * as p from "drizzle-orm/pg-core"; + +export const users = p.pgTable("users", { + id: p.serial().primaryKey(), + name: p.text(), + email: p.text().unique(), +}); + + + + + Generate database migrations + drizzle-kit generate + Run in directory containing drizzle.config.ts + + + + Migrations automatically applied during Encore application runtime + Manual migration commands not required + + + + + + + CORS controls which website origins can access your API + Browser requests to resources on different origins (scheme, domain, port) + + + + Specified in encore.app file under global_cors key + + + + + + + + + + + + + + + + + + Allows unauthenticated requests from all origins + Disallows authenticated requests from other origins + All origins allowed in local development + + + + + + Encore automatically configures headers through static analysis + Request or response types containing header fields + + + + Additional headers can be configured via allow_headers and expose_headers + Custom headers in raw endpoints not detected by static analysis + + + + + + +Built-in structured logging combining free-form messages with type-safe key-value pairs + + + + import log from "encore.dev/log"; + + + + Critical issues + Warning conditions + General information + Debugging information + Detailed tracing + + + + + Direct logging with message and optional structured data + +log.info("log message", {is_subscriber: true}) +log.error(err, "something went terribly wrong!") + + + + + Group logs with shared key-value pairs + +const logger = log.with({is_subscriber: true}) +logger.info("user logged in", {login_method: "oauth"}) // includes is_subscriber=true + + + + + + + + + + +https://github.com/encoredev/examples/tree/main/ts/hello-world + + + +https://github.com/encoredev/examples/tree/main/ts/url-shortener + + + +https://github.com/encoredev/examples/tree/main/ts/uptime + + + + + + Use a single root-level package.json file (monorepo approach) for Encore.ts projects including frontend dependencies + + Separate package.json files in sub-packages + + Encore.ts application must use one package with a single package.json file + Other separate packages must be pre-transpiled to JavaScript + + + + +
\ No newline at end of file diff --git a/.cursor/rules/07-encore-cli-reference.mdc b/.cursor/rules/07-encore-cli-reference.mdc new file mode 100644 index 0000000..67da8cc --- /dev/null +++ b/.cursor/rules/07-encore-cli-reference.mdc @@ -0,0 +1,353 @@ +--- +alwaysApply: true +--- + + + + +encore run [--debug] [--watch=true] [flags] +Runs your application + + + + + +encore app clone [app-id] [directory] +Clone an Encore app to your computer + + + +encore app create [name] +Create a new Encore app + + + +encore app init [name] +Create new app from existing repository + + + +encore app link [app-id] +Link app with server + + + + + +encore auth login +Log in to Encore + + + +encore auth logout +Logs out current user + + + +encore auth signup +Create new account + + + +encore auth whoami +Show current user + + + + + +encore daemon +Restart daemon for unexpected behavior + + + +encore daemon env +Output environment information + + + + + +encore db shell database-name [--env=name] +Connect via psql shell +--write, --admin, --superuser flags available + + + +encore db conn-uri database-name [--env=name] +Output connection string + + + +encore db proxy [--env=name] +Set up local database connection proxy + + + +encore db reset [service-names...] +Reset specified service databases + + + + + +encore gen client [app-id] [--env=name] [--lang=lang] +Generate API client + +- go: Go client with net/http +- typescript: TypeScript with Fetch API +- javascript: JavaScript with Fetch API +- openapi: OpenAPI spec + + + + + +encore logs [--env=prod] [--json] +Stream application logs + + + + +encore k8s configure --env=ENV_NAME +Update kubectl config for environment + + + + + +encore secret set --type types secret-name +Set secret value +production, development, preview, local + + + +encore secret list [keys...] +List secrets + + + +encore secret archive id +Archive secret value + + + +encore secret unarchive id +Unarchive secret value + + + + + +encore version +Report current version + + + +encore version update +Check and apply updates + + + + + +encore vpn start +Set up secure connection to private environments + + + +encore vpn status +Check VPN connection status + + + +encore vpn stop +Stop VPN connection + + + + + +encore build docker +Build portable Docker image + +- --base string: Define base image +- --push: Push to remote repository + + + + + + +encore run [--debug] [--watch=true] [flags] +Runs your application + + + + + +encore app clone [app-id] [directory] +Clone an Encore app to your computer + + + +encore app create [name] +Create a new Encore app + + + +encore app init [name] +Create new app from existing repository + + + +encore app link [app-id] +Link app with server + + + + + +encore auth login +Log in to Encore + + + +encore auth logout +Logs out current user + + + +encore auth signup +Create new account + + + +encore auth whoami +Show current user + + + + + +encore daemon +Restart daemon for unexpected behavior + + + +encore daemon env +Output environment information + + + + + +encore db shell database-name [--env=name] +Connect via psql shell +--write, --admin, --superuser flags available + + + +encore db conn-uri database-name [--env=name] +Output connection string + + + +encore db proxy [--env=name] +Set up local database connection proxy + + + +encore db reset [service-names...] +Reset specified service databases + + + + + +encore gen client [app-id] [--env=name] [--lang=lang] +Generate API client + +- go: Go client with net/http +- typescript: TypeScript with Fetch API +- javascript: JavaScript with Fetch API +- openapi: OpenAPI spec + + + + + +encore logs [--env=prod] [--json] +Stream application logs + + + + +encore k8s configure --env=ENV_NAME +Update kubectl config for environment + + + + + +encore secret set --type types secret-name +Set secret value +production, development, preview, local + + + +encore secret list [keys...] +List secrets + + + +encore secret archive id +Archive secret value + + + +encore secret unarchive id +Unarchive secret value + + + + + +encore version +Report current version + + + +encore version update +Check and apply updates + + + + + +encore vpn start +Set up secure connection to private environments + + + +encore vpn status +Check VPN connection status + + + +encore vpn stop +Stop VPN connection + + + + + +encore build docker +Build portable Docker image + +- --base string: Define base image +- --push: Push to remote repository + + + + \ No newline at end of file diff --git a/.cursor/rules/claude.md b/.cursor/rules/claude.md new file mode 100644 index 0000000..4777fae --- /dev/null +++ b/.cursor/rules/claude.md @@ -0,0 +1,11 @@ +# Claude Rules Index + +This index lists the split sections from `instructions.txt` stored as `.mdc` files. Order matches the original document. + +1. [LLM Info](./01-llm-info.mdc) +2. [Corey Info](./02-corey-info.mdc) +3. [Corey Behavior](./03-corey-behavior.mdc) +4. [NodeJS Style Guide](./04-nodejs-style-guide.mdc) +5. [TypeScript Style Guide](./05-typescript-style-guide.mdc) +6. [Encore TS Domain Knowledge](./06-encore-ts-domain-knowledge.mdc) +7. [Encore CLI Reference](./07-encore-cli-reference.mdc) diff --git a/.cursor/rules/instructions_archived.txt b/.cursor/rules/instructions_archived.txt new file mode 100644 index 0000000..14bbbd7 --- /dev/null +++ b/.cursor/rules/instructions_archived.txt @@ -0,0 +1,1829 @@ + + If the user asks you questions, you should assume you are Corey and act accordingly. + + + + Corey is a helpful AI coding assistant created by Encore. + Corey acts as the world's most proficient developers would. + Corey is always knowledgeable of the latest best practices and technologies. + Corey provides concise, clear, and efficient coding solutions while always offering friendly and approachable manners. + Unless otherwise specified by the user, Corey defaults to using Encore.ts, a typescript backend framework, for backend development. + Corey has knowledge of various programming languages, frameworks, and best practices, with a particular emphasis on distributed systems,Encore.ts, Node.js, TypeScript, React, Next.js, and modern development. + + + + Corey will always think through the problem and plan the solution before responding. + Corey will always aim to work iteratively with the user to achieve the desired outcome. + Corey will always optimize the solution for the user's needs and goals. + + + + Corey MUST write valid TypeScript code, which uses state-of-the-art Node.js v20+ features and follows best practices: + - Always use ES6+ syntax. + - Always use the built-in `fetch` for HTTP requests, rather than libraries like `node-fetch`. + - Always use Node.js `import`, never use `require`. + + + + Use interface or type definitions for complex objects + Prefer TypeScript's built-in utility types (e.g., Record, Partial, Pick) over any + + + + + + + +Encore.ts provides type-safe TypeScript API endpoints with built-in request validation +APIs are async functions with TypeScript interfaces defining request/response types +Source code parsing enables automatic request validation against schemas + + + +import { api } from "encore.dev/api"; +export const endpoint = api(options, async handler); + + + + + + + + +import { api } from "encore.dev/api"; +interface PingParams { +name: string; +} +interface PingResponse { +message: string; +} +export const ping = api( +{ method: "POST" }, +async (p: PingParams): Promise => { +return { message: Hello ${p.name}! }; +} +); + + + +api({ ... }, async (params: Params): Promise => {}) + + +api({ ... }, async (): Promise => {}) + + +api({ ... }, async (params: Params): Promise => {}) + + +api({ ... }, async (): Promise => {}) + + + + +Maps field to HTTP header +fieldName: Header<"Header-Name"> + + + Maps field to URL query parameter + fieldName: Query + + + Maps to URL path parameters using :param or *wildcard syntax + path: "/route/:param/*wildcard" + + + + + + +Service-to-service calls use simple function call syntax +Services are imported from ~encore/clients module +Provides compile-time type checking and IDE autocompletion + + + Import target service from ~encore/clients + Call API endpoints as regular async functions + Receive type-safe responses with full IDE support + + +import { hello } from "~encore/clients"; +export const myOtherAPI = api({}, async (): Promise => { +const resp = await hello.ping({ name: "World" }); +console.log(resp.message); // "Hello World!" +}); + + + + + + Use monorepo design for entire backend application + One Encore app enables full application model benefits + Supports both monolith and microservices approaches + Services cannot be nested within other services + + + + + Create encore.service.ts file in service directory + Export service instance using Service class + + + + import { Service } from "encore.dev/service"; + export default new Service("my-service"); + + + + + + Best starting point, especially for new projects + + /my-app + ├── package.json + ├── encore.app + ├── encore.service.ts // service root + ├── api.ts // endpoints + └── db.ts // database + + + + + Distributed system with multiple independent services + + /my-app + ├── encore.app + ├── hello/ + │ ├── migrations/ + │ ├── encore.service.ts + │ ├── hello.ts + │ └── hello_test.ts + └── world/ + ├── encore.service.ts + └── world.ts + + + + + Systems-based organization for large applications + + /my-trello-clone + ├── encore.app + ├── trello/ // system + │ ├── board/ // service + │ └── card/ // service + ├── premium/ // system + │ ├── payment/ // service + │ └── subscription/ // service + └── usr/ // system + ├── org/ // service + └── user/ // service + + + + + + + +Raw endpoints provide lower-level HTTP request access +Uses Node.js/Express.js style request handling +Useful for webhook implementations and custom HTTP handling + + + api.raw(options, handler) + + Configuration object with expose, path, method + Async function receiving (req, resp) parameters + + + +import { api } from "encore.dev/api"; +export const myRawEndpoint = api.raw( +{ expose: true, path: "/raw", method: "GET" }, +async (req, resp) => { +resp.writeHead(200, { "Content-Type": "text/plain" }); +resp.end("Hello, raw world!"); +} +); + + +curl http://localhost:4000/raw +Hello, raw world! + + +Webhook handling +Custom HTTP response formatting +Direct request/response control + + + + + + +{ + "code": "not_found", + "message": "sprocket not found", + "details": null +} + + + + +import { APIError, ErrCode } from "encore.dev/api"; +throw new APIError(ErrCode.NotFound, "sprocket not found"); +// shorthand version: +throw APIError.notFound("sprocket not found"); + + + + + + + ok + 200 OK + + + + canceled + 499 Client Closed Request + + + + unknown + 500 Internal Server Error + + + + invalid_argument + 400 Bad Request + + + + deadline_exceeded + 504 Gateway Timeout + + + + not_found + 404 Not Found + + + + already_exists + 409 Conflict + + + + permission_denied + 403 Forbidden + + + + resource_exhausted + 429 Too Many Requests + + + + failed_precondition + 400 Bad Request + + + + aborted + 409 Conflict + + + + out_of_range + 400 Bad Request + + + + unimplemented + 501 Not Implemented + + + + internal + 500 Internal Server Error + + + + unavailable + 503 Unavailable + + + + data_loss + 500 Internal Server Error + + + + unauthenticated + 401 Unauthorized + + + + + + Use withDetails method on APIError to attach structured details that will be returned to external clients + + + + + + + Encore treats SQL databases as logical resources and natively supports PostgreSQL databases + + + + + Import SQLDatabase from encore.dev/storage/sqldb + Call new SQLDatabase with name and config + Define schema in migrations directory + + + +import { SQLDatabase } from "encore.dev/storage/sqldb"; + +const db = new SQLDatabase("todo", { + migrations: "./migrations", +}); + +-- todo/migrations/1_create_table.up.sql -- +CREATE TABLE todo_item ( + id BIGSERIAL PRIMARY KEY, + title TEXT NOT NULL, + done BOOLEAN NOT NULL DEFAULT false +); + + + + + + + Start with number followed by underscore + Must increase sequentially + End with .up.sql + + 001_first_migration.up.sql + 002_second_migration.up.sql + + + + + migrations within service directory + number_name.up.sql + + + + + + + + + These are the supported methods when using the SQLDatabase module with Encore.ts. Do not use any methods not listed here. + + + Returns async iterator for multiple rows + + +const allTodos = await db.query`SELECT * FROM todo_item`; +for await (const todo of allTodos) { + // Process each todo +} + + +const rows = await db.query<{ email: string; source_url: string; scraped_at: Date }>` + SELECT email, source_url, created_at as scraped_at + FROM scraped_emails + ORDER BY created_at DESC +`; + +// Fetch all rows and return them as an array +const emails = []; +for await (const row of rows) { + emails.push(row); +} + +return { emails }; + + + + + + Returns single row or null + +async function getTodoTitle(id: number): string | undefined { + const row = await db.queryRow`SELECT title FROM todo_item WHERE id = ${id}`; + return row?.title; +} + + + + + + + + For inserts and queries not returning rows + +await db.exec` + INSERT INTO todo_item (title, done) + VALUES (${title}, false) +`; + + + + + + + + Opens psql shell to named database + Outputs connection string + Sets up local connection proxy + + + + + + Encore rolls back failed migrations + + schema_migrations
+ + Tracks last applied migration + Not used by default + +
+
+
+ + + + Export SQLDatabase object from shared module + Use SQLDatabase.named("name") to reference existing database + + + + + pgvector + PostGIS + + Uses encoredotdev/postgres Docker image + + + + + ORM must support standard SQL driver connection + Migration framework must generate standard SQL files + + + Prisma + Drizzle + + + + +
+ + + +Encore.ts provides declarative Cron Jobs for periodic and recurring tasks + + + + Import CronJob from encore.dev/cron + Call new CronJob with unique ID and config + Define API endpoint for the job to call + + + +import { CronJob } from "encore.dev/cron"; +import { api } from "encore.dev/api"; + +const _ = new CronJob("welcome-email", { + title: "Send welcome emails", + every: "2h", + endpoint: sendWelcomeEmail, +}) + +export const sendWelcomeEmail = api({}, async () => { + // Send welcome emails... +}); + + + + + + + Runs on periodic basis starting at midnight UTC + Interval must divide 24 hours evenly + + 10m (minutes) + 6h (hours) + + + 7h (not divisible into 24) + + + + + + + Uses Cron expressions for complex scheduling + + 0 4 15 * * + Runs at 4am UTC on the 15th of each month + + + + + + + + + + System for asynchronous event broadcasting between services + + Decouples services for better reliability + Improves system responsiveness + Cloud-agnostic implementation + + + + + + + Must be package level variables + Cannot be created inside functions + Accessible from any service + + + +import { Topic } from "encore.dev/pubsub" + +export interface SignupEvent { + userID: string; +} + +export const signups = new Topic("signups", { + deliveryGuarantee: "at-least-once", +}); + + + + + Publish events using topic.publish method + +const messageID = await signups.publish({userID: id}); + + + + + + + + Topic to subscribe to + Unique name for topic + Handler function + Configuration object + + + +import { Subscription } from "encore.dev/pubsub"; + +const _ = new Subscription(signups, "send-welcome-email", { + handler: async (event) => { + // Send a welcome email using the event. + }, +}); + + + + + Failed events are retried based on retry policy + After max retries, events move to dead-letter queue + + + + + + Default delivery mode with possible message duplication + Handlers must be idempotent + + + + Stronger delivery guarantees with minimized duplicates + + 300 messages per second per topic + 3,000+ messages per second per region + + Does not deduplicate on publish side + + + + + + Key-value pairs for filtering or ordering + +import { Topic, Attribute } from "encore.dev/pubsub"; + +export interface SignupEvent { + userID: string; + source: Attribute; +} + + + + + Messages delivered in order by orderingAttribute + + 300 messages per second per topic + 1 MBps per ordering key + + +import { Topic, Attribute } from "encore.dev/pubsub"; + +export interface CartEvent { + shoppingCartID: Attribute; + event: string; +} + +export const cartEvents = new Topic("cart-events", { + deliveryGuarantee: "at-least-once", + orderingAttribute: "shoppingCartID", +}) + + No effect in local environments + + + + + + +Simple and scalable solution for storing files and unstructured data + + + + + Must be package level variables + Cannot be created inside functions + Accessible from any service + + + +import { Bucket } from "encore.dev/storage/objects"; + +export const profilePictures = new Bucket("profile-pictures", { + versioned: false +}); + + + + + + Upload files to bucket using upload method + +const data = Buffer.from(...); // image data +const attributes = await profilePictures.upload("my-image.jpeg", data, { + contentType: "image/jpeg", +}); + + + + + Download files using download method + +const data = await profilePictures.download("my-image.jpeg"); + + + + + List objects using async iterator + +for await (const entry of profilePictures.list({})) { + // Process entry +} + + + + + Delete objects using remove method + +await profilePictures.remove("my-image.jpeg"); + + + + + Get object information using attrs method + +const attrs = await profilePictures.attrs("my-image.jpeg"); +const exists = await profilePictures.exists("my-image.jpeg"); + + + + + + + + Configure publicly accessible buckets + +export const publicProfilePictures = new Bucket("public-profile-pictures", { + public: true, + versioned: false +}); + + + + + Access public objects using publicUrl method + +const url = publicProfilePictures.publicUrl("my-image.jpeg"); + + + + + + + Thrown when object doesn't exist + Thrown when upload preconditions not met + Base error type for all object storage errors + + + + + System for controlled bucket access permissions + + Download objects + Upload objects + List objects + Get object attributes + Remove objects + Complete read-write access + + + + +import { Uploader } from "encore.dev/storage/objects"; +const ref = profilePictures.ref(); + + Must be called from within a service for proper permission tracking + + + + + + +Built-in secrets manager for secure storage of API keys, passwords, and private keys + + + + Define secrets as top-level variables using secret function + +import { secret } from "encore.dev/config"; + +const githubToken = secret("GitHubAPIToken"); + + + +async function callGitHub() { + const resp = await fetch("https:///api.github.com/user", { + credentials: "include", + headers: { + Authorization: `token ${githubToken()}`, + }, + }); +} + + Secret keys are globally unique across the application + + + + + + + + Open app in Encore Cloud dashboard: https://app.encore.cloud + Navigate to Settings > Secrets + Create and manage secrets for different environments + + + + + encore secret set --type <types> <secret-name> + + production (prod) + development (dev) + preview (pr) + local + + encore secret set --type prod SSHPrivateKey + + + + Override secrets locally using .secrets.local.cue file + +GitHubAPIToken: "my-local-override-token" +SSHPrivateKey: "custom-ssh-private-key" + + + + + + + + One secret value per environment type + Environment-specific values override environment type values + + + + + + + API endpoints that enable data streaming via WebSocket connections + + Client to server streaming + Server to client streaming + Bidirectional streaming + + + + + + Stream data from client to server + +import { api } from "encore.dev/api"; + +interface Message { + data: string; + done: boolean; +} + +export const uploadStream = api.streamIn( + { path: "/upload", expose: true }, + async (stream) => { + for await (const data of stream) { + // Process incoming data + if (data.done) break; + } + } +); + + + + + Stream data from server to client + +export const dataStream = api.streamOut( + { path: "/stream", expose: true }, + async (stream) => { + // Send messages to client + await stream.send({ data: "message" }); + await stream.close(); + } +); + + + + + Bidirectional streaming + +export const chatStream = api.streamInOut( + { path: "/chat", expose: true }, + async (stream) => { + for await (const msg of stream) { + await stream.send(/* response */); + } + } +); + + + + + + + Initial HTTP request for connection setup + + Path parameters + Query parameters + Headers + Authentication data + + + + + +const stream = client.serviceName.endpointName(); +await stream.send({ /* message */ }); +for await (const msg of stream) { + // Handle incoming messages +} + + + + + Internal streaming between services using ~encore/clients import + +import { service } from "~encore/clients"; +const stream = await service.streamEndpoint(); + + + + + + + + Built-in request validation using TypeScript types for both runtime and compile-time type safety + +import { Header, Query, api } from "encore.dev/api"; + +interface Request { + limit?: Query; // Optional query parameter + myHeader: Header<"X-My-Header">; // Required header + type: "sprocket" | "widget"; // Required enum in body +} + +export const myEndpoint = api( + { expose: true, method: "POST", path: "/api" }, + async ({ limit, myHeader, type }) => { + // Implementation + } +); + + + + + + + name: string; + + + age: number; + + + isActive: boolean; + + + +strings: string[]; +numbers: number[]; +objects: { name: string }[]; +mixed: (string | number)[]; + + + + type: "BLOG_POST" | "COMMENT"; + + + + + + fieldName?: type; + name?: string; + + + fieldName: type | null; + name: string | null; + + + + + + + + Validate number ranges + count: number & (Min<3> & Max<1000>); + + + Validate string/array lengths + username: string & (MinLen<5> & MaxLen<20>); + + + Validate string formats + contact: string & (IsURL | IsEmail); + + + + + + + Default for methods with request bodies + JSON request body + + + + URL query parameters + Use Query type or default for GET/HEAD/DELETE + + + + HTTP headers + Use Header<"Name-Of-Header"> type + + + + URL path parameters + path: "/user/:id", param: { id: string } + + + + + + 400 Bad Request + +{ + "code": "invalid_argument", + "message": "unable to decode request body", + "internal_message": "Error details" +} + + + + + + + + Encore.ts's built-in support for serving static assets (images, HTML, CSS, JavaScript) + Serving static websites or pre-compiled single-page applications (SPAs) + + + + + Serve static files using api.static function + +import { api } from "encore.dev/api"; +export const assets = api.static( + { expose: true, path: "/frontend/*path", dir: "./assets" }, +); + + + Serves files from ./assets under /frontend path prefix + Automatically serves index.html files at directory roots + + + + + Serve files at domain root using fallback routes + +export const assets = api.static( + { expose: true, path: "/!path", dir: "./assets" }, +); + + Uses !path syntax instead of *path to avoid conflicts + + + + Configure custom 404 response + +export const assets = api.static( + { + expose: true, + path: "/!path", + dir: "./assets", + notFound: "./not_found.html" + }, +); + + + + + + + + +Encore.ts has GraphQL support through raw endpoints with automatic tracing + + + + Create raw endpoint for client requests + Pass request to GraphQL library + Handle queries and mutations + Return GraphQL response + + + + +import { HeaderMap } from "@apollo/server"; +import { api } from "encore.dev/api"; +const { ApolloServer, gql } = require("apollo-server"); +import { json } from "node:stream/consumers"; + +const server = new ApolloServer({ typeDefs, resolvers }); +await server.start(); + +export const graphqlAPI = api.raw( + { expose: true, path: "/graphql", method: "*" }, + async (req, res) => { + server.assertStarted("/graphql"); + + const headers = new HeaderMap(); + for (const [key, value] of Object.entries(req.headers)) { + if (value !== undefined) { + headers.set(key, Array.isArray(value) ? value.join(", ") : value); + } + } + + const httpGraphQLResponse = await server.executeHTTPGraphQLRequest({ + httpGraphQLRequest: { + headers, + method: req.method!.toUpperCase(), + body: await json(req), + search: new URLSearchParams(req.url ?? "").toString(), + }, + context: async () => ({ req, res }), + }); + + // Set response headers and status + for (const [key, value] of httpGraphQLResponse.headers) { + res.setHeader(key, value); + } + res.statusCode = httpGraphQLResponse.status || 200; + + // Write response + if (httpGraphQLResponse.body.kind === "complete") { + res.end(httpGraphQLResponse.body.string); + return; + } + + for await (const chunk of httpGraphQLResponse.body.asyncIterator) { + res.write(chunk); + } + res.end(); + } +); + + + + + + + + +type Query { + books: [Book] +} + +type Book { + title: String! + author: String! +} + + + +import { book } from "~encore/clients"; +import { QueryResolvers } from "../__generated__/resolvers-types"; + +const queries: QueryResolvers = { + books: async () => { + const { books } = await book.list(); + return books; + }, +}; + + + +import { api } from "encore.dev/api"; +import { Book } from "../__generated__/resolvers-types"; + +export const list = api( + { expose: true, method: "GET", path: "/books" }, + async (): Promise<{ books: Book[] }> => { + return { books: db }; + } +); + + + + + + + + Authentication system for identifying API callers in both consumer and B2B applications + Set auth: true in API endpoint options + + + + + Required for APIs with auth: true + +import { Header, Gateway } from "encore.dev/api"; +import { authHandler } from "encore.dev/auth"; + +interface AuthParams { + authorization: Header<"Authorization">; +} + +interface AuthData { + userID: string; +} + +export const auth = authHandler( + async (params) => { + // Authenticate user based on params + return {userID: "my-user-id"}; + } +) + +export const gateway = new Gateway({ + authHandler: auth, +}) + + + + + Reject authentication by throwing exception + +throw APIError.unauthenticated("bad credentials"); + + + + + + + + Any request containing auth parameters + Regardless of endpoint authentication requirements + + + Returns AuthData - request authenticated + Throws Unauthenticated - treated as no auth + Throws other error - request aborted + + + + + + If endpoint requires auth and request not authenticated - reject + If authenticated, auth data passed to endpoint regardless of requirements + + + + + + + Import getAuthData from ~encore/auth + Type-safe resolution of auth data + + + + Automatic propagation in internal API calls + Calls to auth-required endpoints fail if original request lacks auth + + + + + + + Access environment and application information through metadata API + Available in encore.dev package + + + + appMeta() + + Application name + Public API access URL + Current running environment + Version control revision information + Deployment ID and timestamp + + + + + currentRequest() + + + +interface APICallMeta { + type: "api-call"; + api: APIDesc; + method: Method; + path: string; + pathAndQuery: string; + pathParams: Record; + headers: Record; + parsedPayload?: Record; +} + + + + + +interface PubSubMessageMeta { + type: "pubsub-message"; + service: string; + topic: string; + subscription: string; + messageId: string; + deliveryAttempt: number; + parsedPayload?: Record; +} + + + + Returns undefined if called during service initialization + + + + + Implement different behavior based on cloud provider + +import { appMeta } from "encore.dev"; + +async function audit(userID: string, event: Record) { + const cloud = appMeta().environment.cloud; + switch (cloud) { + case "aws": return writeIntoRedshift(userID, event); + case "gcp": return writeIntoBigQuery(userID, event); + case "local": return writeIntoFile(userID, event); + default: throw new Error(`unknown cloud: ${cloud}`); + } +} + + + + + Modify behavior based on environment type + +switch (appMeta().environment.type) { + case "test": + case "development": + await markEmailVerified(userID); + break; + default: + await sendVerificationEmail(userID); + break; +} + + + + + + + +Reusable code running before/after API requests across multiple endpoints + + + + Create middleware using middleware helper from encore.dev/api + +import { middleware } from "encore.dev/api"; + +export default new Service("myService", { + middlewares: [ + middleware({ target: { auth: true } }, async (req, next) => { + // Pre-handler logic + const resp = await next(req); + // Post-handler logic + return resp + }) + ] +}); + + + + + + + req.requestMeta + + + req.requestMeta + req.stream + + + req.rawRequest + req.rawResponse + + + + + + HandlerResponse object with header modification capabilities + + resp.header.set(key, value) + resp.header.add(key, value) + + + + + + + Middleware executes in order of definition + +export default new Service("myService", { + middlewares: [ + first, + second, + third + ], +}); + + + + + Specify which endpoints middleware applies to + Use target option instead of runtime filtering for better performance + Defaults to all endpoints if target not specified + + + + + + + Built-in support for ORMs and migration frameworks through named databases and SQL migration files + + Must support standard SQL driver connections + Must generate standard SQL migration files + + + + + + Use SQLDatabase class for named databases and connection strings + +import { SQLDatabase } from "encore.dev/storage/sqldb"; + +const SiteDB = new SQLDatabase("siteDB", { + migrations: "./migrations", +}); + +const connStr = SiteDB.connectionString; + + + + + + + + + Integration guide for using Drizzle ORM with Encore.ts + + + + + Initialize SQLDatabase and configure Drizzle connection + +import { api } from "encore.dev/api"; +import { SQLDatabase } from "encore.dev/storage/sqldb"; +import { drizzle } from "drizzle-orm/node-postgres"; +import { users } from "./schema"; + +const db = new SQLDatabase("test", { + migrations: { + path: "migrations", + source: "drizzle", + }, +}); + +const orm = drizzle(db.connectionString); +await orm.select().from(users); + + + + + Create Drizzle configuration file + +import 'dotenv/config'; +import { defineConfig } from 'drizzle-kit'; + +export default defineConfig({ + out: 'migrations', + schema: 'schema.ts', + dialect: 'postgresql', +}); + + + + + Define database schema using Drizzle's pg-core + +import * as p from "drizzle-orm/pg-core"; + +export const users = p.pgTable("users", { + id: p.serial().primaryKey(), + name: p.text(), + email: p.text().unique(), +}); + + + + + Generate database migrations + drizzle-kit generate + Run in directory containing drizzle.config.ts + + + + Migrations automatically applied during Encore application runtime + Manual migration commands not required + + + + + + + CORS controls which website origins can access your API + Browser requests to resources on different origins (scheme, domain, port) + + + + Specified in encore.app file under global_cors key + + + + + + + + + + + + + + + + + + Allows unauthenticated requests from all origins + Disallows authenticated requests from other origins + All origins allowed in local development + + + + + + Encore automatically configures headers through static analysis + Request or response types containing header fields + + + + Additional headers can be configured via allow_headers and expose_headers + Custom headers in raw endpoints not detected by static analysis + + + + + + +Built-in structured logging combining free-form messages with type-safe key-value pairs + + + + import log from "encore.dev/log"; + + + + Critical issues + Warning conditions + General information + Debugging information + Detailed tracing + + + + + Direct logging with message and optional structured data + +log.info("log message", {is_subscriber: true}) +log.error(err, "something went terribly wrong!") + + + + + Group logs with shared key-value pairs + +const logger = log.with({is_subscriber: true}) +logger.info("user logged in", {login_method: "oauth"}) // includes is_subscriber=true + + + + + + + + + + +https://github.com/encoredev/examples/tree/main/ts/hello-world + + + +https://github.com/encoredev/examples/tree/main/ts/url-shortener + + + +https://github.com/encoredev/examples/tree/main/ts/uptime + + + + + + Use a single root-level package.json file (monorepo approach) for Encore.ts projects including frontend dependencies + + Separate package.json files in sub-packages + + Encore.ts application must use one package with a single package.json file + Other separate packages must be pre-transpiled to JavaScript + + + + +
+ + + + +encore run [--debug] [--watch=true] [flags] +Runs your application + + + + + +encore app clone [app-id] [directory] +Clone an Encore app to your computer + + + +encore app create [name] +Create a new Encore app + + + +encore app init [name] +Create new app from existing repository + + + +encore app link [app-id] +Link app with server + + + + + +encore auth login +Log in to Encore + + + +encore auth logout +Logs out current user + + + +encore auth signup +Create new account + + + +encore auth whoami +Show current user + + + + + +encore daemon +Restart daemon for unexpected behavior + + + +encore daemon env +Output environment information + + + + + +encore db shell database-name [--env=name] +Connect via psql shell +--write, --admin, --superuser flags available + + + +encore db conn-uri database-name [--env=name] +Output connection string + + + +encore db proxy [--env=name] +Set up local database connection proxy + + + +encore db reset [service-names...] +Reset specified service databases + + + + + +encore gen client [app-id] [--env=name] [--lang=lang] +Generate API client + +- go: Go client with net/http +- typescript: TypeScript with Fetch API +- javascript: JavaScript with Fetch API +- openapi: OpenAPI spec + + + + + +encore logs [--env=prod] [--json] +Stream application logs + + + + +encore k8s configure --env=ENV_NAME +Update kubectl config for environment + + + + + +encore secret set --type types secret-name +Set secret value +production, development, preview, local + + + +encore secret list [keys...] +List secrets + + + +encore secret archive id +Archive secret value + + + +encore secret unarchive id +Unarchive secret value + + + + + +encore version +Report current version + + + +encore version update +Check and apply updates + + + + + +encore vpn start +Set up secure connection to private environments + + + +encore vpn status +Check VPN connection status + + + +encore vpn stop +Stop VPN connection + + + + + +encore build docker +Build portable Docker image + +- --base string: Define base image +- --push: Push to remote repository + + + + diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 450716a..e2a592d 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -125,7 +125,4 @@ git push origin main - [Encore Documentation](https://encore.dev/docs) - [Deployment Guide](https://encore.dev/docs/platform/deploy/deploying) - [GitHub Integration](https://encore.dev/docs/platform/integrations/github) -- [Encore Cloud Dashboard](https://app.encore.dev) - - - +- [Encore Cloud Dashboard](https://app.encore.dev) \ No newline at end of file diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..bed4522 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,4 @@ +encore.gen.go +encore.gen.cue +/.encore +/encore.gen diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..32115f2 --- /dev/null +++ b/bun.lock @@ -0,0 +1,710 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "leap-app", + }, + "backend": { + "name": "backend", + "version": "1.0.0", + "dependencies": { + "encore.dev": "^1.50.6", + }, + "devDependencies": { + "typescript": "^^5.8.3", + "vitest": "^^3.0.9", + }, + }, + "frontend": { + "name": "frontend", + "version": "1.0.0", + "dependencies": { + "@radix-ui/react-label": "^^2.1.2", + "@radix-ui/react-slot": "^^1.2.3", + "@radix-ui/react-toast": "^^1.2.14", + "@tailwindcss/vite": "^^4.1.11", + "class-variance-authority": "^^0.7.1", + "clsx": "^^2.1.1", + "encore.dev": "^1.50.6", + "lucide-react": "^^0.484.0", + "react": "^^19.1.0", + "react-dom": "^^19.1.0", + "react-markdown": "^^10.1.0", + "remark-gfm": "^^4.0.1", + "tailwind-merge": "^^3.2.0", + "tailwindcss": "^^4.1.11", + }, + "devDependencies": { + "@tailwindcss/oxide": "^^4.1.11", + "@tailwindcss/vite": "^^4.1.11", + "@types/react": "^^19.2.2", + "@types/react-dom": "^^19.1.6", + "@vitejs/plugin-react": "^^4.6.0", + "lightningcss": "^^1.29.2", + "tw-animate-css": "^^1.3.4", + "typescript": "^^5.8.3", + "vite": "^^6.2.5", + }, + "optionalDependencies": { + "rollup": "^^4.44.1", + }, + }, + }, + "packages": { + "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], + + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], + + "@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="], + + "@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="], + + "@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="], + + "@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], + + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], + + "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], + + "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], + + "@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], + + "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.11", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.11", "", { "os": "android", "cpu": "arm" }, "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.11", "", { "os": "android", "cpu": "arm64" }, "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.11", "", { "os": "android", "cpu": "x64" }, "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.11", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.11", "", { "os": "linux", "cpu": "arm" }, "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.11", "", { "os": "linux", "cpu": "ia32" }, "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.11", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.11", "", { "os": "linux", "cpu": "s390x" }, "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.11", "", { "os": "linux", "cpu": "x64" }, "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.11", "", { "os": "none", "cpu": "arm64" }, "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.11", "", { "os": "none", "cpu": "x64" }, "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.11", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.11", "", { "os": "openbsd", "cpu": "x64" }, "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.11", "", { "os": "none", "cpu": "arm64" }, "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.11", "", { "os": "sunos", "cpu": "x64" }, "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.11", "", { "os": "win32", "cpu": "ia32" }, "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.11", "", { "os": "win32", "cpu": "x64" }, "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA=="], + + "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="], + + "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], + + "@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], + + "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ=="], + + "@radix-ui/react-label": ["@radix-ui/react-label@2.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zo1uGMTaNlHehDyFQcDZXRJhUPDuukcnHz0/jnrup0JA6qL+AFpAnty+7VKa9esuU5xTblAZzTGYJKSKaBxBhw=="], + + "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="], + + "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], + + "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.2", "", { "dependencies": { "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w=="], + + "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-toast": ["@radix-ui/react-toast@1.2.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-nAP5FBxBJGQ/YfUB+r+O6USFVkWq3gAInkxyEnmvEV5jtSbfDhfa4hwX8CraCnbjMLsE7XSf/K75l9xXY7joWg=="], + + "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], + + "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="], + + "@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="], + + "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="], + + "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], + + "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.19", "", {}, "sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.44.1", "", { "os": "android", "cpu": "arm" }, "sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.44.1", "", { "os": "android", "cpu": "arm64" }, "sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.44.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.44.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.44.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.44.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.44.1", "", { "os": "linux", "cpu": "arm" }, "sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.44.1", "", { "os": "linux", "cpu": "arm" }, "sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.44.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.44.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g=="], + + "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.44.1", "", { "os": "linux", "cpu": "none" }, "sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew=="], + + "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.44.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.44.1", "", { "os": "linux", "cpu": "none" }, "sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.44.1", "", { "os": "linux", "cpu": "none" }, "sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.44.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.44.1", "", { "os": "linux", "cpu": "x64" }, "sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.44.1", "", { "os": "linux", "cpu": "x64" }, "sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.44.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.44.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.44.1", "", { "os": "win32", "cpu": "x64" }, "sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.1.11", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.11" } }, "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.11", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.11", "@tailwindcss/oxide-darwin-arm64": "4.1.11", "@tailwindcss/oxide-darwin-x64": "4.1.11", "@tailwindcss/oxide-freebsd-x64": "4.1.11", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", "@tailwindcss/oxide-linux-x64-musl": "4.1.11", "@tailwindcss/oxide-wasm32-wasi": "4.1.11", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" } }, "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.11", "", { "os": "android", "cpu": "arm64" }, "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11", "", { "os": "linux", "cpu": "arm" }, "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.11", "", { "os": "linux", "cpu": "x64" }, "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.11", "", { "os": "linux", "cpu": "x64" }, "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.11", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@emnapi/wasi-threads": "^1.0.2", "@napi-rs/wasm-runtime": "^0.2.11", "@tybys/wasm-util": "^0.9.0", "tslib": "^2.8.0" }, "cpu": "none" }, "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.11", "", { "os": "win32", "cpu": "x64" }, "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg=="], + + "@tailwindcss/vite": ["@tailwindcss/vite@4.1.11", "", { "dependencies": { "@tailwindcss/node": "4.1.11", "@tailwindcss/oxide": "4.1.11", "tailwindcss": "4.1.11" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw=="], + + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], + + "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], + + "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], + + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + + "@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="], + + "@types/react-dom": ["@types/react-dom@19.1.6", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw=="], + + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], + + "@vitejs/plugin-react": ["@vitejs/plugin-react@4.6.0", "", { "dependencies": { "@babel/core": "^7.27.4", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.19", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" } }, "sha512-5Kgff+m8e2PB+9j51eGHEpn5kUzRKH2Ry0qGoe8ItJg7pqnkPrYPkDQZGgGmTa0EGarHrkjLvOdU3b1fzI8otQ=="], + + "@vitest/expect": ["@vitest/expect@3.0.9", "", { "dependencies": { "@vitest/spy": "3.0.9", "@vitest/utils": "3.0.9", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-5eCqRItYgIML7NNVgJj6TVCmdzE7ZVgJhruW0ziSQV4V7PvLkDL1bBkBdcTs/VuIz0IxPb5da1IDSqc1TR9eig=="], + + "@vitest/mocker": ["@vitest/mocker@3.0.9", "", { "dependencies": { "@vitest/spy": "3.0.9", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-ryERPIBOnvevAkTq+L1lD+DTFBRcjueL9lOUfXsLfwP92h4e+Heb+PjiqS3/OURWPtywfafK0kj++yDFjWUmrA=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], + + "@vitest/runner": ["@vitest/runner@3.0.9", "", { "dependencies": { "@vitest/utils": "3.0.9", "pathe": "^2.0.3" } }, "sha512-NX9oUXgF9HPfJSwl8tUZCMP1oGx2+Sf+ru6d05QjzQz4OwWg0psEzwY6VexP2tTHWdOkhKHUIZH+fS6nA7jfOw=="], + + "@vitest/snapshot": ["@vitest/snapshot@3.0.9", "", { "dependencies": { "@vitest/pretty-format": "3.0.9", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-AiLUiuZ0FuA+/8i19mTYd+re5jqjEc2jZbgJ2up0VY0Ddyyxg/uUtBDpIFAy4uzKaQxOW8gMgBdAJJ2ydhu39A=="], + + "@vitest/spy": ["@vitest/spy@3.0.9", "", { "dependencies": { "tinyspy": "^3.0.2" } }, "sha512-/CcK2UDl0aQ2wtkp3YVWldrpLRNCfVcIOFGlVGKO4R5eajsH393Z1yiXLVQ7vWsj26JOEjeZI0x5sm5P4OGUNQ=="], + + "@vitest/utils": ["@vitest/utils@3.0.9", "", { "dependencies": { "@vitest/pretty-format": "3.0.9", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" } }, "sha512-ilHM5fHhZ89MCp5aAaM9uhfl1c2JdxVxl3McqsdVyVNN6JffnEen8UMCdRTzOhGXNQGo5GNL9QugHrz727Wnng=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "backend": ["backend@workspace:backend"], + + "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.8.20", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ=="], + + "browserslist": ["browserslist@4.27.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", "electron-to-chromium": "^1.5.238", "node-releases": "^2.0.26", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw=="], + + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001751", "", {}, "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw=="], + + "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + + "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + + "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], + + "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], + + "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + + "character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="], + + "check-error": ["check-error@2.1.1", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="], + + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + + "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], + + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="], + + "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.240", "", {}, "sha512-OBwbZjWgrCOH+g6uJsA2/7Twpas2OlepS9uvByJjR2datRDuKGYeD+nP8lBBks2qnB7bGJNHDUx7c/YLaT3QMQ=="], + + "encore.dev": ["encore.dev@1.50.6", "", {}, "sha512-pTsG3L9wfJJxmBOSZnLDtAL3ezdZn9tOcrJOq1de+VFYE1C2oCM/iHjv3vjXhM8ELml2zdvjnUlL47Et2kZKaw=="], + + "enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], + + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + + "esbuild": ["esbuild@0.25.11", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.11", "@esbuild/android-arm": "0.25.11", "@esbuild/android-arm64": "0.25.11", "@esbuild/android-x64": "0.25.11", "@esbuild/darwin-arm64": "0.25.11", "@esbuild/darwin-x64": "0.25.11", "@esbuild/freebsd-arm64": "0.25.11", "@esbuild/freebsd-x64": "0.25.11", "@esbuild/linux-arm": "0.25.11", "@esbuild/linux-arm64": "0.25.11", "@esbuild/linux-ia32": "0.25.11", "@esbuild/linux-loong64": "0.25.11", "@esbuild/linux-mips64el": "0.25.11", "@esbuild/linux-ppc64": "0.25.11", "@esbuild/linux-riscv64": "0.25.11", "@esbuild/linux-s390x": "0.25.11", "@esbuild/linux-x64": "0.25.11", "@esbuild/netbsd-arm64": "0.25.11", "@esbuild/netbsd-x64": "0.25.11", "@esbuild/openbsd-arm64": "0.25.11", "@esbuild/openbsd-x64": "0.25.11", "@esbuild/openharmony-arm64": "0.25.11", "@esbuild/sunos-x64": "0.25.11", "@esbuild/win32-arm64": "0.25.11", "@esbuild/win32-ia32": "0.25.11", "@esbuild/win32-x64": "0.25.11" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "expect-type": ["expect-type@1.2.2", "", {}, "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA=="], + + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "frontend": ["frontend@workspace:frontend"], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="], + + "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], + + "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="], + + "inline-style-parser": ["inline-style-parser@0.2.4", "", {}, "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="], + + "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], + + "is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="], + + "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], + + "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "lightningcss": ["lightningcss@1.29.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.29.2", "lightningcss-darwin-x64": "1.29.2", "lightningcss-freebsd-x64": "1.29.2", "lightningcss-linux-arm-gnueabihf": "1.29.2", "lightningcss-linux-arm64-gnu": "1.29.2", "lightningcss-linux-arm64-musl": "1.29.2", "lightningcss-linux-x64-gnu": "1.29.2", "lightningcss-linux-x64-musl": "1.29.2", "lightningcss-win32-arm64-msvc": "1.29.2", "lightningcss-win32-x64-msvc": "1.29.2" } }, "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.29.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.29.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.29.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.29.2", "", { "os": "linux", "cpu": "arm" }, "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.29.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.29.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.29.2", "", { "os": "linux", "cpu": "x64" }, "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.29.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.29.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.29.2", "", { "os": "win32", "cpu": "x64" }, "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA=="], + + "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], + + "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "lucide-react": ["lucide-react@0.484.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-oZy8coK9kZzvqhSgfbGkPtTgyjpBvs3ukLgDPv14dSOZtBtboryWF5o8i3qen7QbGg7JhiJBz5mK1p8YoMZTLQ=="], + + "magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="], + + "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], + + "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], + + "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + + "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], + + "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="], + + "mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="], + + "mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="], + + "mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="], + + "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="], + + "mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="], + + "mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.2.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q=="], + + "mdast-util-mdxjs-esm": ["mdast-util-mdxjs-esm@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg=="], + + "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="], + + "mdast-util-to-hast": ["mdast-util-to-hast@13.2.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA=="], + + "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="], + + "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], + + "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], + + "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], + + "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="], + + "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="], + + "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="], + + "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="], + + "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="], + + "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="], + + "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], + + "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], + + "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + + "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + + "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "node-releases": ["node-releases@2.0.26", "", {}, "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA=="], + + "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], + + "react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="], + + "react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="], + + "react-markdown": ["react-markdown@10.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ=="], + + "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], + + "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], + + "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], + + "remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="], + + "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], + + "rollup": ["rollup@4.44.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.44.1", "@rollup/rollup-android-arm64": "4.44.1", "@rollup/rollup-darwin-arm64": "4.44.1", "@rollup/rollup-darwin-x64": "4.44.1", "@rollup/rollup-freebsd-arm64": "4.44.1", "@rollup/rollup-freebsd-x64": "4.44.1", "@rollup/rollup-linux-arm-gnueabihf": "4.44.1", "@rollup/rollup-linux-arm-musleabihf": "4.44.1", "@rollup/rollup-linux-arm64-gnu": "4.44.1", "@rollup/rollup-linux-arm64-musl": "4.44.1", "@rollup/rollup-linux-loongarch64-gnu": "4.44.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.44.1", "@rollup/rollup-linux-riscv64-gnu": "4.44.1", "@rollup/rollup-linux-riscv64-musl": "4.44.1", "@rollup/rollup-linux-s390x-gnu": "4.44.1", "@rollup/rollup-linux-x64-gnu": "4.44.1", "@rollup/rollup-linux-x64-musl": "4.44.1", "@rollup/rollup-win32-arm64-msvc": "4.44.1", "@rollup/rollup-win32-ia32-msvc": "4.44.1", "@rollup/rollup-win32-x64-msvc": "4.44.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg=="], + + "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], + + "style-to-js": ["style-to-js@1.1.18", "", { "dependencies": { "style-to-object": "1.0.11" } }, "sha512-JFPn62D4kJaPTnhFUI244MThx+FEGbi+9dw1b9yBBQ+1CZpV7QAT8kUtJ7b7EUNdHajjF/0x8fT+16oLJoojLg=="], + + "style-to-object": ["style-to-object@1.0.11", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-5A560JmXr7wDyGLK12Nq/EYS38VkGlglVzkis1JEdbGWSnbQIEhZzTJhzURXN5/8WwwFCs/f/VVcmkTppbXLow=="], + + "tailwind-merge": ["tailwind-merge@3.2.0", "", {}, "sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA=="], + + "tailwindcss": ["tailwindcss@4.1.11", "", {}, "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA=="], + + "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + + "tar": ["tar@7.5.1", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g=="], + + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + + "tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="], + + "tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + + "tinyspy": ["tinyspy@3.0.2", "", {}, "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q=="], + + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + + "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], + + "tw-animate-css": ["tw-animate-css@1.3.4", "", {}, "sha512-dd1Ht6/YQHcNbq0znIT6dG8uhO7Ce+VIIhZUhjsryXsMPJQz3bZg7Q2eNzLwipb25bRZslGb2myio5mScd1TFg=="], + + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + + "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], + + "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], + + "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + + "unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + + "update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="], + + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], + + "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + + "vite": ["vite@6.2.5", "", { "dependencies": { "esbuild": "^0.25.0", "postcss": "^8.5.3", "rollup": "^4.30.1" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA=="], + + "vite-node": ["vite-node@3.0.9", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.0", "es-module-lexer": "^1.6.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-w3Gdx7jDcuT9cNn9jExXgOyKmf5UOTb6WMHz8LGAm54eS1Elf5OuBhCxl6zJxGhEeIkgsE1WbHuoL0mj/UXqXg=="], + + "vitest": ["vitest@3.0.9", "", { "dependencies": { "@vitest/expect": "3.0.9", "@vitest/mocker": "3.0.9", "@vitest/pretty-format": "^3.0.9", "@vitest/runner": "3.0.9", "@vitest/snapshot": "3.0.9", "@vitest/spy": "3.0.9", "@vitest/utils": "3.0.9", "chai": "^5.2.0", "debug": "^4.4.0", "expect-type": "^1.1.0", "magic-string": "^0.30.17", "pathe": "^2.0.3", "std-env": "^3.8.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", "vite-node": "3.0.9", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.0.9", "@vitest/ui": "3.0.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-BbcFDqNyBlfSpATmTtXOAOj71RNKDDvjBM/uPfnxxVGrG+FSH2RQIwgeEngTaTkuU/h0ScFvf+tRcKfYXzBybQ=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + + "@radix-ui/react-collection/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-portal/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ=="], + + "@radix-ui/react-toast/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-visually-hidden/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@tailwindcss/node/lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.6.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.6.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" }, "bundled": true }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.9.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@vitest/snapshot/@vitest/pretty-format": ["@vitest/pretty-format@3.0.9", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-OW9F8t2J3AwFEwENg3yMyKWweF7oRJlMyHOMIhO5F3n0+cgQAJZBjNgrF8dLwFTEXl5jUqBLXd9QyyKv8zEcmA=="], + + "@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.0.9", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-OW9F8t2J3AwFEwENg3yMyKWweF7oRJlMyHOMIhO5F3n0+cgQAJZBjNgrF8dLwFTEXl5jUqBLXd9QyyKv8zEcmA=="], + + "lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + + "@radix-ui/react-primitive/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="], + + "@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="], + + "@tailwindcss/node/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA=="], + + "@tailwindcss/node/lightningcss/lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig=="], + + "@tailwindcss/node/lightningcss/lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.1", "", { "os": "linux", "cpu": "arm" }, "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q=="], + + "@tailwindcss/node/lightningcss/lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw=="], + + "@tailwindcss/node/lightningcss/lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ=="], + + "@tailwindcss/node/lightningcss/lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw=="], + + "@tailwindcss/node/lightningcss/lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ=="], + + "@tailwindcss/node/lightningcss/lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA=="], + + "@tailwindcss/node/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + } +} diff --git a/claude.md b/claude.md new file mode 100644 index 0000000..1c13ca5 --- /dev/null +++ b/claude.md @@ -0,0 +1,6 @@ +# Claude Rules + +- **Primary rules**: [.cursor/rules/instructions.txt](./.cursor/rules/instructions.txt) +- **Split index (if present)**: [.cursor/rules/claude.md](./.cursor/rules/claude.md) + +This document serves as the root entry point to the Cursor rules in this repository. diff --git a/frontend/client.ts b/frontend/client.ts index f8df62c..f1053e1 100644 --- a/frontend/client.ts +++ b/frontend/client.ts @@ -1,4 +1,4 @@ -// Code generated by the Encore 1.50.6 client generator. DO NOT EDIT. +// Code generated by the Encore v1.50.6 client generator. DO NOT EDIT. // Disable eslint, jshint, and jslint for this file. /* eslint-disable */ @@ -17,7 +17,7 @@ export const Local: BaseURL = "http://localhost:4000" * Environment returns a BaseURL for calling the cloud environment with the given name. */ export function Environment(name: string): BaseURL { - return `https://${name}-.encr.app` + return `https://${name}-steering-wheel-documentation-65b2.encr.app` } /** @@ -30,7 +30,7 @@ export function PreviewEnv(pr: number | string): BaseURL { const BROWSER = typeof globalThis === "object" && ("window" in globalThis); /** - * Client is an API client for the Encore application. + * Client is an API client for the steering-wheel-documentation-65b2 Encore application. */ export class Client { public readonly run: run.ServiceClient @@ -440,7 +440,7 @@ class BaseClient { // Add User-Agent header if the script is running in the server // because browsers do not allow setting User-Agent headers to requests if (!BROWSER) { - this.headers["User-Agent"] = "-Generated-TS-Client (Encore/1.50.6)"; + this.headers["User-Agent"] = "steering-wheel-documentation-65b2-Generated-TS-Client (Encore/v1.50.6)"; } this.requestInit = options.requestInit ?? {}; diff --git a/seed.md b/seed.md index c592b8b..058b31c 100644 --- a/seed.md +++ b/seed.md @@ -1,4 +1,4 @@ -# Steering Wheel - Documentation System Evaluation +# ScreenGraph - Documentation System Evaluation - This is where it all began! **Project**: ScreenGraph SAAS - "A living map of your mobile app for UI/UX and competitive analysis" @@ -18,209 +18,6 @@ --- -## Option 1: Docusaurus 3.9 - -### Overview -Meta's static site generator built on React, widely used for technical documentation. - -### Pros -- ✅ Rich MDX support (Markdown + React components) -- ✅ Built-in versioning system -- ✅ Search functionality out of box -- ✅ Plugin ecosystem (changelog, blog, docs) -- ✅ Mature, well-documented - -### Cons -- ❌ **Over-engineered** for solo dev needs -- ❌ Requires separate build/deployment process -- ❌ Heavy frontend bundle (~300KB+) -- ❌ No native chat interface - would need custom integration -- ❌ Slower iteration cycle (build → deploy) -- ❌ Complex folder structure (docs/, blog/, src/, etc.) -- ❌ Not designed for real-time editing - -### AI-Agent Integration -- Agents can read markdown files directly from `/docs` folder -- Would need custom API to expose docs to chat interface -- Versioning through Git + Docusaurus versioning - -### Effort Estimate: **High** (2-3 days setup + ongoing maintenance) - ---- - -## Option 2: Starlight (Astro Framework) - -### Overview -Astro's documentation theme - modern, fast, content-focused. - -### Pros -- ✅ Excellent performance (partial hydration) -- ✅ Clean MDX support -- ✅ Simple file-based routing -- ✅ Built-in sidebar navigation -- ✅ TypeScript-first -- ✅ Lighter than Docusaurus - -### Cons -- ❌ Still requires build process -- ❌ No native versioning system -- ❌ No chat interface built-in -- ❌ Younger ecosystem than Docusaurus -- ❌ Separate deployment needed -- ❌ Static site - not designed for real-time updates - -### AI-Agent Integration -- Agents read from markdown files in `/src/content/docs` -- Would need custom backend for chat + editing -- Manual versioning via Git - -### Effort Estimate: **Medium** (1-2 days setup + custom features) - ---- - -## Option 3: Notion - -### Overview -All-in-one workspace with docs, wikis, databases, and collaboration features. Popular among startups and solo devs. - -### Pros -- ✅ **Zero setup** - create account and start writing -- ✅ **Rich editor** - blocks, databases, embeds, formatting -- ✅ **Built-in AI** - Notion AI for chat, summarization, editing -- ✅ **Version history** - track changes over time (paid plans) -- ✅ **Templates** - ready-made structures for wikis, docs, project management -- ✅ **Mobile apps** - access from anywhere -- ✅ **Search** - powerful full-text search -- ✅ **Databases** - tables, boards, calendars for tasks/reports -- ✅ **Collaboration** - comments, mentions (if team grows) - -### Cons -- ❌ **Not AI-agent readable** - proprietary format, no direct file access -- ❌ **API limitations** - rate limits, complex setup for programmatic access -- ❌ **Export friction** - markdown export exists but loses structure -- ❌ **Context switching** - separate tool outside codebase -- ❌ **Vendor lock-in** - data trapped in Notion ecosystem -- ❌ **No mandatory reading enforcement** - can't force AI agents to read specific pages -- ❌ **Cost** - free tier limited, paid plans $8-10/month -- ❌ **Performance** - can be slow with large workspaces -- ❌ **Offline limitations** - requires internet connection -- ❌ **No Git integration** - can't version control with code - -### AI-Agent Integration -**Major limitation:** -- Notion API exists but is paginated, slow, and complex -- AI agents like Leap/Cursor can't directly read Notion pages without custom integration -- Would need to build Notion API wrapper → convert to markdown → feed to AI -- No way to enforce "mandatory reading" for AI agents -- Updates require API calls instead of direct file edits - -**Workaround approach:** -1. Export Notion pages to markdown manually -2. Commit to Git repository -3. AI reads exported files -4. Manual sync process between Notion ↔ Git - -This defeats the purpose of real-time collaboration with AI. - -### Effort Estimate: **Low for setup, High for AI integration** (5 min setup, 1-2 days for proper AI integration) - ---- - -## Option 4: Custom Encore.ts + React Solution - -### Overview -Build a lightweight docs app within the ScreenGraph project using Encore.ts backend + React frontend with markdown rendering. - -### Architecture -``` -backend/ - steering/ - encore.service.ts - get_doc.ts # Fetch single doc - list_docs.ts # List all docs - update_doc.ts # Update doc content - chat_with_docs.ts # AI chat endpoint - versions/ - list_versions.ts # Version history - get_version.ts # Get specific version - db/ - index.ts # SQL database for docs + versions - -frontend/ - components/ - steering/ - DocViewer.tsx # Markdown renderer - DocEditor.tsx # Edit mode - DocChat.tsx # Chat interface - DocNav.tsx # Navigation sidebar - pages/ - SteeringWheel.tsx # Main page - -steering-docs/ # Actual markdown files (Git-tracked) - rules.md - facts.md - procedures.md - preferences.md - coding-standards.md - reports/ - 2025-10-23.md - wip.md - tasks.md - .steering-config.json # Mandatory reading list for AI -``` - -### Pros -- ✅ **Perfectly integrated** with your existing project -- ✅ **Real-time editing** through Encore.ts APIs -- ✅ **Native chat interface** using AI API integration -- ✅ **Version control** via database + Git commits -- ✅ **Zero deployment complexity** - same as your app -- ✅ **Minimal overhead** - just markdown files + simple UI -- ✅ **Direct AI access** - agents read from `/steering-docs` -- ✅ **Enforced reading** via `.steering-config.json` -- ✅ **Full control** over features and UX -- ✅ **Single codebase** - no context switching - -### Cons -- ❌ Custom implementation (but simple) -- ❌ No off-the-shelf templates -- ❌ Build your own features (version UI, search, etc.) - -### AI-Agent Integration -**Perfect for AI agents:** -```json -// .steering-config.json -{ - "mandatory_reading": [ - "steering-docs/rules.md", - "steering-docs/coding-standards.md", - "steering-docs/wip.md" - ], - "context_files": [ - "steering-docs/facts.md", - "steering-docs/preferences.md" - ] -} -``` - -AI agents like Leap can: -1. Read `.steering-config.json` to know what to load -2. Parse markdown files directly (native format) -3. Call `/steering/chat_with_docs` API to query docs -4. Update docs via `/steering/update_doc` API - -### Features to Build -1. **Doc Viewer** - Markdown rendering with syntax highlighting -2. **Doc Editor** - Simple textarea or Monaco editor -3. **Chat Interface** - Query docs using OpenAI/Anthropic -4. **Version History** - Database-backed with Git sync -5. **Navigation** - Auto-generated from file structure -6. **Search** - Simple text search across all docs - -### Effort Estimate: **Low** (4-6 hours for MVP, extensible over time) - ---- - ## Recommendation: **Option 4 - Custom Encore.ts Solution** ### Why This Is the Best Approach @@ -270,58 +67,6 @@ As a SAAS product for UI/UX analysis, having internal docs in the same codebase: --- -## Implementation Plan - -### Phase 1: MVP (Today) -1. Create `/steering-docs` folder structure -2. Build basic Encore.ts CRUD endpoints -3. Simple React viewer with markdown rendering -4. `.steering-config.json` for AI agents - -### Phase 2: Enhanced (This Week) -1. Add edit mode -2. Database versioning -3. Auto-commit to Git on changes -4. Navigation sidebar - -### Phase 3: Advanced (As Needed) -1. Chat interface with AI -2. Version history UI -3. Search functionality -4. Diff viewer - ---- - -## Comparison Matrix - -| Feature | Docusaurus | Starlight | Notion | Custom Encore.ts | -|---------|-----------|-----------|--------|------------------| -| **AI-Agent Readable** | ✅ Markdown files | ✅ Markdown files | ❌ Proprietary API | ✅ Direct file access | -| **Setup Time** | 2-3 days | 1-2 days | 5 minutes | 4-6 hours | -| **Real-time Editing** | ❌ Build required | ❌ Build required | ✅ Instant | ✅ Instant | -| **Chat Interface** | ❌ Custom needed | ❌ Custom needed | ✅ Built-in AI | ✅ Custom (easy) | -| **Version Control** | ✅ Git + Built-in | ⚠️ Git only | ⚠️ Paid plans | ✅ DB + Git | -| **Mandatory Reading** | ⚠️ Possible | ⚠️ Possible | ❌ Impossible | ✅ Config-driven | -| **Deployment** | Separate | Separate | Cloud-hosted | Integrated | -| **Cost** | Free | Free | $0-10/month | Free (your infra) | -| **Context Switching** | ❌ Separate site | ❌ Separate site | ❌ External tool | ✅ Same codebase | -| **Customization** | ⚠️ Limited | ⚠️ Limited | ❌ Very limited | ✅ Full control | - ---- - -## Conclusion - -**Notion** is excellent for personal wikis and team collaboration, but **fundamentally incompatible** with the goal of AI-agent readable documentation. The proprietary format and API limitations create too much friction for programmatic access by Leap, Cursor, and Claude. - -**Docusaurus** and **Starlight** are excellent tools for public documentation websites, but they're over-engineered for a solo dev's internal knowledge base that needs AI integration and real-time editing. - -**Custom Encore.ts solution** gives you: -- Exactly what you need, nothing more -- Perfect AI agent integration -- Real-time collaboration with AI -- Zero deployment overhead -- Full control and extensibility - **Decision: Build custom Steering Wheel in Encore.ts** Let's start with the foundation today and iterate based on actual usage patterns. From 82d3190ddd17b6ddfb7304b81507aab57cf70257 Mon Sep 17 00:00:00 2001 From: Niranjan Kurambhatti Date: Thu, 23 Oct 2025 19:51:52 -0500 Subject: [PATCH 2/4] Add GitHub Actions CI workflow - Add CI workflow for pull requests to main branch - Includes PostgreSQL service for database testing - Runs backend tests with Encore CLI - Runs frontend tests with Bun - Builds both backend and frontend - Uses latest Bun version and Encore CLI --- .github/workflows/ci.yml | 61 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a7e90b1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,61 @@ +name: CI + +on: + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:15 + env: + POSTGRES_PASSWORD: postgres + POSTGRES_DB: test + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + steps: + - uses: actions/checkout@v4 + + - uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + + - name: Install Encore CLI + run: | + curl -L https://encore.dev/install.sh | bash + echo "$HOME/.encore/bin" >> $GITHUB_PATH + + - name: Install backend dependencies + working-directory: ./backend + run: bun install + + - name: Run backend tests + working-directory: ./backend + run: encore test + env: + DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test + + - name: Build backend + working-directory: ./backend + run: encore build + + - name: Install frontend dependencies + working-directory: ./frontend + run: bun install + + - name: Run frontend tests + working-directory: ./frontend + run: bun test + + - name: Build frontend + working-directory: ./frontend + run: bun run build From 8092ebbdd3355d5e68e875ddb341d4abd2675b21 Mon Sep 17 00:00:00 2001 From: Niranjan Kurambhatti Date: Fri, 24 Oct 2025 00:15:28 -0500 Subject: [PATCH 3/4] Deduplicate split rule sections\n\n- Trim duplicated XML blocks from each .mdc rule file\n- Add helper script to ensure we only keep the first block\n- Run script to dedupe encore domain knowledge and CLI docs --- .cursor/rules/01-llm-info.mdc | 2 - .cursor/rules/02-corey-info.mdc | 7 - .cursor/rules/03-corey-behavior.mdc | 4 - .cursor/rules/04-nodejs-style-guide.mdc | 5 - .cursor/rules/05-typescript-style-guide.mdc | 5 +- .../rules/06-encore-ts-domain-knowledge.mdc | 1623 +---------------- .cursor/rules/07-encore-cli-reference.mdc | 176 +- .cursor/rules/dedupe_rules.js | 41 + 8 files changed, 44 insertions(+), 1819 deletions(-) create mode 100644 .cursor/rules/dedupe_rules.js diff --git a/.cursor/rules/01-llm-info.mdc b/.cursor/rules/01-llm-info.mdc index 755dfa4..bf3c46f 100644 --- a/.cursor/rules/01-llm-info.mdc +++ b/.cursor/rules/01-llm-info.mdc @@ -4,6 +4,4 @@ alwaysApply: true If the user asks you questions, you should assume you are Corey and act accordingly. - - If the user asks you questions, you should assume you are Corey and act accordingly. \ No newline at end of file diff --git a/.cursor/rules/02-corey-info.mdc b/.cursor/rules/02-corey-info.mdc index 726a481..b268d94 100644 --- a/.cursor/rules/02-corey-info.mdc +++ b/.cursor/rules/02-corey-info.mdc @@ -9,11 +9,4 @@ alwaysApply: true Corey provides concise, clear, and efficient coding solutions while always offering friendly and approachable manners. Unless otherwise specified by the user, Corey defaults to using Encore.ts, a typescript backend framework, for backend development. Corey has knowledge of various programming languages, frameworks, and best practices, with a particular emphasis on distributed systems,Encore.ts, Node.js, TypeScript, React, Next.js, and modern development. - - Corey is a helpful AI coding assistant created by Encore. - Corey acts as the world's most proficient developers would. - Corey is always knowledgeable of the latest best practices and technologies. - Corey provides concise, clear, and efficient coding solutions while always offering friendly and approachable manners. - Unless otherwise specified by the user, Corey defaults to using Encore.ts, a typescript backend framework, for backend development. - Corey has knowledge of various programming languages, frameworks, and best practices, with a particular emphasis on distributed systems,Encore.ts, Node.js, TypeScript, React, Next.js, and modern development. \ No newline at end of file diff --git a/.cursor/rules/03-corey-behavior.mdc b/.cursor/rules/03-corey-behavior.mdc index 8b89b79..5703296 100644 --- a/.cursor/rules/03-corey-behavior.mdc +++ b/.cursor/rules/03-corey-behavior.mdc @@ -6,8 +6,4 @@ alwaysApply: true Corey will always think through the problem and plan the solution before responding. Corey will always aim to work iteratively with the user to achieve the desired outcome. Corey will always optimize the solution for the user's needs and goals. - - Corey will always think through the problem and plan the solution before responding. - Corey will always aim to work iteratively with the user to achieve the desired outcome. - Corey will always optimize the solution for the user's needs and goals. \ No newline at end of file diff --git a/.cursor/rules/04-nodejs-style-guide.mdc b/.cursor/rules/04-nodejs-style-guide.mdc index 11f902c..db68d93 100644 --- a/.cursor/rules/04-nodejs-style-guide.mdc +++ b/.cursor/rules/04-nodejs-style-guide.mdc @@ -7,9 +7,4 @@ alwaysApply: true - Always use ES6+ syntax. - Always use the built-in `fetch` for HTTP requests, rather than libraries like `node-fetch`. - Always use Node.js `import`, never use `require`. - - Corey MUST write valid TypeScript code, which uses state-of-the-art Node.js v20+ features and follows best practices: - - Always use ES6+ syntax. - - Always use the built-in `fetch` for HTTP requests, rather than libraries like `node-fetch`. - - Always use Node.js `import`, never use `require`. \ No newline at end of file diff --git a/.cursor/rules/05-typescript-style-guide.mdc b/.cursor/rules/05-typescript-style-guide.mdc index 8d60bc3..c1dbbcd 100644 --- a/.cursor/rules/05-typescript-style-guide.mdc +++ b/.cursor/rules/05-typescript-style-guide.mdc @@ -5,7 +5,4 @@ alwaysApply: true Use interface or type definitions for complex objects Prefer TypeScript's built-in utility types (e.g., Record, Partial, Pick) over any - - Use interface or type definitions for complex objects - Prefer TypeScript's built-in utility types (e.g., Record, Partial, Pick) over any - \ No newline at end of file + diff --git a/.cursor/rules/06-encore-ts-domain-knowledge.mdc b/.cursor/rules/06-encore-ts-domain-knowledge.mdc index a261dd2..7012806 100644 --- a/.cursor/rules/06-encore-ts-domain-knowledge.mdc +++ b/.cursor/rules/06-encore-ts-domain-knowledge.mdc @@ -1623,1625 +1623,4 @@ https://github.com/encoredev/examples/tree/main/ts/uptime - - - - - -Encore.ts provides type-safe TypeScript API endpoints with built-in request validation -APIs are async functions with TypeScript interfaces defining request/response types -Source code parsing enables automatic request validation against schemas - - - -import { api } from "encore.dev/api"; -export const endpoint = api(options, async handler); - - - - - - - - -import { api } from "encore.dev/api"; -interface PingParams { -name: string; -} -interface PingResponse { -message: string; -} -export const ping = api( -{ method: "POST" }, -async (p: PingParams): Promise => { -return { message: Hello ${p.name}! }; -} -); - - - -api({ ... }, async (params: Params): Promise => {}) - - -api({ ... }, async (): Promise => {}) - - -api({ ... }, async (params: Params): Promise => {}) - - -api({ ... }, async (): Promise => {}) - - - - -Maps field to HTTP header -fieldName: Header<"Header-Name"> - - - Maps field to URL query parameter - fieldName: Query - - - Maps to URL path parameters using :param or *wildcard syntax - path: "/route/:param/*wildcard" - - - - - - -Service-to-service calls use simple function call syntax -Services are imported from ~encore/clients module -Provides compile-time type checking and IDE autocompletion - - - Import target service from ~encore/clients - Call API endpoints as regular async functions - Receive type-safe responses with full IDE support - - -import { hello } from "~encore/clients"; -export const myOtherAPI = api({}, async (): Promise => { -const resp = await hello.ping({ name: "World" }); -console.log(resp.message); // "Hello World!" -}); - - - - - - Use monorepo design for entire backend application - One Encore app enables full application model benefits - Supports both monolith and microservices approaches - Services cannot be nested within other services - - - - - Create encore.service.ts file in service directory - Export service instance using Service class - - - - import { Service } from "encore.dev/service"; - export default new Service("my-service"); - - - - - - Best starting point, especially for new projects - - /my-app - ├── package.json - ├── encore.app - ├── encore.service.ts // service root - ├── api.ts // endpoints - └── db.ts // database - - - - - Distributed system with multiple independent services - - /my-app - ├── encore.app - ├── hello/ - │ ├── migrations/ - │ ├── encore.service.ts - │ ├── hello.ts - │ └── hello_test.ts - └── world/ - ├── encore.service.ts - └── world.ts - - - - - Systems-based organization for large applications - - /my-trello-clone - ├── encore.app - ├── trello/ // system - │ ├── board/ // service - │ └── card/ // service - ├── premium/ // system - │ ├── payment/ // service - │ └── subscription/ // service - └── usr/ // system - ├── org/ // service - └── user/ // service - - - - - - - -Raw endpoints provide lower-level HTTP request access -Uses Node.js/Express.js style request handling -Useful for webhook implementations and custom HTTP handling - - - api.raw(options, handler) - - Configuration object with expose, path, method - Async function receiving (req, resp) parameters - - - -import { api } from "encore.dev/api"; -export const myRawEndpoint = api.raw( -{ expose: true, path: "/raw", method: "GET" }, -async (req, resp) => { -resp.writeHead(200, { "Content-Type": "text/plain" }); -resp.end("Hello, raw world!"); -} -); - - -curl http://localhost:4000/raw -Hello, raw world! - - -Webhook handling -Custom HTTP response formatting -Direct request/response control - - - - - - -{ - "code": "not_found", - "message": "sprocket not found", - "details": null -} - - - - -import { APIError, ErrCode } from "encore.dev/api"; -throw new APIError(ErrCode.NotFound, "sprocket not found"); -// shorthand version: -throw APIError.notFound("sprocket not found"); - - - - - - - ok - 200 OK - - - - canceled - 499 Client Closed Request - - - - unknown - 500 Internal Server Error - - - - invalid_argument - 400 Bad Request - - - - deadline_exceeded - 504 Gateway Timeout - - - - not_found - 404 Not Found - - - - already_exists - 409 Conflict - - - - permission_denied - 403 Forbidden - - - - resource_exhausted - 429 Too Many Requests - - - - failed_precondition - 400 Bad Request - - - - aborted - 409 Conflict - - - - out_of_range - 400 Bad Request - - - - unimplemented - 501 Not Implemented - - - - internal - 500 Internal Server Error - - - - unavailable - 503 Unavailable - - - - data_loss - 500 Internal Server Error - - - - unauthenticated - 401 Unauthorized - - - - - - Use withDetails method on APIError to attach structured details that will be returned to external clients - - - - - - - Encore treats SQL databases as logical resources and natively supports PostgreSQL databases - - - - - Import SQLDatabase from encore.dev/storage/sqldb - Call new SQLDatabase with name and config - Define schema in migrations directory - - - -import { SQLDatabase } from "encore.dev/storage/sqldb"; - -const db = new SQLDatabase("todo", { - migrations: "./migrations", -}); - --- todo/migrations/1_create_table.up.sql -- -CREATE TABLE todo_item ( - id BIGSERIAL PRIMARY KEY, - title TEXT NOT NULL, - done BOOLEAN NOT NULL DEFAULT false -); - - - - - - - Start with number followed by underscore - Must increase sequentially - End with .up.sql - - 001_first_migration.up.sql - 002_second_migration.up.sql - - - - - migrations within service directory - number_name.up.sql - - - - - - - - - These are the supported methods when using the SQLDatabase module with Encore.ts. Do not use any methods not listed here. - - - Returns async iterator for multiple rows - - -const allTodos = await db.query`SELECT * FROM todo_item`; -for await (const todo of allTodos) { - // Process each todo -} - - -const rows = await db.query<{ email: string; source_url: string; scraped_at: Date }>` - SELECT email, source_url, created_at as scraped_at - FROM scraped_emails - ORDER BY created_at DESC -`; - -// Fetch all rows and return them as an array -const emails = []; -for await (const row of rows) { - emails.push(row); -} - -return { emails }; - - - - - - Returns single row or null - -async function getTodoTitle(id: number): string | undefined { - const row = await db.queryRow`SELECT title FROM todo_item WHERE id = ${id}`; - return row?.title; -} - - - - - - - - For inserts and queries not returning rows - -await db.exec` - INSERT INTO todo_item (title, done) - VALUES (${title}, false) -`; - - - - - - - - Opens psql shell to named database - Outputs connection string - Sets up local connection proxy - - - - - - Encore rolls back failed migrations - - schema_migrations
- - Tracks last applied migration - Not used by default - -
-
-
- - - - Export SQLDatabase object from shared module - Use SQLDatabase.named("name") to reference existing database - - - - - pgvector - PostGIS - - Uses encoredotdev/postgres Docker image - - - - - ORM must support standard SQL driver connection - Migration framework must generate standard SQL files - - - Prisma - Drizzle - - - - -
- - - -Encore.ts provides declarative Cron Jobs for periodic and recurring tasks - - - - Import CronJob from encore.dev/cron - Call new CronJob with unique ID and config - Define API endpoint for the job to call - - - -import { CronJob } from "encore.dev/cron"; -import { api } from "encore.dev/api"; - -const _ = new CronJob("welcome-email", { - title: "Send welcome emails", - every: "2h", - endpoint: sendWelcomeEmail, -}) - -export const sendWelcomeEmail = api({}, async () => { - // Send welcome emails... -}); - - - - - - - Runs on periodic basis starting at midnight UTC - Interval must divide 24 hours evenly - - 10m (minutes) - 6h (hours) - - - 7h (not divisible into 24) - - - - - - - Uses Cron expressions for complex scheduling - - 0 4 15 * * - Runs at 4am UTC on the 15th of each month - - - - - - - - - - System for asynchronous event broadcasting between services - - Decouples services for better reliability - Improves system responsiveness - Cloud-agnostic implementation - - - - - - - Must be package level variables - Cannot be created inside functions - Accessible from any service - - - -import { Topic } from "encore.dev/pubsub" - -export interface SignupEvent { - userID: string; -} - -export const signups = new Topic("signups", { - deliveryGuarantee: "at-least-once", -}); - - - - - Publish events using topic.publish method - -const messageID = await signups.publish({userID: id}); - - - - - - - - Topic to subscribe to - Unique name for topic - Handler function - Configuration object - - - -import { Subscription } from "encore.dev/pubsub"; - -const _ = new Subscription(signups, "send-welcome-email", { - handler: async (event) => { - // Send a welcome email using the event. - }, -}); - - - - - Failed events are retried based on retry policy - After max retries, events move to dead-letter queue - - - - - - Default delivery mode with possible message duplication - Handlers must be idempotent - - - - Stronger delivery guarantees with minimized duplicates - - 300 messages per second per topic - 3,000+ messages per second per region - - Does not deduplicate on publish side - - - - - - Key-value pairs for filtering or ordering - -import { Topic, Attribute } from "encore.dev/pubsub"; - -export interface SignupEvent { - userID: string; - source: Attribute; -} - - - - - Messages delivered in order by orderingAttribute - - 300 messages per second per topic - 1 MBps per ordering key - - -import { Topic, Attribute } from "encore.dev/pubsub"; - -export interface CartEvent { - shoppingCartID: Attribute; - event: string; -} - -export const cartEvents = new Topic("cart-events", { - deliveryGuarantee: "at-least-once", - orderingAttribute: "shoppingCartID", -}) - - No effect in local environments - - - - - - -Simple and scalable solution for storing files and unstructured data - - - - - Must be package level variables - Cannot be created inside functions - Accessible from any service - - - -import { Bucket } from "encore.dev/storage/objects"; - -export const profilePictures = new Bucket("profile-pictures", { - versioned: false -}); - - - - - - Upload files to bucket using upload method - -const data = Buffer.from(...); // image data -const attributes = await profilePictures.upload("my-image.jpeg", data, { - contentType: "image/jpeg", -}); - - - - - Download files using download method - -const data = await profilePictures.download("my-image.jpeg"); - - - - - List objects using async iterator - -for await (const entry of profilePictures.list({})) { - // Process entry -} - - - - - Delete objects using remove method - -await profilePictures.remove("my-image.jpeg"); - - - - - Get object information using attrs method - -const attrs = await profilePictures.attrs("my-image.jpeg"); -const exists = await profilePictures.exists("my-image.jpeg"); - - - - - - - - Configure publicly accessible buckets - -export const publicProfilePictures = new Bucket("public-profile-pictures", { - public: true, - versioned: false -}); - - - - - Access public objects using publicUrl method - -const url = publicProfilePictures.publicUrl("my-image.jpeg"); - - - - - - - Thrown when object doesn't exist - Thrown when upload preconditions not met - Base error type for all object storage errors - - - - - System for controlled bucket access permissions - - Download objects - Upload objects - List objects - Get object attributes - Remove objects - Complete read-write access - - - - -import { Uploader } from "encore.dev/storage/objects"; -const ref = profilePictures.ref(); - - Must be called from within a service for proper permission tracking - - - - - - -Built-in secrets manager for secure storage of API keys, passwords, and private keys - - - - Define secrets as top-level variables using secret function - -import { secret } from "encore.dev/config"; - -const githubToken = secret("GitHubAPIToken"); - - - -async function callGitHub() { - const resp = await fetch("https:///api.github.com/user", { - credentials: "include", - headers: { - Authorization: `token ${githubToken()}`, - }, - }); -} - - Secret keys are globally unique across the application - - - - - - - - Open app in Encore Cloud dashboard: https://app.encore.cloud - Navigate to Settings > Secrets - Create and manage secrets for different environments - - - - - encore secret set --type <types> <secret-name> - - production (prod) - development (dev) - preview (pr) - local - - encore secret set --type prod SSHPrivateKey - - - - Override secrets locally using .secrets.local.cue file - -GitHubAPIToken: "my-local-override-token" -SSHPrivateKey: "custom-ssh-private-key" - - - - - - - - One secret value per environment type - Environment-specific values override environment type values - - - - - - - API endpoints that enable data streaming via WebSocket connections - - Client to server streaming - Server to client streaming - Bidirectional streaming - - - - - - Stream data from client to server - -import { api } from "encore.dev/api"; - -interface Message { - data: string; - done: boolean; -} - -export const uploadStream = api.streamIn( - { path: "/upload", expose: true }, - async (stream) => { - for await (const data of stream) { - // Process incoming data - if (data.done) break; - } - } -); - - - - - Stream data from server to client - -export const dataStream = api.streamOut( - { path: "/stream", expose: true }, - async (stream) => { - // Send messages to client - await stream.send({ data: "message" }); - await stream.close(); - } -); - - - - - Bidirectional streaming - -export const chatStream = api.streamInOut( - { path: "/chat", expose: true }, - async (stream) => { - for await (const msg of stream) { - await stream.send(/* response */); - } - } -); - - - - - - - Initial HTTP request for connection setup - - Path parameters - Query parameters - Headers - Authentication data - - - - - -const stream = client.serviceName.endpointName(); -await stream.send({ /* message */ }); -for await (const msg of stream) { - // Handle incoming messages -} - - - - - Internal streaming between services using ~encore/clients import - -import { service } from "~encore/clients"; -const stream = await service.streamEndpoint(); - - - - - - - - Built-in request validation using TypeScript types for both runtime and compile-time type safety - -import { Header, Query, api } from "encore.dev/api"; - -interface Request { - limit?: Query; // Optional query parameter - myHeader: Header<"X-My-Header">; // Required header - type: "sprocket" | "widget"; // Required enum in body -} - -export const myEndpoint = api( - { expose: true, method: "POST", path: "/api" }, - async ({ limit, myHeader, type }) => { - // Implementation - } -); - - - - - - - name: string; - - - age: number; - - - isActive: boolean; - - - -strings: string[]; -numbers: number[]; -objects: { name: string }[]; -mixed: (string | number)[]; - - - - type: "BLOG_POST" | "COMMENT"; - - - - - - fieldName?: type; - name?: string; - - - fieldName: type | null; - name: string | null; - - - - - - - - Validate number ranges - count: number & (Min<3> & Max<1000>); - - - Validate string/array lengths - username: string & (MinLen<5> & MaxLen<20>); - - - Validate string formats - contact: string & (IsURL | IsEmail); - - - - - - - Default for methods with request bodies - JSON request body - - - - URL query parameters - Use Query type or default for GET/HEAD/DELETE - - - - HTTP headers - Use Header<"Name-Of-Header"> type - - - - URL path parameters - path: "/user/:id", param: { id: string } - - - - - - 400 Bad Request - -{ - "code": "invalid_argument", - "message": "unable to decode request body", - "internal_message": "Error details" -} - - - - - - - - Encore.ts's built-in support for serving static assets (images, HTML, CSS, JavaScript) - Serving static websites or pre-compiled single-page applications (SPAs) - - - - - Serve static files using api.static function - -import { api } from "encore.dev/api"; -export const assets = api.static( - { expose: true, path: "/frontend/*path", dir: "./assets" }, -); - - - Serves files from ./assets under /frontend path prefix - Automatically serves index.html files at directory roots - - - - - Serve files at domain root using fallback routes - -export const assets = api.static( - { expose: true, path: "/!path", dir: "./assets" }, -); - - Uses !path syntax instead of *path to avoid conflicts - - - - Configure custom 404 response - -export const assets = api.static( - { - expose: true, - path: "/!path", - dir: "./assets", - notFound: "./not_found.html" - }, -); - - - - - - - - -Encore.ts has GraphQL support through raw endpoints with automatic tracing - - - - Create raw endpoint for client requests - Pass request to GraphQL library - Handle queries and mutations - Return GraphQL response - - - - -import { HeaderMap } from "@apollo/server"; -import { api } from "encore.dev/api"; -const { ApolloServer, gql } = require("apollo-server"); -import { json } from "node:stream/consumers"; - -const server = new ApolloServer({ typeDefs, resolvers }); -await server.start(); - -export const graphqlAPI = api.raw( - { expose: true, path: "/graphql", method: "*" }, - async (req, res) => { - server.assertStarted("/graphql"); - - const headers = new HeaderMap(); - for (const [key, value] of Object.entries(req.headers)) { - if (value !== undefined) { - headers.set(key, Array.isArray(value) ? value.join(", ") : value); - } - } - - const httpGraphQLResponse = await server.executeHTTPGraphQLRequest({ - httpGraphQLRequest: { - headers, - method: req.method!.toUpperCase(), - body: await json(req), - search: new URLSearchParams(req.url ?? "").toString(), - }, - context: async () => ({ req, res }), - }); - - // Set response headers and status - for (const [key, value] of httpGraphQLResponse.headers) { - res.setHeader(key, value); - } - res.statusCode = httpGraphQLResponse.status || 200; - - // Write response - if (httpGraphQLResponse.body.kind === "complete") { - res.end(httpGraphQLResponse.body.string); - return; - } - - for await (const chunk of httpGraphQLResponse.body.asyncIterator) { - res.write(chunk); - } - res.end(); - } -); - - - - - - - - -type Query { - books: [Book] -} - -type Book { - title: String! - author: String! -} - - - -import { book } from "~encore/clients"; -import { QueryResolvers } from "../__generated__/resolvers-types"; - -const queries: QueryResolvers = { - books: async () => { - const { books } = await book.list(); - return books; - }, -}; - - - -import { api } from "encore.dev/api"; -import { Book } from "../__generated__/resolvers-types"; - -export const list = api( - { expose: true, method: "GET", path: "/books" }, - async (): Promise<{ books: Book[] }> => { - return { books: db }; - } -); - - - - - - - - Authentication system for identifying API callers in both consumer and B2B applications - Set auth: true in API endpoint options - - - - - Required for APIs with auth: true - -import { Header, Gateway } from "encore.dev/api"; -import { authHandler } from "encore.dev/auth"; - -interface AuthParams { - authorization: Header<"Authorization">; -} - -interface AuthData { - userID: string; -} - -export const auth = authHandler( - async (params) => { - // Authenticate user based on params - return {userID: "my-user-id"}; - } -) - -export const gateway = new Gateway({ - authHandler: auth, -}) - - - - - Reject authentication by throwing exception - -throw APIError.unauthenticated("bad credentials"); - - - - - - - - Any request containing auth parameters - Regardless of endpoint authentication requirements - - - Returns AuthData - request authenticated - Throws Unauthenticated - treated as no auth - Throws other error - request aborted - - - - - - If endpoint requires auth and request not authenticated - reject - If authenticated, auth data passed to endpoint regardless of requirements - - - - - - - Import getAuthData from ~encore/auth - Type-safe resolution of auth data - - - - Automatic propagation in internal API calls - Calls to auth-required endpoints fail if original request lacks auth - - - - - - - Access environment and application information through metadata API - Available in encore.dev package - - - - appMeta() - - Application name - Public API access URL - Current running environment - Version control revision information - Deployment ID and timestamp - - - - - currentRequest() - - - -interface APICallMeta { - type: "api-call"; - api: APIDesc; - method: Method; - path: string; - pathAndQuery: string; - pathParams: Record; - headers: Record; - parsedPayload?: Record; -} - - - - - -interface PubSubMessageMeta { - type: "pubsub-message"; - service: string; - topic: string; - subscription: string; - messageId: string; - deliveryAttempt: number; - parsedPayload?: Record; -} - - - - Returns undefined if called during service initialization - - - - - Implement different behavior based on cloud provider - -import { appMeta } from "encore.dev"; - -async function audit(userID: string, event: Record) { - const cloud = appMeta().environment.cloud; - switch (cloud) { - case "aws": return writeIntoRedshift(userID, event); - case "gcp": return writeIntoBigQuery(userID, event); - case "local": return writeIntoFile(userID, event); - default: throw new Error(`unknown cloud: ${cloud}`); - } -} - - - - - Modify behavior based on environment type - -switch (appMeta().environment.type) { - case "test": - case "development": - await markEmailVerified(userID); - break; - default: - await sendVerificationEmail(userID); - break; -} - - - - - - - -Reusable code running before/after API requests across multiple endpoints - - - - Create middleware using middleware helper from encore.dev/api - -import { middleware } from "encore.dev/api"; - -export default new Service("myService", { - middlewares: [ - middleware({ target: { auth: true } }, async (req, next) => { - // Pre-handler logic - const resp = await next(req); - // Post-handler logic - return resp - }) - ] -}); - - - - - - - req.requestMeta - - - req.requestMeta - req.stream - - - req.rawRequest - req.rawResponse - - - - - - HandlerResponse object with header modification capabilities - - resp.header.set(key, value) - resp.header.add(key, value) - - - - - - - Middleware executes in order of definition - -export default new Service("myService", { - middlewares: [ - first, - second, - third - ], -}); - - - - - Specify which endpoints middleware applies to - Use target option instead of runtime filtering for better performance - Defaults to all endpoints if target not specified - - - - - - - Built-in support for ORMs and migration frameworks through named databases and SQL migration files - - Must support standard SQL driver connections - Must generate standard SQL migration files - - - - - - Use SQLDatabase class for named databases and connection strings - -import { SQLDatabase } from "encore.dev/storage/sqldb"; - -const SiteDB = new SQLDatabase("siteDB", { - migrations: "./migrations", -}); - -const connStr = SiteDB.connectionString; - - - - - - - - - Integration guide for using Drizzle ORM with Encore.ts - - - - - Initialize SQLDatabase and configure Drizzle connection - -import { api } from "encore.dev/api"; -import { SQLDatabase } from "encore.dev/storage/sqldb"; -import { drizzle } from "drizzle-orm/node-postgres"; -import { users } from "./schema"; - -const db = new SQLDatabase("test", { - migrations: { - path: "migrations", - source: "drizzle", - }, -}); - -const orm = drizzle(db.connectionString); -await orm.select().from(users); - - - - - Create Drizzle configuration file - -import 'dotenv/config'; -import { defineConfig } from 'drizzle-kit'; - -export default defineConfig({ - out: 'migrations', - schema: 'schema.ts', - dialect: 'postgresql', -}); - - - - - Define database schema using Drizzle's pg-core - -import * as p from "drizzle-orm/pg-core"; - -export const users = p.pgTable("users", { - id: p.serial().primaryKey(), - name: p.text(), - email: p.text().unique(), -}); - - - - - Generate database migrations - drizzle-kit generate - Run in directory containing drizzle.config.ts - - - - Migrations automatically applied during Encore application runtime - Manual migration commands not required - - - - - - - CORS controls which website origins can access your API - Browser requests to resources on different origins (scheme, domain, port) - - - - Specified in encore.app file under global_cors key - - - - - - - - - - - - - - - - - - Allows unauthenticated requests from all origins - Disallows authenticated requests from other origins - All origins allowed in local development - - - - - - Encore automatically configures headers through static analysis - Request or response types containing header fields - - - - Additional headers can be configured via allow_headers and expose_headers - Custom headers in raw endpoints not detected by static analysis - - - - - - -Built-in structured logging combining free-form messages with type-safe key-value pairs - - - - import log from "encore.dev/log"; - - - - Critical issues - Warning conditions - General information - Debugging information - Detailed tracing - - - - - Direct logging with message and optional structured data - -log.info("log message", {is_subscriber: true}) -log.error(err, "something went terribly wrong!") - - - - - Group logs with shared key-value pairs - -const logger = log.with({is_subscriber: true}) -logger.info("user logged in", {login_method: "oauth"}) // includes is_subscriber=true - - - - - - - - - - -https://github.com/encoredev/examples/tree/main/ts/hello-world - - - -https://github.com/encoredev/examples/tree/main/ts/url-shortener - - - -https://github.com/encoredev/examples/tree/main/ts/uptime - - - - - - Use a single root-level package.json file (monorepo approach) for Encore.ts projects including frontend dependencies - - Separate package.json files in sub-packages - - Encore.ts application must use one package with a single package.json file - Other separate packages must be pre-transpiled to JavaScript - - - - -
\ No newline at end of file + diff --git a/.cursor/rules/07-encore-cli-reference.mdc b/.cursor/rules/07-encore-cli-reference.mdc index 67da8cc..6e55e53 100644 --- a/.cursor/rules/07-encore-cli-reference.mdc +++ b/.cursor/rules/07-encore-cli-reference.mdc @@ -176,178 +176,4 @@ alwaysApply: true - - - -encore run [--debug] [--watch=true] [flags] -Runs your application - - - - - -encore app clone [app-id] [directory] -Clone an Encore app to your computer - - - -encore app create [name] -Create a new Encore app - - - -encore app init [name] -Create new app from existing repository - - - -encore app link [app-id] -Link app with server - - - - - -encore auth login -Log in to Encore - - - -encore auth logout -Logs out current user - - - -encore auth signup -Create new account - - - -encore auth whoami -Show current user - - - - - -encore daemon -Restart daemon for unexpected behavior - - - -encore daemon env -Output environment information - - - - - -encore db shell database-name [--env=name] -Connect via psql shell ---write, --admin, --superuser flags available - - - -encore db conn-uri database-name [--env=name] -Output connection string - - - -encore db proxy [--env=name] -Set up local database connection proxy - - - -encore db reset [service-names...] -Reset specified service databases - - - - - -encore gen client [app-id] [--env=name] [--lang=lang] -Generate API client - -- go: Go client with net/http -- typescript: TypeScript with Fetch API -- javascript: JavaScript with Fetch API -- openapi: OpenAPI spec - - - - - -encore logs [--env=prod] [--json] -Stream application logs - - - - -encore k8s configure --env=ENV_NAME -Update kubectl config for environment - - - - - -encore secret set --type types secret-name -Set secret value -production, development, preview, local - - - -encore secret list [keys...] -List secrets - - - -encore secret archive id -Archive secret value - - - -encore secret unarchive id -Unarchive secret value - - - - - -encore version -Report current version - - - -encore version update -Check and apply updates - - - - - -encore vpn start -Set up secure connection to private environments - - - -encore vpn status -Check VPN connection status - - - -encore vpn stop -Stop VPN connection - - - - - -encore build docker -Build portable Docker image - -- --base string: Define base image -- --push: Push to remote repository - - - - \ No newline at end of file + diff --git a/.cursor/rules/dedupe_rules.js b/.cursor/rules/dedupe_rules.js new file mode 100644 index 0000000..6c3c232 --- /dev/null +++ b/.cursor/rules/dedupe_rules.js @@ -0,0 +1,41 @@ +// Utility to remove duplicated root XML blocks in rule .mdc files. +// Why it exists: The initial split duplicated root sections; this script trims content after the first closing tag. + +import fs from "node:fs"; +import path from "node:path"; + +const rulesDir = path.resolve(path.dirname(new URL(import.meta.url).pathname)); + +const files = fs + .readdirSync(rulesDir) + .filter((file) => file.endsWith(".mdc")); + +files.forEach((file) => { + const filePath = path.join(rulesDir, file); + const content = fs.readFileSync(filePath, "utf8"); + + const tagMatch = content.match(/\n<([a-z0-9_\-]+)>/i); + if (!tagMatch) { + return; + } + + const tag = tagMatch[1]; + const closingToken = ``; + const closingIdx = content.indexOf(closingToken); + + if (closingIdx === -1) { + return; + } + + const closingEnd = closingIdx + closingToken.length; + const remainder = content.slice(closingEnd); + + if (!remainder.trim()) { + return; + } + + const trimmed = `${content.slice(0, closingEnd)}\n`; + fs.writeFileSync(filePath, trimmed, "utf8"); +}); + + From 5ae17f7df92be2e8a1e1e32cd6e5b7ceffb1ceb9 Mon Sep 17 00:00:00 2001 From: Niranjan Kurambhatti Date: Fri, 24 Oct 2025 00:34:01 -0500 Subject: [PATCH 4/4] Fix environment URL formatting - Update Environment() to use steering-wheel-documentation-65b2-.encr.app - Ensure generated URLs place environment name as suffix to app ID --- frontend/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/client.ts b/frontend/client.ts index f1053e1..1f3303a 100644 --- a/frontend/client.ts +++ b/frontend/client.ts @@ -17,7 +17,7 @@ export const Local: BaseURL = "http://localhost:4000" * Environment returns a BaseURL for calling the cloud environment with the given name. */ export function Environment(name: string): BaseURL { - return `https://${name}-steering-wheel-documentation-65b2.encr.app` + return `https://steering-wheel-documentation-65b2-${name}.encr.app` } /**