From d5b6a8269bd6b98f45dadb501e28469baa0dbaba Mon Sep 17 00:00:00 2001 From: Morten Olsen Date: Tue, 9 Sep 2025 18:43:14 +0200 Subject: [PATCH] improved contracts --- .../services/documents/documents.schemas.ts | 18 +++++++++- .../core/src/services/documents/documents.ts | 34 +++++++++++-------- .../src/api/endpoints/endpoints.search.ts | 6 ++-- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/packages/core/src/services/documents/documents.schemas.ts b/packages/core/src/services/documents/documents.schemas.ts index b4b7338..beaf8a9 100644 --- a/packages/core/src/services/documents/documents.schemas.ts +++ b/packages/core/src/services/documents/documents.schemas.ts @@ -89,6 +89,13 @@ const documentSearchOptionsSchema = z.object({ type DocumentSearchOptions = z.infer; +const documentSearchResultSchema = z.object({ + documents: z.array(documentSchema), + more: z.boolean(), +}); + +type DocumentSearchResult = z.infer; + const documentUpsertEventSchema = z.object({ action: z.union([z.literal('insert'), z.literal('update'), z.literal('delete')]), document: documentSchema, @@ -96,12 +103,21 @@ const documentUpsertEventSchema = z.object({ type DocumentUpsertEvent = z.infer; -export type { Document, DocumentUpsert, MetaFilter, MetaCondition, DocumentSearchOptions, DocumentUpsertEvent }; +export type { + Document, + DocumentUpsert, + MetaFilter, + MetaCondition, + DocumentSearchOptions, + DocumentSearchResult, + DocumentUpsertEvent, +}; export { documentSchema, documentUpsertSchema, metaFilterSchema, metaConditionSchema, documentSearchOptionsSchema, + documentSearchResultSchema, documentUpsertEventSchema, }; diff --git a/packages/core/src/services/documents/documents.ts b/packages/core/src/services/documents/documents.ts index a18cfcd..e288ea0 100644 --- a/packages/core/src/services/documents/documents.ts +++ b/packages/core/src/services/documents/documents.ts @@ -7,6 +7,7 @@ import { documentUpsertEventSchema, type Document, type DocumentSearchOptions, + type DocumentSearchResult, type DocumentUpsert, type DocumentUpsertEvent, } from './documents.schemas.ts'; @@ -26,7 +27,7 @@ class DocumentsService extends EventEmitter { this.#services = services; } - public get = async (uri: string, type: string) => { + public get = async (uri: string, type: string): Promise => { const db = await this.#services.get(DatabaseService).getDb(); const [document] = await db(tableNames.documents) .where({ @@ -37,7 +38,7 @@ class DocumentsService extends EventEmitter { return document; }; - public delete = async (uri: string, type: string) => { + public delete = async (uri: string, type: string): Promise => { const db = await this.#services.get(DatabaseService).getDb(); const [document] = await db(tableNames.documents) .where({ @@ -53,10 +54,12 @@ class DocumentsService extends EventEmitter { deletedAt: new Date().toISOString(), }; await db(tableNames.documents).where({ uri, type }).update(toDelete); - this.emit('upsert', { action: 'delete', document: toDelete }); + const event: DocumentUpsertEvent = { action: 'delete', document: toDelete }; + this.emit('upsert', event); + return event; }; - public upsert = async (document: DocumentUpsert) => { + public upsert = async (document: DocumentUpsert): Promise => { const db = await this.#services.get(DatabaseService).getDb(); const [current] = await db(tableNames.documents) .where({ @@ -80,7 +83,9 @@ class DocumentsService extends EventEmitter { uri: document.uri, type: document.type, }); - this.emit('upsert', { action: 'update', document: toInsert }); + const event: DocumentUpsertEvent = { action: 'update', document: toInsert }; + this.emit('upsert', event); + return event; } else { const toInsert: Document = { ...document, @@ -88,12 +93,14 @@ class DocumentsService extends EventEmitter { updatedAt: new Date().toISOString(), }; await db(tableNames.documents).insert(toInsert); - this.emit('upsert', { action: 'insert', document: toInsert }); + const event: DocumentUpsertEvent = { action: 'insert', document: toInsert }; + this.emit('upsert', event); + return event; } }; - public search = async (options: DocumentSearchOptions) => { - const { uris, types, meta, limit, offset } = options; + public search = async (options: DocumentSearchOptions): Promise => { + const { uris, types, meta, limit = 10, offset = 0 } = options; const db = await this.#services.get(DatabaseService).getDb(); let query = db(tableNames.documents); if (uris) { @@ -102,18 +109,15 @@ class DocumentsService extends EventEmitter { if (types) { query = query.whereIn('type', types); } - if (limit) { - query = query.limit(limit); - } if (meta) { query = query.where((builder) => { buildMetaCondition(builder, meta); }); } - if (offset) { - query = query.offset(offset); - } - return query; + query = query.limit(limit + 1).offset(offset); + const documents = await query; + const more = documents.length > limit; + return { documents: documents.slice(0, limit), more }; }; } diff --git a/packages/server/src/api/endpoints/endpoints.search.ts b/packages/server/src/api/endpoints/endpoints.search.ts index 1209a83..ae8a52c 100644 --- a/packages/server/src/api/endpoints/endpoints.search.ts +++ b/packages/server/src/api/endpoints/endpoints.search.ts @@ -4,7 +4,7 @@ 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, + documentSearchResultSchema, type DocumentUpsertEvent, } from '@morten-olsen/fluxcurrent-core/services/documents/documents.schemas.ts'; import { filterDocument } from '@morten-olsen/fluxcurrent-core/services/documents/documents.filter.ts'; @@ -18,7 +18,7 @@ const searchEndpoint: FastifyPluginAsyncZod<{ services: Services }> = async (fas query: z.string().optional(), }), response: { - 200: z.array(documentSchema), + 200: documentSearchResultSchema, }, }, handler: async (req, res) => { @@ -42,7 +42,7 @@ const searchEndpoint: FastifyPluginAsyncZod<{ services: Services }> = async (fas const documentsService = services.get(DocumentsService); res.sse({ event: 'init' }); const documents = await documentsService.search(query); - for (const document of documents) { + for (const document of documents.documents) { res.sse({ event: 'upsert', data: JSON.stringify(document) }); } const listener = (event: DocumentUpsertEvent) => {