diff --git a/packages/server/package.json b/packages/server/package.json index d8c3c0c3..98a9aad7 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -73,6 +73,7 @@ "next": "^15.0.0", "fastify": "^5.6.1", "fastify-plugin": "^5.1.0", + "elysia": "^1.3.1", "supertest": "^7.1.4", "zod": "~3.25.0" }, @@ -81,6 +82,7 @@ "next": "^15.0.0", "fastify": "^5.0.0", "fastify-plugin": "^5.0.0", + "elysia": "^1.3.0", "zod": "catalog:" }, "peerDependenciesMeta": { @@ -95,6 +97,9 @@ }, "fastify-plugin": { "optional": true + }, + "elysia": { + "optional": true } } } diff --git a/packages/server/src/adapter/elysia/handler.ts b/packages/server/src/adapter/elysia/handler.ts new file mode 100644 index 00000000..12d79f51 --- /dev/null +++ b/packages/server/src/adapter/elysia/handler.ts @@ -0,0 +1,78 @@ +import type { ClientContract } from '@zenstackhq/orm'; +import type { SchemaDef } from '@zenstackhq/orm/schema'; +import { Elysia, type Context as ElysiaContext } from 'elysia'; +import { log } from '../../api/utils'; +import type { CommonAdapterOptions } from '../common'; + +/** + * Options for initializing an Elysia middleware. + */ +export interface ElysiaOptions extends CommonAdapterOptions { + /** + * Callback method for getting a ZenStackClient instance for the given request context. + */ + getClient: (context: ElysiaContext) => Promise> | ClientContract; + + /** + * Optional base path to strip from the request path before passing to the API handler. + */ + basePath?: string; +} + +/** + * Creates an Elysia middleware handler for ZenStack. + * This handler provides automatic CRUD APIs through Elysia's routing system. + */ +export function createElysiaHandler(options: ElysiaOptions) { + return async (app: Elysia) => { + app.all('/*', async (ctx: ElysiaContext) => { + const { request, body, set } = ctx; + const client = await options.getClient(ctx); + if (!client) { + set.status = 500; + return { + message: 'unable to get ZenStackClient from request context', + }; + } + + const url = new URL(request.url); + const query = Object.fromEntries(url.searchParams); + let path = url.pathname; + + if (options.basePath && path.startsWith(options.basePath)) { + path = path.slice(options.basePath.length); + if (!path.startsWith('/')) { + path = '/' + path; + } + } + + if (!path || path === '/') { + set.status = 400; + return { + message: 'missing path parameter', + }; + } + + try { + const r = await options.apiHandler.handleRequest({ + method: request.method, + path, + query, + requestBody: body, + client, + }); + + set.status = r.status; + return r.body; + } catch (err) { + set.status = 500; + log(options.apiHandler.log, 'error', `An unhandled error occurred while processing the request: ${err}${err instanceof Error ? '\n' + err.stack : ''}`); + return { + message: 'An internal server error occurred', + }; + } + }); + + return app; + }; +} diff --git a/packages/server/src/adapter/elysia/index.ts b/packages/server/src/adapter/elysia/index.ts new file mode 100644 index 00000000..bc0d7e16 --- /dev/null +++ b/packages/server/src/adapter/elysia/index.ts @@ -0,0 +1 @@ +export * from './handler'; \ No newline at end of file diff --git a/packages/server/src/adapter/express/middleware.ts b/packages/server/src/adapter/express/middleware.ts index e8088f04..5b727b27 100644 --- a/packages/server/src/adapter/express/middleware.ts +++ b/packages/server/src/adapter/express/middleware.ts @@ -1,6 +1,7 @@ import type { ClientContract } from '@zenstackhq/orm'; import type { SchemaDef } from '@zenstackhq/orm/schema'; import type { Handler, Request, Response } from 'express'; +import { log } from '../../api/utils'; import type { CommonAdapterOptions } from '../common'; /** @@ -70,7 +71,8 @@ const factory = (options: MiddlewareOptions): if (sendResponse === false) { throw err; } - return response.status(500).json({ message: `An unhandled error occurred: ${err}` }); + log(options.apiHandler.log, 'error', `An unhandled error occurred while processing the request: ${err}${err instanceof Error ? '\n' + err.stack : ''}`); + return response.status(500).json({ message: `An internal server error occurred` }); } }; }; diff --git a/packages/server/src/adapter/fastify/plugin.ts b/packages/server/src/adapter/fastify/plugin.ts index 38eb1429..a30e4966 100644 --- a/packages/server/src/adapter/fastify/plugin.ts +++ b/packages/server/src/adapter/fastify/plugin.ts @@ -2,6 +2,7 @@ import type { ClientContract } from '@zenstackhq/orm'; import type { SchemaDef } from '@zenstackhq/orm/schema'; import type { FastifyPluginCallback, FastifyReply, FastifyRequest } from 'fastify'; import fp from 'fastify-plugin'; +import { log } from '../../api/utils'; import type { CommonAdapterOptions } from '../common'; /** @@ -43,7 +44,8 @@ const pluginHandler: FastifyPluginCallback> = (fastify, }); reply.status(response.status).send(response.body); } catch (err) { - reply.status(500).send({ message: `An unhandled error occurred: ${err}` }); + log(options.apiHandler.log, 'error', `An unhandled error occurred while processing the request: ${err}${err instanceof Error ? '\n' + err.stack : ''}`); + reply.status(500).send({ message: `An internal server error occurred` }); } return reply; diff --git a/packages/server/src/adapter/next/app-route-handler.ts b/packages/server/src/adapter/next/app-route-handler.ts index c220e988..b06c24f4 100644 --- a/packages/server/src/adapter/next/app-route-handler.ts +++ b/packages/server/src/adapter/next/app-route-handler.ts @@ -1,6 +1,7 @@ import type { SchemaDef } from '@zenstackhq/orm/schema'; import { NextRequest, NextResponse } from 'next/server'; import type { AppRouteRequestHandlerOptions } from '.'; +import { log } from '../../api/utils'; type Context = { params: Promise<{ path: string[] }> }; @@ -58,7 +59,8 @@ export default function factory( }); return NextResponse.json(r.body, { status: r.status }); } catch (err) { - return NextResponse.json({ message: `An unhandled error occurred: ${err}` }, { status: 500 }); + log(options.apiHandler.log, 'error', `An unhandled error occurred while processing the request: ${err}${err instanceof Error ? '\n' + err.stack : ''}`); + return NextResponse.json({ message: 'An internal server error occurred' }, { status: 500 }); } }; } diff --git a/packages/server/src/adapter/next/pages-route-handler.ts b/packages/server/src/adapter/next/pages-route-handler.ts index e65c65e8..b5043692 100644 --- a/packages/server/src/adapter/next/pages-route-handler.ts +++ b/packages/server/src/adapter/next/pages-route-handler.ts @@ -1,6 +1,7 @@ import type { SchemaDef } from '@zenstackhq/orm/schema'; import type { NextApiRequest, NextApiResponse } from 'next'; import type { PageRouteRequestHandlerOptions } from '.'; +import { log } from '../../api/utils'; /** * Creates a Next.js API endpoint "pages" router request handler that handles ZenStack CRUD requests. @@ -34,7 +35,8 @@ export default function factory( }); res.status(r.status).send(r.body); } catch (err) { - res.status(500).send({ message: `An unhandled error occurred: ${err}` }); + log(options.apiHandler.log, 'error', `An unhandled error occurred while processing the request: ${err}${err instanceof Error ? '\n' + err.stack : ''}`); + res.status(500).send({ message: 'An internal server error occurred' }); } }; } diff --git a/packages/server/src/api/rest/index.ts b/packages/server/src/api/rest/index.ts index bcf27829..e0a08dcb 100644 --- a/packages/server/src/api/rest/index.ts +++ b/packages/server/src/api/rest/index.ts @@ -285,6 +285,10 @@ export class RestApiHandler implements ApiHandler { const options = { segmentValueCharset: urlSegmentNameCharset }; diff --git a/packages/server/src/api/rpc/index.ts b/packages/server/src/api/rpc/index.ts index e9d73fc0..51fd4bc0 100644 --- a/packages/server/src/api/rpc/index.ts +++ b/packages/server/src/api/rpc/index.ts @@ -38,6 +38,10 @@ export class RPCApiHandler implements ApiHandler): Promise { const parts = path.split('/').filter((p) => !!p); const op = parts.pop(); diff --git a/packages/server/src/types.ts b/packages/server/src/types.ts index 1d2b4321..290081f5 100644 --- a/packages/server/src/types.ts +++ b/packages/server/src/types.ts @@ -70,6 +70,11 @@ export interface ApiHandler { */ get schema(): Schema; + /** + * Logging configuration. + */ + get log(): LogConfig | undefined; + /** * Handle an API request. */ diff --git a/packages/server/test/adapter/elysia.test.ts b/packages/server/test/adapter/elysia.test.ts new file mode 100644 index 00000000..009ee938 --- /dev/null +++ b/packages/server/test/adapter/elysia.test.ts @@ -0,0 +1,164 @@ +import { createTestClient } from '@zenstackhq/testtools'; +import { Elysia } from 'elysia'; +import superjson from 'superjson'; +import { describe, expect, it } from 'vitest'; +import { createElysiaHandler } from '../../src/adapter/elysia'; +import { RestApiHandler, RPCApiHandler } from '../../src/api'; +import { makeUrl, schema } from '../utils'; + +describe('Elysia adapter tests - rpc handler', () => { + it('properly handles requests', async () => { + const client = await createTestClient(schema); + + const handler = await createElysiaApp( + createElysiaHandler({ getClient: () => client, basePath: '/api', apiHandler: new RPCApiHandler({ schema: client.schema }) }) + ); + + let r = await handler(makeRequest('GET', makeUrl('/api/post/findMany', { where: { id: { equals: '1' } } }))); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data).toHaveLength(0); + + r = await handler( + makeRequest('POST', '/api/user/create', { + include: { posts: true }, + data: { + id: 'user1', + email: 'user1@abc.com', + posts: { + create: [ + { title: 'post1', published: true, viewCount: 1 }, + { title: 'post2', published: false, viewCount: 2 }, + ], + }, + }, + }) + ); + expect(r.status).toBe(201); + expect((await unmarshal(r)).data).toMatchObject({ + email: 'user1@abc.com', + posts: expect.arrayContaining([ + expect.objectContaining({ title: 'post1' }), + expect.objectContaining({ title: 'post2' }), + ]), + }); + + r = await handler(makeRequest('GET', makeUrl('/api/post/findMany'))); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data).toHaveLength(2); + + r = await handler(makeRequest('GET', makeUrl('/api/post/findMany', { where: { viewCount: { gt: 1 } } }))); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data).toHaveLength(1); + + r = await handler( + makeRequest('PUT', '/api/user/update', { where: { id: 'user1' }, data: { email: 'user1@def.com' } }) + ); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data.email).toBe('user1@def.com'); + + r = await handler(makeRequest('GET', makeUrl('/api/post/count', { where: { viewCount: { gt: 1 } } }))); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data).toBe(1); + + r = await handler(makeRequest('GET', makeUrl('/api/post/aggregate', { _sum: { viewCount: true } }))); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data._sum.viewCount).toBe(3); + + r = await handler( + makeRequest('GET', makeUrl('/api/post/groupBy', { by: ['published'], _sum: { viewCount: true } })) + ); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data).toEqual( + expect.arrayContaining([ + expect.objectContaining({ published: true, _sum: { viewCount: 1 } }), + expect.objectContaining({ published: false, _sum: { viewCount: 2 } }), + ]) + ); + + r = await handler(makeRequest('DELETE', makeUrl('/api/user/deleteMany', { where: { id: 'user1' } }))); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data.count).toBe(1); + }); +}); + +describe('Elysia adapter tests - rest handler', () => { + it('properly handles requests', async () => { + const client = await createTestClient(schema); + + const handler = await createElysiaApp( + createElysiaHandler({ + getClient: () => client, + basePath: '/api', + apiHandler: new RestApiHandler({schema: client.$schema, endpoint: 'http://localhost/api' }), + }) + ); + + let r = await handler(makeRequest('GET', makeUrl('/api/post/1'))); + expect(r.status).toBe(404); + + r = await handler( + makeRequest('POST', '/api/user', { + data: { + type: 'user', + attributes: { id: 'user1', email: 'user1@abc.com' }, + }, + }) + ); + expect(r.status).toBe(201); + expect(await unmarshal(r)).toMatchObject({ + data: { + id: 'user1', + attributes: { + email: 'user1@abc.com', + }, + }, + }); + + r = await handler(makeRequest('GET', makeUrl('/api/user?filter[id]=user1'))); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data).toHaveLength(1); + + r = await handler(makeRequest('GET', makeUrl('/api/user?filter[id]=user2'))); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data).toHaveLength(0); + + r = await handler(makeRequest('GET', makeUrl('/api/user?filter[id]=user1&filter[email]=xyz'))); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data).toHaveLength(0); + + r = await handler( + makeRequest('PUT', makeUrl('/api/user/user1'), { + data: { type: 'user', attributes: { email: 'user1@def.com' } }, + }) + ); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data.attributes.email).toBe('user1@def.com'); + + r = await handler(makeRequest('DELETE', makeUrl('/api/user/user1'))); + expect(r.status).toBe(200); + expect(await client.user.findMany()).toHaveLength(0); + }); +}); + +function makeRequest(method: string, path: string, body?: any) { + if (body) { + return new Request(`http://localhost${path}`, { + method, + body: JSON.stringify(body), + headers: { 'Content-Type': 'application/json' }, + }); + } else { + return new Request(`http://localhost${path}`, { method }); + } +} + +async function unmarshal(r: Response, useSuperJson = false) { + const text = await r.text(); + return (useSuperJson ? superjson.parse(text) : JSON.parse(text)) as any; +} + +async function createElysiaApp(middleware: (app: Elysia) => Promise) { + const app = new Elysia(); + await middleware(app); + return app.handle; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0998b9e3..8fa84642 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -463,6 +463,9 @@ importers: body-parser: specifier: ^2.2.0 version: 2.2.0 + elysia: + specifier: ^1.3.1 + version: 1.4.13(@sinclair/typebox@0.34.41)(exact-mirror@0.2.2(@sinclair/typebox@0.34.41))(file-type@21.0.0)(openapi-types@12.1.3)(typescript@5.8.3) express: specifier: ^5.0.0 version: 5.1.0 @@ -685,6 +688,9 @@ importers: packages: + '@borewit/text-codec@0.1.1': + resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==} + '@chevrotain/cst-dts-gen@11.0.3': resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} @@ -1276,6 +1282,9 @@ packages: cpu: [x64] os: [win32] + '@sinclair/typebox@0.34.41': + resolution: {integrity: sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==} + '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} @@ -1365,6 +1374,13 @@ packages: peerDependencies: react: ^18 || ^19 + '@tokenizer/inflate@0.2.7': + resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + '@types/better-sqlite3@7.6.13': resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==} @@ -1902,6 +1918,21 @@ packages: effect@3.16.12: resolution: {integrity: sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg==} + elysia@1.4.13: + resolution: {integrity: sha512-6QaWQEm7QN1UCo1TPpEjaRJPHUmnM7R29y6LY224frDGk5PrpAnWmdHkoZxkcv+JRWp1j2ROr2IHbxHbG/jRjw==} + peerDependencies: + '@sinclair/typebox': '>= 0.34.0 < 1' + '@types/bun': '>= 1.2.0' + exact-mirror: '>= 0.0.9' + file-type: '>= 20.0.0' + openapi-types: '>= 12.0.0' + typescript: '>= 5.0.0' + peerDependenciesMeta: + '@types/bun': + optional: true + typescript: + optional: true + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1999,6 +2030,14 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + exact-mirror@0.2.2: + resolution: {integrity: sha512-CrGe+4QzHZlnrXZVlo/WbUZ4qQZq8C0uATQVGVgXIrNXgHDBBNFD1VRfssRA2C9t3RYvh3MadZSdg2Wy7HBoQA==} + peerDependencies: + '@sinclair/typebox': ^0.34.15 + peerDependenciesMeta: + '@sinclair/typebox': + optional: true + expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} @@ -2063,10 +2102,17 @@ packages: picomatch: optional: true + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + file-type@21.0.0: + resolution: {integrity: sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==} + engines: {node: '>=20'} + file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} @@ -2413,6 +2459,9 @@ packages: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} + memoirist@0.4.0: + resolution: {integrity: sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg==} + merge-descriptors@2.0.0: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} @@ -2582,6 +2631,9 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + openapi-types@12.1.3: + resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -3063,6 +3115,10 @@ packages: strip-literal@3.0.0: resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} + strtok3@10.3.4: + resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} + engines: {node: '>=18'} + styled-jsx@5.1.6: resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} @@ -3155,6 +3211,10 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + token-types@6.1.1: + resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==} + engines: {node: '>=14.16'} + toposort@2.0.2: resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} @@ -3268,6 +3328,10 @@ packages: ufo@1.6.1: resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + uint8array-extras@1.5.0: + resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} + engines: {node: '>=18'} + ulid@3.0.0: resolution: {integrity: sha512-yvZYdXInnJve6LdlPIuYmURdS2NP41ZoF4QW7SXwbUKYt53+0eDAySO+rGSvM2O/ciuB/G+8N7GQrZ1mCJpuqw==} hasBin: true @@ -3456,6 +3520,8 @@ packages: snapshots: + '@borewit/text-codec@0.1.1': {} + '@chevrotain/cst-dts-gen@11.0.3': dependencies: '@chevrotain/gast': 11.0.3 @@ -3891,6 +3957,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.44.0': optional: true + '@sinclair/typebox@0.34.41': {} + '@standard-schema/spec@1.0.0': {} '@swc/core-darwin-arm64@1.12.5': @@ -3959,6 +4027,16 @@ snapshots: '@tanstack/query-core': 5.81.0 react: 19.1.0 + '@tokenizer/inflate@0.2.7': + dependencies: + debug: 4.4.1 + fflate: 0.8.2 + token-types: 6.1.1 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} + '@types/better-sqlite3@7.6.13': dependencies: '@types/node': 20.17.24 @@ -4531,6 +4609,18 @@ snapshots: '@standard-schema/spec': 1.0.0 fast-check: 3.23.2 + elysia@1.4.13(@sinclair/typebox@0.34.41)(exact-mirror@0.2.2(@sinclair/typebox@0.34.41))(file-type@21.0.0)(openapi-types@12.1.3)(typescript@5.8.3): + dependencies: + '@sinclair/typebox': 0.34.41 + cookie: 1.0.2 + exact-mirror: 0.2.2(@sinclair/typebox@0.34.41) + fast-decode-uri-component: 1.0.1 + file-type: 21.0.0 + memoirist: 0.4.0 + openapi-types: 12.1.3 + optionalDependencies: + typescript: 5.8.3 + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -4667,6 +4757,10 @@ snapshots: etag@1.8.1: {} + exact-mirror@0.2.2(@sinclair/typebox@0.34.41): + optionalDependencies: + '@sinclair/typebox': 0.34.41 + expand-template@2.0.3: {} expect-type@1.2.1: {} @@ -4770,10 +4864,21 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + fflate@0.8.2: {} + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 + file-type@21.0.0: + dependencies: + '@tokenizer/inflate': 0.2.7 + strtok3: 10.3.4 + token-types: 6.1.1 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color + file-uri-to-path@1.0.0: {} fill-range@7.1.1: @@ -5127,6 +5232,8 @@ snapshots: media-typer@1.1.0: {} + memoirist@0.4.0: {} + merge-descriptors@2.0.0: {} merge2@1.4.1: {} @@ -5270,6 +5377,8 @@ snapshots: dependencies: mimic-fn: 2.1.0 + openapi-types@12.1.3: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -5804,6 +5913,10 @@ snapshots: dependencies: js-tokens: 9.0.1 + strtok3@10.3.4: + dependencies: + '@tokenizer/token': 0.3.0 + styled-jsx@5.1.6(react@19.1.0): dependencies: client-only: 0.0.1 @@ -5902,6 +6015,12 @@ snapshots: toidentifier@1.0.1: {} + token-types@6.1.1: + dependencies: + '@borewit/text-codec': 0.1.1 + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + toposort@2.0.2: {} tr46@1.0.1: @@ -6013,6 +6132,8 @@ snapshots: ufo@1.6.1: {} + uint8array-extras@1.5.0: {} + ulid@3.0.0: {} undici-types@6.19.8: {}