import fastifyCors from '@fastify/cors'; import fastifySwagger from '@fastify/swagger'; import fastify from 'fastify'; import { hasZodFastifySchemaValidationErrors, isResponseSerializationError, jsonSchemaTransform, jsonSchemaTransformObject, serializerCompiler, validatorCompiler, type ZodTypeProvider, } from 'fastify-type-provider-zod'; import scalar from '@scalar/fastify-api-reference'; import { StashRuntime } from '@morten-olsen/stash-runtime'; import { systemEndpoints } from './endpoints/system/system.js'; import { documentEndpoints } from './endpoints/documents/documents.js'; import { documentFilterEndpoints } from './endpoints/document-filters/document-filters.js'; import { documentChunkFilterEndpoints } from './endpoints/document-chunk-filters/document-chunk-filters.js'; class BaseError extends Error { public statusCode: number; constructor(message: string, statusCode = 500) { super(message); this.name = this.constructor.name; this.statusCode = statusCode; } } const createApi = async (runtime: StashRuntime = new StashRuntime()) => { const app = fastify().withTypeProvider(); app.setValidatorCompiler(validatorCompiler); app.setSerializerCompiler(serializerCompiler); app.decorate('runtime', runtime); app.register(fastifyCors); app.register(fastifySwagger, { openapi: { info: { title: 'My API', version: '1.0.0', }, }, transform: jsonSchemaTransform, transformObject: jsonSchemaTransformObject, }); await app.register(scalar, { routePrefix: '/docs', configuration: { pageTitle: 'Foo', title: 'Hello World!', telemetry: false, hideClientButton: true, theme: 'laserwave', persistAuth: true, orderRequiredPropertiesFirst: false, }, }); app.setErrorHandler((err, req, reply) => { console.error(err); if (hasZodFastifySchemaValidationErrors(err)) { return reply.code(400).send({ error: 'Response Validation Error', message: "Request doesn't match the schema", statusCode: 400, details: { issues: err.validation, method: req.method, url: req.url, }, }); } if (isResponseSerializationError(err)) { return reply.code(500).send({ error: 'Internal Server Error', message: "Response doesn't match the schema", statusCode: 500, details: { issues: err.cause.issues, method: err.method, url: err.url, }, }); } if (err instanceof BaseError) { return reply.code(err.statusCode ?? 500).send({ error: err.name, message: err.message, statusCode: err.statusCode, }); } return reply.code(500).send({ error: 'Internal Server Error', message: err instanceof Error ? err.message : 'An unknown error occurred', statusCode: 500, }); }); app.addHook('onReady', async () => { app.runtime.warmup.ensure(); }); await app.register(systemEndpoints, { prefix: '/system' }); await app.register(documentEndpoints, { prefix: '/documents' }); await app.register(documentFilterEndpoints, { prefix: '/document-filters' }); await app.register(documentChunkFilterEndpoints, { prefix: '/document-chunk-filters' }); await app.ready(); app.swagger(); return app; }; export { createApi };