mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-21 05:18:00 +00:00
fix(web): register the drifted document endpoints in the api contract
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.
This commit is contained in:
parent
2cebe81512
commit
ee1d50b13d
4 changed files with 86 additions and 3 deletions
|
|
@ -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" },
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue