fix(docs): correct stale @supermemory/memory-graph API references

apps/docs/integrations/memory-graph.mdx and apps/memory-graph-playground/README.md
documented an older version of the @supermemory/memory-graph public API. Following
the Quick Start, Props Reference, or Exports section as written produced code that
either fails to type-check or silently drops props the component no longer has.

Verified against packages/memory-graph/src/{index.tsx,types.ts,api-types.ts,mock-data.ts,
constants.ts} and cross-checked the API normalization logic against two independent
real call sites: apps/web/components/memory-graph/hooks/use-graph-api.ts and
apps/memory-graph-playground/src/app/page.tsx.

- Exports section listed Legend, NodeDetailPanel, SpacesDropdown, useGraphInteractions,
  colors, LAYOUT_CONSTANTS - none of these are actually exported. Corrected to the
  real exports and added the previously-undocumented ./mock-data subpath.
- All examples used loadMoreDocuments/totalLoaded/selectedSpace/onSpaceChange/
  showSpacesSelector, none of which exist on MemoryGraphProps. Corrected to
  onLoadMore/totalCount and removed the Controlled Space Selection example
  (that feature no longer exists on the component).
- documents was typed as DocumentWithMemories[] with a fabricated shape; the real
  prop type is GraphApiDocument[]. Added a toGraphDocument mapping example grounded
  in the real production code that talks to /v3/documents/documents.
- Rewrote Props Reference and Data Types to match the real MemoryGraphProps and
  GraphApiDocument/GraphApiMemory interfaces field-for-field, including several
  real props that weren't documented at all before.
- Removed the false 'space selector visible/hidden' claim from the Variants section
  (no such component exists anywhere in packages/memory-graph/src); kept the 0.8x/0.5x
  zoom figures, which checked out against constants.ts.
- Made the Pages Router and Express tabs in Backend API Route self-contained (each
  defines its own toGraphDocument mapping) instead of depending on code shown only
  in the App Router tab, since CodeGroup tabs are alternatives a reader copies
  individually.
- Applied the identical corrections to apps/memory-graph-playground/README.md,
  which had the same drift independently.

Address Graphite automated review feedback:
- Set the content field in all three toGraphDocument mappings (was a valid but
  unpopulated field on GraphApiMemory). Confirmed via use-graph-data.ts that it's
  inert for rendering (always overwritten with mem.memory before draw), so this
  wasn't visible data loss, but it's a free fix that matches the type exactly.
- Replaced the (data.documents as RawDocument[]) cast with a proper
  RawDocumentsResponse type annotation on data in the App Router and Pages Router
  tabs, per the custom TypeScript style rule Graphite flagged. Implemented what
  the comment's prose described rather than its literal suggested diff, which
  would have just deleted the cast and left data as any.

Docs-only change, no .ts/.tsx files touched. This repo's biome.json has no
Markdown/MDX support configured, so format-lint doesn't apply here; checked
fence and MDX-component balance by hand instead (34 fence lines, all CodeGroup/
Note/Warning/Card tags balanced 1:1).
This commit is contained in:
Abhijeet Sharma 2026-08-05 20:55:50 +05:30
parent 570ed22b6c
commit 8d3f7ac8d6
2 changed files with 359 additions and 90 deletions

View file

