This commit is contained in:
Morten Olsen
2025-09-09 18:06:45 +02:00
parent ba7aa90434
commit 0ff0b0992b
25 changed files with 3177 additions and 198 deletions

View File

@@ -0,0 +1,45 @@
import fastify from 'fastify';
import fastifySwagger from '@fastify/swagger';
import fastifyApiReference from '@scalar/fastify-api-reference';
import { jsonSchemaTransform, serializerCompiler, validatorCompiler } from 'fastify-type-provider-zod';
import type { Services } from '@morten-olsen/fluxcurrent-core/utils/services.ts';
import { FastifySSEPlugin } from 'fastify-sse-v2';
import { searchEndpoint } from './endpoints/endpoints.search.ts';
import { documentsEndpoint } from './endpoints/endpoints.documents.ts';
type CreateApiOptions = {
services: Services;
};
const createApi = async (options: CreateApiOptions) => {
const app = fastify();
app.setValidatorCompiler(validatorCompiler);
app.setSerializerCompiler(serializerCompiler);
app.register(fastifySwagger, {
openapi: {
info: {
title: 'SampleApi',
description: 'Sample backend service',
version: '1.0.0',
},
servers: [],
},
transform: jsonSchemaTransform,
});
await app.register(fastifyApiReference, {
routePrefix: '/docs',
});
await app.register(FastifySSEPlugin);
await app.register(searchEndpoint, { services: options.services, prefix: '/search' });
await app.register(documentsEndpoint, { services: options.services, prefix: '/documents' });
await app.ready();
return app;
};
export { createApi };

View File

@@ -0,0 +1,24 @@
import type { FastifyPluginAsyncZod } from 'fastify-type-provider-zod';
import { z } from 'zod/v4';
import type { Services } from '@morten-olsen/fluxcurrent-core/utils/services.ts';
import { DocumentsService } from '@morten-olsen/fluxcurrent-core/services/documents/documents.ts';
import { documentUpsertSchema } from '@morten-olsen/fluxcurrent-core/services/documents/documents.schemas.ts';
const documentsEndpoint: FastifyPluginAsyncZod<{ services: Services }> = async (fastify, { services }) => {
fastify.route({
method: 'POST',
url: '',
schema: {
body: z.object({
document: documentUpsertSchema,
}),
},
handler: async (req, res) => {
const documentsService = services.get(DocumentsService);
const documents = await documentsService.upsert(req.body.document);
res.send(documents);
},
});
};
export { documentsEndpoint };

View File

@@ -0,0 +1,62 @@
import type { FastifyPluginAsyncZod } from 'fastify-type-provider-zod';
import { z } from 'zod/v4';
import type { Services } from '@morten-olsen/fluxcurrent-core/utils/services.ts';
import { parseDSL } from '@morten-olsen/fluxcurrent-core/services/documents/documents.dsl.ts';
import { DocumentsService } from '@morten-olsen/fluxcurrent-core/services/documents/documents.ts';
import {
documentSchema,
type DocumentUpsertEvent,
} from '@morten-olsen/fluxcurrent-core/services/documents/documents.schemas.ts';
import { filterDocument } from '@morten-olsen/fluxcurrent-core/services/documents/documents.filter.ts';
const searchEndpoint: FastifyPluginAsyncZod<{ services: Services }> = async (fastify, { services }) => {
fastify.route({
method: 'POST',
url: '',
schema: {
body: z.object({
query: z.string().optional(),
}),
response: {
200: z.array(documentSchema),
},
},
handler: async (req, res) => {
const query = req.body.query ? parseDSL(req.body.query) : {};
const documentsService = services.get(DocumentsService);
const documents = await documentsService.search(query);
res.send(documents);
},
});
fastify.route({
method: 'GET',
url: '/stream',
schema: {
querystring: z.object({
query: z.string().optional(),
}),
},
handler: async (req, res) => {
const query = req.query.query ? parseDSL(req.query.query) : {};
const documentsService = services.get(DocumentsService);
res.sse({ event: 'init' });
const documents = await documentsService.search(query);
for (const document of documents) {
res.sse({ event: 'upsert', data: JSON.stringify(document) });
}
const listener = (event: DocumentUpsertEvent) => {
if (query.meta && !filterDocument(query.meta, event.document)) {
return;
}
res.sse({ event: 'upsert', data: JSON.stringify(event) });
};
documentsService.on('upsert', listener);
req.socket.on('close', () => {
documentsService.off('upsert', listener);
});
},
});
};
export { searchEndpoint };