164 lines
4.3 KiB
TypeScript
164 lines
4.3 KiB
TypeScript
/**
|
|
* Memory Card — Cross-module data interchange format.
|
|
*
|
|
* Any module can export items as Memory Cards; any module can import/reference them.
|
|
* Think of it as a universal "clip" format for data flowing between rSpace modules.
|
|
*/
|
|
|
|
import type { DocumentId } from './document';
|
|
|
|
// ============================================================================
|
|
// TYPES
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Core Memory Card — the universal interchange unit.
|
|
*/
|
|
export interface MemoryCard {
|
|
/** Unique ID (UUID v4 or module-specific) */
|
|
id: string;
|
|
/** Semantic type */
|
|
type: MemoryCardType | string;
|
|
/** Human-readable title */
|
|
title: string;
|
|
/** Optional body text (markdown) */
|
|
body?: string;
|
|
/** Source provenance */
|
|
source: {
|
|
module: string;
|
|
docId: DocumentId;
|
|
itemId?: string;
|
|
};
|
|
/** Freeform tags for filtering/grouping */
|
|
tags: string[];
|
|
/** Unix timestamp (ms) */
|
|
createdAt: number;
|
|
/** Module-specific structured data */
|
|
data?: Record<string, unknown>;
|
|
}
|
|
|
|
/**
|
|
* Well-known card types. Modules can extend with custom strings.
|
|
*/
|
|
export type MemoryCardType =
|
|
| 'note'
|
|
| 'task'
|
|
| 'event'
|
|
| 'link'
|
|
| 'file'
|
|
| 'vote'
|
|
| 'transaction'
|
|
| 'trip'
|
|
| 'contact'
|
|
| 'shape'
|
|
| 'book'
|
|
| 'product'
|
|
| 'post';
|
|
|
|
// ============================================================================
|
|
// HELPERS
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Create a Memory Card with defaults.
|
|
*/
|
|
export function createCard(
|
|
fields: Pick<MemoryCard, 'type' | 'title' | 'source'> & Partial<MemoryCard>
|
|
): MemoryCard {
|
|
return {
|
|
id: fields.id ?? generateId(),
|
|
type: fields.type,
|
|
title: fields.title,
|
|
body: fields.body,
|
|
source: fields.source,
|
|
tags: fields.tags ?? [],
|
|
createdAt: fields.createdAt ?? Date.now(),
|
|
data: fields.data,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Filter cards by type.
|
|
*/
|
|
export function filterByType(cards: MemoryCard[], type: string): MemoryCard[] {
|
|
return cards.filter((c) => c.type === type);
|
|
}
|
|
|
|
/**
|
|
* Filter cards by tag (any match).
|
|
*/
|
|
export function filterByTag(cards: MemoryCard[], tag: string): MemoryCard[] {
|
|
return cards.filter((c) => c.tags.includes(tag));
|
|
}
|
|
|
|
/**
|
|
* Filter cards by source module.
|
|
*/
|
|
export function filterByModule(cards: MemoryCard[], module: string): MemoryCard[] {
|
|
return cards.filter((c) => c.source.module === module);
|
|
}
|
|
|
|
/**
|
|
* Sort cards by creation time (newest first).
|
|
*/
|
|
export function sortByNewest(cards: MemoryCard[]): MemoryCard[] {
|
|
return [...cards].sort((a, b) => b.createdAt - a.createdAt);
|
|
}
|
|
|
|
/**
|
|
* Search cards by title/body text (case-insensitive substring match).
|
|
*/
|
|
export function searchCards(cards: MemoryCard[], query: string): MemoryCard[] {
|
|
const q = query.toLowerCase();
|
|
return cards.filter(
|
|
(c) =>
|
|
c.title.toLowerCase().includes(q) ||
|
|
(c.body && c.body.toLowerCase().includes(q))
|
|
);
|
|
}
|
|
|
|
// ============================================================================
|
|
// CARD EXPORTER INTERFACE
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Modules implement this to export their data as Memory Cards.
|
|
*/
|
|
export interface CardExporter {
|
|
module: string;
|
|
/** Export all items from a document as cards */
|
|
exportCards(docId: DocumentId, doc: any): MemoryCard[];
|
|
/** Export a single item as a card */
|
|
exportCard?(docId: DocumentId, doc: any, itemId: string): MemoryCard | null;
|
|
}
|
|
|
|
/**
|
|
* Registry of card exporters.
|
|
*/
|
|
const exporters = new Map<string, CardExporter>();
|
|
|
|
export function registerExporter(exporter: CardExporter): void {
|
|
exporters.set(exporter.module, exporter);
|
|
}
|
|
|
|
export function getExporter(module: string): CardExporter | undefined {
|
|
return exporters.get(module);
|
|
}
|
|
|
|
export function getAllExporters(): CardExporter[] {
|
|
return Array.from(exporters.values());
|
|
}
|
|
|
|
// ============================================================================
|
|
// UTILITIES
|
|
// ============================================================================
|
|
|
|
function generateId(): string {
|
|
// crypto.randomUUID() is available in all modern browsers and Bun
|
|
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
return crypto.randomUUID();
|
|
}
|
|
// Fallback
|
|
return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
}
|