@ -25,11 +25,11 @@ npm install @supermemory/memory-graph
'use client'; // For Next.js App Router
import { MemoryGraph } from '@supermemory/memory-graph';
import type { DocumentWithMemories } from '@supermemory/memory-graph';
import type { GraphApiDocument } from '@supermemory/memory-graph';
import { useEffect, useState } from 'react';
export default function GraphPage() {
const [documents, setDocuments] = useState<DocumentWithMemories[]>([]);
const [documents, setDocuments] = useState<GraphApiDocument[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
@ -59,15 +59,97 @@ export default function GraphPage() {
}
```
<Note>
`documents` must be `GraphApiDocument[]`, the shape the API route below returns, with `documentType` and `memories` fields (not the raw API's `type` and `memoryEntries`).
</Note>
## Backend API Route
Create an API route to fetch documents from Supermemory:
Create an API route to fetch documents from Supermemory. The `/v3/documents/documents` response uses `type` and `memoryEntries`; `<MemoryGraph>` expects `documentType` and `memories`, so the route normalizes the shape before returning it:
<CodeGroup>
```typescript Next.js App Router
// app/api/graph/route.ts
import { NextResponse } from 'next/server';
import type { GraphApiDocument, GraphApiMemory } from '@supermemory/memory-graph';
interface RawMemoryEntry {
id: string;
memory?: string | null;
content?: string | null;
spaceId?: string | null;
isStatic?: boolean;
isLatest?: boolean;
isForgotten?: boolean;
forgetAfter?: string | null;
forgetReason?: string | null;
version?: number;
parentMemoryId?: string | null;
rootMemoryId?: string | null;
createdAt: string;
updatedAt: string;
relation?: 'updates' | 'extends' | 'derives' | null;
updatesMemoryId?: string | null;
nextVersionId?: string | null;
memoryRelations?: Record<string, 'updates' | 'extends' | 'derives'> | null;
spaceContainerTag?: string | null;
}
interface RawDocument {
id: string;
title: string | null;
summary?: string | null;
type: string;
createdAt: string;
updatedAt: string;
memoryEntries: RawMemoryEntry[];
}
interface RawDocumentsResponse {
documents: RawDocument[];
pagination: {
currentPage: number;
limit: number;
totalItems: number;
totalPages: number;
};
}
// Maps the raw API document (`type` / `memoryEntries`) onto the shape
// <MemoryGraph> expects (`documentType` / `memories`), filling in the
// defaults for fields the API may omit.
function toGraphDocument(doc: RawDocument): GraphApiDocument {
return {
id: doc.id,
title: doc.title,
summary: doc.summary ?? null,
documentType: doc.type,
createdAt: doc.createdAt,
updatedAt: doc.updatedAt,
memories: doc.memoryEntries.map((mem): GraphApiMemory => ({
id: mem.id,
memory: mem.memory ?? mem.content ?? '',
content: mem.content ?? null,
isStatic: mem.isStatic ?? false,
spaceId: mem.spaceId ?? '',
isLatest: mem.isLatest ?? true,
isForgotten: mem.isForgotten ?? false,
forgetAfter: mem.forgetAfter ?? null,
forgetReason: mem.forgetReason ?? null,
version: mem.version ?? 1,
parentMemoryId: mem.parentMemoryId ?? null,
rootMemoryId: mem.rootMemoryId ?? null,
createdAt: mem.createdAt,
updatedAt: mem.updatedAt,
relation: mem.relation ?? null,
updatesMemoryId: mem.updatesMemoryId ?? null,
nextVersionId: mem.nextVersionId ?? null,
memoryRelations: mem.memoryRelations ?? null,
spaceContainerTag: mem.spaceContainerTag ?? null,
})),
};
}
export async function GET() {
const response = await fetch('https://api.supermemory.ai/v3/documents/documents', {
@ -84,14 +166,95 @@ export async function GET() {
}),
});
const data = await response.json();
return NextResponse.json(data);
const data: RawDocumentsResponse = await response.json();
return NextResponse.json({
documents: data.documents.map(toGraphDocument),
pagination: data.pagination,
});
}
```
```typescript Next.js Pages Router
// pages/api/graph.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import type { GraphApiDocument, GraphApiMemory } from '@supermemory/memory-graph';
interface RawMemoryEntry {
id: string;
memory?: string | null;
content?: string | null;
spaceId?: string | null;
isStatic?: boolean;
isLatest?: boolean;
isForgotten?: boolean;
forgetAfter?: string | null;
forgetReason?: string | null;
version?: number;
parentMemoryId?: string | null;
rootMemoryId?: string | null;
createdAt: string;
updatedAt: string;
relation?: 'updates' | 'extends' | 'derives' | null;
updatesMemoryId?: string | null;
nextVersionId?: string | null;
memoryRelations?: Record<string, 'updates' | 'extends' | 'derives'> | null;
spaceContainerTag?: string | null;
}
interface RawDocument {
id: string;
title: string | null;
summary?: string | null;
type: string;
createdAt: string;
updatedAt: string;
memoryEntries: RawMemoryEntry[];
}
interface RawDocumentsResponse {
documents: RawDocument[];
pagination: {
currentPage: number;
limit: number;
totalItems: number;
totalPages: number;
};
}
// Maps the raw API document (`type` / `memoryEntries`) onto the shape
// <MemoryGraph> expects (`documentType` / `memories`), filling in the
// defaults for fields the API may omit.
function toGraphDocument(doc: RawDocument): GraphApiDocument {
return {
id: doc.id,
title: doc.title,
summary: doc.summary ?? null,
documentType: doc.type,
createdAt: doc.createdAt,
updatedAt: doc.updatedAt,
memories: doc.memoryEntries.map((mem): GraphApiMemory => ({
id: mem.id,
memory: mem.memory ?? mem.content ?? '',
content: mem.content ?? null,
isStatic: mem.isStatic ?? false,
spaceId: mem.spaceId ?? '',
isLatest: mem.isLatest ?? true,
isForgotten: mem.isForgotten ?? false,
forgetAfter: mem.forgetAfter ?? null,
forgetReason: mem.forgetReason ?? null,
version: mem.version ?? 1,
parentMemoryId: mem.parentMemoryId ?? null,
rootMemoryId: mem.rootMemoryId ?? null,
createdAt: mem.createdAt,
updatedAt: mem.updatedAt,
relation: mem.relation ?? null,
updatesMemoryId: mem.updatesMemoryId ?? null,
nextVersionId: mem.nextVersionId ?? null,
memoryRelations: mem.memoryRelations ?? null,
spaceContainerTag: mem.spaceContainerTag ?? null,
})),
};
}
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const response = await fetch('https://api.supermemory.ai/v3/documents/documents', {
@ -103,8 +266,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
body: JSON.stringify({ page: 1, limit: 500, sort: 'createdAt', order: 'desc' }),
});
const data = await response.json();
res.json(data);
const data: RawDocumentsResponse = await response.json();
res.json({
documents: data.documents.map(toGraphDocument),
pagination: data.pagination,
});
}
```
@ -120,7 +286,41 @@ app.get('/api/graph', async (req, res) => {
});
const data = await response.json();
res.json(data);
// Map the raw API document (`type` / `memoryEntries`) onto the shape
// <MemoryGraph> expects (`documentType` / `memories`), filling in the
// defaults for fields the API may omit.
const documents = data.documents.map((doc) => ({
id: doc.id,
title: doc.title,
summary: doc.summary ?? null,
documentType: doc.type,
createdAt: doc.createdAt,
updatedAt: doc.updatedAt,
memories: doc.memoryEntries.map((mem) => ({
id: mem.id,
memory: mem.memory ?? mem.content ?? '',
content: mem.content ?? null,
isStatic: mem.isStatic ?? false,
spaceId: mem.spaceId ?? '',
isLatest: mem.isLatest ?? true,
isForgotten: mem.isForgotten ?? false,
forgetAfter: mem.forgetAfter ?? null,
forgetReason: mem.forgetReason ?? null,
version: mem.version ?? 1,
parentMemoryId: mem.parentMemoryId ?? null,
rootMemoryId: mem.rootMemoryId ?? null,
createdAt: mem.createdAt,
updatedAt: mem.updatedAt,
relation: mem.relation ?? null,
updatesMemoryId: mem.updatesMemoryId ?? null,
nextVersionId: mem.nextVersionId ?? null,
memoryRelations: mem.memoryRelations ?? null,
spaceContainerTag: mem.spaceContainerTag ?? null,
})),
}));
res.json({ documents, pagination: data.pagination });
});
```
@ -134,13 +334,13 @@ app.get('/api/graph', async (req, res) => {
## Variants
**Console Variant** - Full-featured dashboard view (0.8x zoom, space selector visible):
**Console Variant** - Full-featured dashboard view (0.8x initial zoom):
```tsx
<MemoryGraph documents={documents} variant="console" />
```
**Consumer Variant** - Embedded widget view (0.5x zoom, space selector hidden):
**Consumer Variant** - Embedded widget view (0.5x initial zoom):
```tsx
<MemoryGraph documents={documents} variant="consumer" />
@ -156,10 +356,11 @@ app.get('/api/graph', async (req, res) => {
'use client';
import { MemoryGraph } from '@supermemory/memory-graph';
import type { GraphApiDocument } from '@supermemory/memory-graph';
import { useCallback, useEffect, useState } from 'react';
export default function PaginatedGraph() {
const [documents, setDocuments] = useState([]);
const [documents, setDocuments] = useState<GraphApiDocument[]>([]);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const [isLoading, setIsLoading] = useState(true);
@ -193,8 +394,8 @@ export default function PaginatedGraph() {
isLoading={isLoading}
isLoadingMore={isLoadingMore}
hasMore={hasMore}
totalLoaded={documents.length}
loadMoreDocuments={loadMore}
totalCount={documents.length}
onLoadMore={loadMore}
/>
);
}
@ -210,17 +411,6 @@ export default function PaginatedGraph() {
/>
```
### Controlled Space Selection
```tsx
<MemoryGraph
documents={documents}
selectedSpace={selectedSpace}
onSpaceChange={setSelectedSpace}
showSpacesSelector={false}
/>
```
### Custom Empty State
```tsx
@ -240,80 +430,131 @@ export default function PaginatedGraph() {
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `documents` | `DocumentWithMemories[]` | required | Array of documents to display |
| `isLoading` | `boolean` | `false` | Shows loading indicator |
| `error` | `Error \| null` | `null` | Error to display |
| `documents` | `GraphApiDocument[]` | `[]` | Documents to display - pass this for direct data mode |
| `isLoading` | `boolean` | `false` | Whether data is loading |
| `error` | `Error \| null` | `null` | Error from data fetching |
| `variant` | `"console" \| "consumer"` | `"console"` | Visual variant |
| `children` | `ReactNode` | - | Custom empty state content |
| `children` | `ReactNode` | - | Rendered when there are no documents |
### Pagination Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `isLoadingMore` | `boolean` | `false` | Shows indicator when loading more |
| `hasMore` | `boolean` | `false` | Whether more documents available |
| `totalLoaded` | `number` | - | Total documents currently loaded |
| `loadMoreDocuments` | `() => Promise<void>` | - | Callback to load more |
| `autoLoadOnViewport` | `boolean` | `true` | Auto-load when 80% visible |
| `isLoadingMore` | `boolean` | `false` | Whether more data is being loaded |
| `hasMore` | `boolean` | `false` | Whether there are more documents to load |
| `onLoadMore` | `() => void` | - | Callback to load more documents |
| `totalCount` | `number` | - | Total count, used by the loading indicator |
### Display Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `showSpacesSelector` | `boolean` | variant-based | Show space filter dropdown |
| `highlightDocumentIds` | `string[]` | `[]` | Document IDs to highlight |
| `highlightsVisible` | `boolean` | `true` | Whether highlights shown |
| `occludedRightPx` | `number` | `0` | Pixels occluded on right |
| `highlightDocumentIds` | `string[]` | - | Document IDs to highlight |
| `highlightsVisible` | `boolean` | - | Whether highlights are visible |
| `legendId` | `string` | - | Optional legend element id |
| `showFps` | `boolean` | - | Show FPS counter overlay |
| `canvasRef` | `RefObject<HTMLCanvasElement \| null>` | - | External ref to the canvas (e.g. for screenshot export) |
| `colors` | `Partial<GraphThemeColors>` | - | Custom theme colors, merged with CSS-variable/default values |
| `labels` | `MemoryGraphLabels` | - | Custom user-facing labels, merged with defaults |
| `layering` | `MemoryGraphLayering` | - | Overlay layering controls (e.g. hover popover z-index) |
### Controlled State Props
### Filtering Props
| Prop | Type | Description |
|------|------|-------------|
| `selectedSpace` | `string` | Currently selected space (use `"all"` for all) |
| `onSpaceChange` | `(spaceId: string) => void` | Callback when space changes |
| `memoryLimit` | `number` | Max memories per document when space selected |
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `containerTags` | `string[]` | - | Container tags for filtering (used by apps with their own API hooks) |
| `documentIds` | `string[]` | - | Specific document IDs to show |
| `maxNodes` | `number` | - | Max nodes to display |
### Slideshow Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `isSlideshowActive` | `boolean` | - | Whether slideshow mode is active |
| `onSlideshowNodeChange` | `(nodeId: string \| null) => void` | - | Called as the slideshow moves between nodes |
| `onSlideshowStop` | `() => void` | - | Called when the slideshow ends |
### Other
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `onOpenDocument` | `(documentId: string) => void` | - | Called when the user wants to view a document's full content |
---
## Data Types
### DocumentWithMemories
### GraphApiDocument / GraphApiMemory
This is the shape `documents` actually takes: `documentType` and `memories`, matching what the API route in [Backend API Route](#backend-api-route) normalizes into:
```typescript
interface GraphApiDocument {
id: string;
title: string | null;
summary: string | null;
documentType: string;
createdAt: string;
updatedAt: string;
memories: GraphApiMemory[];
}
interface GraphApiMemory {
id: string;
memory: string;
content?: string | null;
isStatic: boolean;
spaceId: string;
isLatest: boolean;
isForgotten: boolean;
forgetAfter: string | null;
forgetReason: string | null;
version: number;
parentMemoryId: string | null;
rootMemoryId: string | null;
createdAt: string;
updatedAt: string;
relation?: 'updates' | 'extends' | 'derives' | null;
updatesMemoryId?: string | null;
nextVersionId?: string | null;
memoryRelations?: Record<string, 'updates' | 'extends' | 'derives'> | null;
spaceContainerTag?: string | null;
}
```
### DocumentWithMemories / MemoryEntry
Also exported for backward compatibility with older API response shapes. Not what `documents` takes today, but still useful for typing a raw fetch response before mapping it to `GraphApiDocument`:
```typescript
interface DocumentWithMemories {
id: string;
customId?: string | null;
title?: string | null;
content?: string | null;
title: string | null;
url: string | null;
documentType: string;
createdAt: string;
updatedAt: string;
summary?: string | null;
url?: string | null;
source?: string | null;
type?: string | null;
status: 'pending' | 'processing' | 'done' | 'failed';
metadata?: Record<string, string | number | boolean> | null;
createdAt: string | Date;
updatedAt: string | Date;
memoryEntries: MemoryEntry[];
memories: MemoryEntry[];
}
```
### MemoryEntry
```typescript
interface MemoryEntry {
id: string;
documentId: string;
content: string | null;
summary?: string | null;
title?: string | null;
type?: string | null;
metadata?: Record<string, string | number | boolean> | null;
createdAt: string | Date;
updatedAt: string | Date;
spaceContainerTag?: string | null;
relation?: 'updates' | 'extends' | 'derives' | null;
isLatest?: boolean;
memory: string;
content?: string | null;
createdAt: string;
updatedAt: string;
spaceId?: string | null;
isStatic?: boolean;
isForgotten?: boolean;
forgetAfter?: string | null;
forgetReason?: string | null;
version?: number;
parentMemoryId?: string | null;
rootMemoryId?: string | null;
isLatest?: boolean;
relation?: 'updates' | 'extends' | 'derives' | null;
spaceContainerTag?: string | null;
}
```
@ -324,26 +565,52 @@ interface MemoryEntry {
### Components
```typescript
import {
MemoryGraph,
GraphCanvas,
Legend,
LoadingIndicator,
NodeDetailPanel,
SpacesDropdown
} from '@supermemory/memory-graph';
import { MemoryGraph, GraphCanvas } from '@supermemory/memory-graph';
```
### Hooks
```typescript
import { useGraphData, useGraphInteractions } from '@supermemory/memory-graph';
import { useGraphData, useGraphTheme } from '@supermemory/memory-graph';
```
### Engine Classes
For advanced usage: driving the force simulation, viewport, or hit-testing directly:
```typescript
import {
ForceSimulation,
ViewportState,
SpatialIndex,
VersionChainIndex,
} from '@supermemory/memory-graph';
```
### Constants
```typescript
import { colors, GRAPH_SETTINGS, LAYOUT_CONSTANTS } from '@supermemory/memory-graph';
import {
DEFAULT_COLORS,
DEFAULT_HOVER_POPOVER_Z_INDEX,
DEFAULT_LABELS,
FORCE_CONFIG,
GRAPH_SETTINGS,
} from '@supermemory/memory-graph';
```
### Mock Data
`@supermemory/memory-graph/mock-data` generates fake `GraphApiDocument[]` data for local development and stress testing, without a live API key:
```typescript
import { generateMockGraphData } from '@supermemory/memory-graph/mock-data';
const { documents } = generateMockGraphData({
documentCount: 100,
memoriesPerDoc: [2, 5],
seed: 12345,
});
```
---

View file

@ -14,10 +14,10 @@ Open [http://localhost:3000](http://localhost:3000) and enter your Supermemory A
## Usage Example
```tsx
import { MemoryGraph, type DocumentWithMemories } from '@supermemory/memory-graph'
import { MemoryGraph, type GraphApiDocument } from '@supermemory/memory-graph'
function App() {
const [documents, setDocuments] = useState<DocumentWithMemories[]>([])
const [documents, setDocuments] = useState<GraphApiDocument[]>([])
return (
<MemoryGraph
@ -26,10 +26,9 @@ function App() {
isLoadingMore={false}
error={null}
hasMore={false}
loadMoreDocuments={() => {}}
totalLoaded={documents.length}
onLoadMore={() => {}}
totalCount={documents.length}
variant="console"
showSpacesSelector={true}
>
<div>No memories found</div>
</MemoryGraph>
@ -37,16 +36,19 @@ function App() {
}
```
See [`src/app/page.tsx`](./src/app/page.tsx) for the full working example, including converting a raw API response into `GraphApiDocument[]`.
## Props
| Prop | Type | Description |
|------|------|-------------|
| `documents` | `DocumentWithMemories[]` | Array of documents to display |
| `documents` | `GraphApiDocument[]` | Array of documents to display |
| `isLoading` | `boolean` | Initial loading state |
| `isLoadingMore` | `boolean` | Loading more documents state |
| `error` | `Error \| null` | Error to display |
| `hasMore` | `boolean` | Whether more documents can be loaded |
| `loadMoreDocuments` | `() => void` | Callback to load more documents |
| `totalLoaded` | `number` | Total number of loaded documents |
| `variant` | `"default" \| "console"` | Visual theme variant |
| `showSpacesSelector` | `boolean` | Show space filter dropdown |
| `onLoadMore` | `() => void` | Callback to load more documents |
| `totalCount` | `number` | Total number of loaded documents |
| `variant` | `"console" \| "consumer"` | Visual variant |
See [`packages/memory-graph`](../../packages/memory-graph/src/types.ts) for the full `MemoryGraphProps` reference.