From ee1d50b13d1182491929312c6f7ea59f3b658968 Mon Sep 17 00:00:00 2001 From: abhay-codes07 Date: Mon, 13 Jul 2026 17:48:31 +0530 Subject: [PATCH] fix(web): register the drifted document endpoints in the api contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The endpoint map and validation schemas have fallen behind what the web app actually exchanges with the API: - the memories grid and the X-bookmarks status query filter @post/documents/documents by `categories`, a field DocumentsWithMemoriesQuerySchema doesn't declare — both smuggle it past client validation with disableValidation, and any future caller that forgets the flag gets the key silently stripped and a filter that no-ops - @post/documents/documents/facets was never registered: DocumentFacetsQuerySchema sits unused in the validation package while the grid calls the endpoint as an untyped raw URL and hand-casts the response - @patch/documents/:id was never registered even though MemoryUpdateSchema exists for exactly this pairing; the document-edit and file-metadata mutations PATCH via template-literal URLs with no request/response typing Add `categories` to the documents query schema, introduce DocumentFacetSchema/DocumentFacetsResponseSchema (total optional — the UI already guards its absence) and register the facets endpoint with the previously-dead query schema, register @patch/documents/:id with MemoryUpdateSchema/MemoryResponseSchema/params, and convert both PATCH call sites to the typed route. The existing disableValidation flags are deliberately left in place: every current caller of @post/documents/documents sets one, so output validation has never been exercised against production responses; removing them is a follow-up that needs verification against the live API. With the schemas now truthful, that cleanup becomes mechanical. Tested: bun test in packages/validation (12 pass, 4 new), tsc --noEmit on packages/validation, bun test + bun run build in apps/web, biome clean on all touched files. --- apps/web/hooks/use-document-mutations.ts | 6 ++-- packages/lib/api.ts | 14 ++++++++++ packages/validation/api.test.ts | 35 +++++++++++++++++++++++- packages/validation/api.ts | 34 +++++++++++++++++++++++ 4 files changed, 86 insertions(+), 3 deletions(-) diff --git a/apps/web/hooks/use-document-mutations.ts b/apps/web/hooks/use-document-mutations.ts index c0b1c8643..09c751426 100644 --- a/apps/web/hooks/use-document-mutations.ts +++ b/apps/web/hooks/use-document-mutations.ts @@ -521,7 +521,8 @@ export function useDocumentMutations({ const data = (await response.json()) as { id: string } if (applyMeta && (title || description)) { - await $fetch(`@patch/documents/${data.id}`, { + await $fetch("@patch/documents/:id", { + params: { id: data.id }, body: { metadata: { ...(title && { title }), @@ -642,7 +643,8 @@ export function useDocumentMutations({ documentId: string content: string }) => { - const response = await $fetch(`@patch/documents/${documentId}`, { + const response = await $fetch("@patch/documents/:id", { + params: { id: documentId }, body: { content, metadata: { sm_source: "consumer" }, diff --git a/packages/lib/api.ts b/packages/lib/api.ts index 42901c0b1..b87f76cf6 100644 --- a/packages/lib/api.ts +++ b/packages/lib/api.ts @@ -11,6 +11,8 @@ import { CreateProjectSchema, DeleteProjectResponseSchema, DeleteProjectSchema, + DocumentFacetsQuerySchema, + DocumentFacetsResponseSchema, DocumentsWithMemoriesQuerySchema, DocumentsWithMemoriesResponseSchema, ListContainerTagsResponseSchema, @@ -18,6 +20,7 @@ import { ListProjectsResponseSchema, MemoryAddSchema, MemoryResponseSchema, + MemoryUpdateSchema, MigrateMCPRequestSchema, MigrateMCPResponseSchema, ProcessingDocumentsResponseSchema, @@ -265,6 +268,10 @@ export const apiSchema = createSchema({ input: DocumentsWithMemoriesQuerySchema, output: DocumentsWithMemoriesResponseSchema, }, + "@post/documents/documents/facets": { + input: DocumentFacetsQuerySchema, + output: DocumentFacetsResponseSchema, + }, "@post/documents/documents/by-ids": { input: z.object({ ids: z.array(z.string()), @@ -291,6 +298,13 @@ export const apiSchema = createSchema({ output: z.any(), }, + // Update a memory + "@patch/documents/:id": { + input: MemoryUpdateSchema, + output: MemoryResponseSchema, + params: z.object({ id: z.string() }), + }, + // Delete a memory "@delete/documents/:id": { output: z.any(), // 204 No-Content diff --git a/packages/validation/api.test.ts b/packages/validation/api.test.ts index 05c18934c..6282f5ba2 100644 --- a/packages/validation/api.test.ts +++ b/packages/validation/api.test.ts @@ -1,6 +1,11 @@ import { describe, expect, it } from "bun:test" import { readFileSync } from "node:fs" -import { SearchRequestSchema, Searchv4RequestSchema } from "./api" +import { + DocumentFacetsResponseSchema, + DocumentsWithMemoriesQuerySchema, + SearchRequestSchema, + Searchv4RequestSchema, +} from "./api" describe("search threshold schemas", () => { it("do not contain redundant number transforms or unreachable range guards", () => { @@ -80,3 +85,31 @@ describe("search threshold schemas", () => { ).toBe(false) }) }) + +describe("documents contract schemas", () => { + it("DocumentsWithMemoriesQuerySchema accepts the categories filter", () => { + const parsed = DocumentsWithMemoriesQuerySchema.parse({ + categories: ["webpage", "tweet"], + }) + expect(parsed.categories).toEqual(["webpage", "tweet"]) + }) + + it("DocumentsWithMemoriesQuerySchema leaves categories undefined when omitted", () => { + expect( + DocumentsWithMemoriesQuerySchema.parse({}).categories, + ).toBeUndefined() + }) + + it("DocumentFacetsResponseSchema parses a facets payload", () => { + const parsed = DocumentFacetsResponseSchema.parse({ + facets: [{ category: "webpage", count: 42, label: "Web pages" }], + total: 42, + }) + expect(parsed.facets[0]?.category).toBe("webpage") + }) + + it("DocumentFacetsResponseSchema tolerates a missing total", () => { + const parsed = DocumentFacetsResponseSchema.parse({ facets: [] }) + expect(parsed.total).toBeUndefined() + }) +}) diff --git a/packages/validation/api.ts b/packages/validation/api.ts index 8579ca408..b33ee2126 100644 --- a/packages/validation/api.ts +++ b/packages/validation/api.ts @@ -1100,6 +1100,13 @@ export const DocumentsWithMemoriesQuerySchema = z description: "Number of items per page", example: 10, }), + categories: z + .array(z.string()) + .optional() + .openapi({ + description: "Optional document categories to filter by", + example: ["webpage", "tweet"], + }), sort: z.enum(["createdAt", "updatedAt"]).default("createdAt").openapi({ description: "Field to sort by", example: "createdAt", @@ -1134,6 +1141,33 @@ export const DocumentFacetsQuerySchema = z description: "Query parameters for getting document facets", }) +export const DocumentFacetSchema = z + .object({ + category: z.string().openapi({ + description: "Document category identifier", + example: "webpage", + }), + count: z.number().openapi({ + description: "Number of documents in this category", + example: 42, + }), + label: z.string().openapi({ + description: "Human-readable category label", + example: "Web pages", + }), + }) + .openapi({ description: "A single document-category facet" }) + +export const DocumentFacetsResponseSchema = z + .object({ + facets: z.array(DocumentFacetSchema), + total: z.number().optional().openapi({ + description: "Total number of documents across all categories", + example: 120, + }), + }) + .openapi({ description: "Document category facets" }) + export const MigrateMCPRequestSchema = z .object({ userId: z.string().openapi({