improved contracts

This commit is contained in:
Morten Olsen
2025-09-09 18:43:14 +02:00
parent c59c9487e6
commit d5b6a8269b
3 changed files with 39 additions and 19 deletions

View File

@@ -89,6 +89,13 @@ const documentSearchOptionsSchema = z.object({
type DocumentSearchOptions = z.infer<typeof documentSearchOptionsSchema>; type DocumentSearchOptions = z.infer<typeof documentSearchOptionsSchema>;
const documentSearchResultSchema = z.object({
documents: z.array(documentSchema),
more: z.boolean(),
});
type DocumentSearchResult = z.infer<typeof documentSearchResultSchema>;
const documentUpsertEventSchema = z.object({ const documentUpsertEventSchema = z.object({
action: z.union([z.literal('insert'), z.literal('update'), z.literal('delete')]), action: z.union([z.literal('insert'), z.literal('update'), z.literal('delete')]),
document: documentSchema, document: documentSchema,
@@ -96,12 +103,21 @@ const documentUpsertEventSchema = z.object({
type DocumentUpsertEvent = z.infer<typeof documentUpsertEventSchema>; type DocumentUpsertEvent = z.infer<typeof documentUpsertEventSchema>;
export type { Document, DocumentUpsert, MetaFilter, MetaCondition, DocumentSearchOptions, DocumentUpsertEvent }; export type {
Document,
DocumentUpsert,
MetaFilter,
MetaCondition,
DocumentSearchOptions,
DocumentSearchResult,
DocumentUpsertEvent,
};
export { export {
documentSchema, documentSchema,
documentUpsertSchema, documentUpsertSchema,
metaFilterSchema, metaFilterSchema,
metaConditionSchema, metaConditionSchema,
documentSearchOptionsSchema, documentSearchOptionsSchema,
documentSearchResultSchema,
documentUpsertEventSchema, documentUpsertEventSchema,
}; };

View File

@@ -7,6 +7,7 @@ import {
documentUpsertEventSchema, documentUpsertEventSchema,
type Document, type Document,
type DocumentSearchOptions, type DocumentSearchOptions,
type DocumentSearchResult,
type DocumentUpsert, type DocumentUpsert,
type DocumentUpsertEvent, type DocumentUpsertEvent,
} from './documents.schemas.ts'; } from './documents.schemas.ts';
@@ -26,7 +27,7 @@ class DocumentsService extends EventEmitter<DocumentEvents> {
this.#services = services; this.#services = services;
} }
public get = async (uri: string, type: string) => { public get = async (uri: string, type: string): Promise<Document | undefined> => {
const db = await this.#services.get(DatabaseService).getDb(); const db = await this.#services.get(DatabaseService).getDb();
const [document] = await db<TableRow['document']>(tableNames.documents) const [document] = await db<TableRow['document']>(tableNames.documents)
.where({ .where({
@@ -37,7 +38,7 @@ class DocumentsService extends EventEmitter<DocumentEvents> {
return document; return document;
}; };
public delete = async (uri: string, type: string) => { public delete = async (uri: string, type: string): Promise<DocumentUpsertEvent | undefined> => {
const db = await this.#services.get(DatabaseService).getDb(); const db = await this.#services.get(DatabaseService).getDb();
const [document] = await db<TableRow['document']>(tableNames.documents) const [document] = await db<TableRow['document']>(tableNames.documents)
.where({ .where({
@@ -53,10 +54,12 @@ class DocumentsService extends EventEmitter<DocumentEvents> {
deletedAt: new Date().toISOString(), deletedAt: new Date().toISOString(),
}; };
await db(tableNames.documents).where({ uri, type }).update(toDelete); 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<DocumentUpsertEvent | undefined> => {
const db = await this.#services.get(DatabaseService).getDb(); const db = await this.#services.get(DatabaseService).getDb();
const [current] = await db<TableRow['document']>(tableNames.documents) const [current] = await db<TableRow['document']>(tableNames.documents)
.where({ .where({
@@ -80,7 +83,9 @@ class DocumentsService extends EventEmitter<DocumentEvents> {
uri: document.uri, uri: document.uri,
type: document.type, type: document.type,
}); });
this.emit('upsert', { action: 'update', document: toInsert }); const event: DocumentUpsertEvent = { action: 'update', document: toInsert };
this.emit('upsert', event);
return event;
} else { } else {
const toInsert: Document = { const toInsert: Document = {
...document, ...document,
@@ -88,12 +93,14 @@ class DocumentsService extends EventEmitter<DocumentEvents> {
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
}; };
await db(tableNames.documents).insert(toInsert); 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) => { public search = async (options: DocumentSearchOptions): Promise<DocumentSearchResult> => {
const { uris, types, meta, limit, offset } = options; const { uris, types, meta, limit = 10, offset = 0 } = options;
const db = await this.#services.get(DatabaseService).getDb(); const db = await this.#services.get(DatabaseService).getDb();
let query = db<TableRow['document']>(tableNames.documents); let query = db<TableRow['document']>(tableNames.documents);
if (uris) { if (uris) {
@@ -102,18 +109,15 @@ class DocumentsService extends EventEmitter<DocumentEvents> {
if (types) { if (types) {
query = query.whereIn('type', types); query = query.whereIn('type', types);
} }
if (limit) {
query = query.limit(limit);
}
if (meta) { if (meta) {
query = query.where((builder) => { query = query.where((builder) => {
buildMetaCondition(builder, meta); buildMetaCondition(builder, meta);
}); });
} }
if (offset) { query = query.limit(limit + 1).offset(offset);
query = query.offset(offset); const documents = await query;
} const more = documents.length > limit;
return query; return { documents: documents.slice(0, limit), more };
}; };
} }

View File

@@ -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 { parseDSL } from '@morten-olsen/fluxcurrent-core/services/documents/documents.dsl.ts';
import { DocumentsService } from '@morten-olsen/fluxcurrent-core/services/documents/documents.ts'; import { DocumentsService } from '@morten-olsen/fluxcurrent-core/services/documents/documents.ts';
import { import {
documentSchema, documentSearchResultSchema,
type DocumentUpsertEvent, type DocumentUpsertEvent,
} from '@morten-olsen/fluxcurrent-core/services/documents/documents.schemas.ts'; } from '@morten-olsen/fluxcurrent-core/services/documents/documents.schemas.ts';
import { filterDocument } from '@morten-olsen/fluxcurrent-core/services/documents/documents.filter.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(), query: z.string().optional(),
}), }),
response: { response: {
200: z.array(documentSchema), 200: documentSearchResultSchema,
}, },
}, },
handler: async (req, res) => { handler: async (req, res) => {
@@ -42,7 +42,7 @@ const searchEndpoint: FastifyPluginAsyncZod<{ services: Services }> = async (fas
const documentsService = services.get(DocumentsService); const documentsService = services.get(DocumentsService);
res.sse({ event: 'init' }); res.sse({ event: 'init' });
const documents = await documentsService.search(query); 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) }); res.sse({ event: 'upsert', data: JSON.stringify(document) });
} }
const listener = (event: DocumentUpsertEvent) => { const listener = (event: DocumentUpsertEvent) => {