mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-11-05 07:39:44 +00:00
fix(lib): parse object-shaped container tag profile entries
The profile endpoint returns entries shaped like { id, memory, ... },
but the $fetch output schema expected string[] and threw for every
non-empty profile.
Add shared ProfileEntrySchema and ContainerTagProfileResponseSchema,
keep unknown fields, and cover object-shaped and empty profiles.
Refs #1849
This commit is contained in:
parent
225fbf0e0c
commit
e45d39d16b
3 changed files with 86 additions and 6 deletions
|
|
@ -7,6 +7,7 @@ import {
|
|||
BulkDeleteMemoriesResponseSchema,
|
||||
BulkDeleteMemoriesSchema,
|
||||
ConnectionResponseSchema,
|
||||
ContainerTagProfileResponseSchema,
|
||||
ContainerTagSettingsUpdateSchema,
|
||||
CreateProjectSchema,
|
||||
DeleteProjectResponseSchema,
|
||||
|
|
@ -312,12 +313,7 @@ export const apiSchema = createSchema({
|
|||
output: ListContainerTagsResponseSchema,
|
||||
},
|
||||
"@get/container-tags/:containerTag/profile": {
|
||||
output: z.object({
|
||||
profile: z.object({
|
||||
static: z.array(z.string()).optional(),
|
||||
dynamic: z.array(z.string()).optional(),
|
||||
}),
|
||||
}),
|
||||
output: ContainerTagProfileResponseSchema,
|
||||
params: z.object({
|
||||
containerTag: z.string(),
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { describe, expect, it } from "bun:test"
|
|||
import { readFileSync } from "node:fs"
|
||||
import {
|
||||
BulkDeleteMemoriesSchema,
|
||||
ContainerTagProfileResponseSchema,
|
||||
DocumentsWithMemoriesQuerySchema,
|
||||
ListMemoriesQuerySchema,
|
||||
SearchRequestSchema,
|
||||
|
|
@ -175,3 +176,61 @@ describe("pagination query schemas", () => {
|
|||
expect(BulkDeleteMemoriesSchema.safeParse(ok).success).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("ContainerTagProfileResponseSchema", () => {
|
||||
const entry = {
|
||||
id: "mem_123",
|
||||
memory: "Nora owns the billing pipeline.",
|
||||
isStatic: true,
|
||||
isInference: false,
|
||||
version: 1,
|
||||
sourceCount: 2,
|
||||
rootMemoryId: "mem_root",
|
||||
parentMemoryId: null,
|
||||
createdAt: "2026-10-27T06:52:34.160Z",
|
||||
updatedAt: "2026-10-27T06:52:34.160Z",
|
||||
metadata: { temporalContext: { documentDate: "2026-10-27" } },
|
||||
}
|
||||
|
||||
it("parses the object-shaped entries returned by the API", () => {
|
||||
const parsed = ContainerTagProfileResponseSchema.parse({
|
||||
profile: {
|
||||
static: [entry],
|
||||
dynamic: [{ ...entry, isStatic: false, id: "mem_456" }],
|
||||
},
|
||||
})
|
||||
|
||||
expect(parsed.profile.static?.[0]).toMatchObject({
|
||||
id: "mem_123",
|
||||
memory: "Nora owns the billing pipeline.",
|
||||
isStatic: true,
|
||||
sourceCount: 2,
|
||||
})
|
||||
expect(parsed.profile.dynamic?.[0]?.id).toBe("mem_456")
|
||||
})
|
||||
|
||||
it("preserves unknown entry and profile fields", () => {
|
||||
const parsed = ContainerTagProfileResponseSchema.parse({
|
||||
profile: {
|
||||
static: [{ ...entry, futureEntryField: "kept" }],
|
||||
buckets: { work: [entry] },
|
||||
},
|
||||
})
|
||||
|
||||
expect(
|
||||
(parsed.profile.static?.[0] as Record<string, unknown>).futureEntryField,
|
||||
).toBe("kept")
|
||||
expect((parsed.profile as Record<string, unknown>).buckets).toEqual({
|
||||
work: [entry],
|
||||
})
|
||||
})
|
||||
|
||||
it("accepts an empty profile", () => {
|
||||
const parsed = ContainerTagProfileResponseSchema.parse({
|
||||
profile: { static: [], dynamic: [] },
|
||||
})
|
||||
|
||||
expect(parsed.profile.static).toEqual([])
|
||||
expect(parsed.profile.dynamic).toEqual([])
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1471,6 +1471,31 @@ export const BulkDeleteMemoriesResponseSchema = z
|
|||
description: "Response for bulk memory deletion",
|
||||
})
|
||||
|
||||
export const ProfileEntrySchema = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
memory: z.string(),
|
||||
isStatic: z.boolean().optional(),
|
||||
isInference: z.boolean().optional(),
|
||||
version: z.number().optional(),
|
||||
sourceCount: z.number().optional(),
|
||||
rootMemoryId: z.string().nullable().optional(),
|
||||
parentMemoryId: z.string().nullable().optional(),
|
||||
createdAt: z.string().optional(),
|
||||
updatedAt: z.string().optional(),
|
||||
metadata: z.record(z.unknown()).nullable().optional(),
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
export const ContainerTagProfileResponseSchema = z.object({
|
||||
profile: z
|
||||
.object({
|
||||
static: z.array(ProfileEntrySchema).optional(),
|
||||
dynamic: z.array(ProfileEntrySchema).optional(),
|
||||
})
|
||||
.passthrough(),
|
||||
})
|
||||
|
||||
export const ContainerTagListTypeSchema = z
|
||||
.object({
|
||||
id: z.string().openapi({
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue