/** * Universal creation-log enumerators. * * Where `MarkwhenSourceFactory` projects *scheduled* events (when will X * happen), a `CreationEnumerator` projects *creations* (when did X come * into being). Every rApp that stores CRDT records with a `createdAt` * field registers one here — and automatically appears in rPast, the * unified chronicle-of-self viewer. * * The contract is deliberately minimal: the enumerator walks whichever * docs it owns and emits one `Creation` per record. The meta-adapter * below turns the merged stream into a single `MwSource` per module. */ import type { MwSource, MwEvent } from './types'; export interface Creation { /** Unix ms — when the record was created. Required. */ createdAt: number; /** Unix ms — last edit. Omit if never updated. */ updatedAt?: number; /** Single-line display name. */ title: string; /** Record-type label (e.g. "event", "note", "task", "vote"). Used as a sub-tag. */ recordType: string; /** Stable ID within the module (prefix not required; registry prefixes with module). */ recordId: string; /** Optional deep-link back to the canonical record. */ href?: string; /** Extra per-record tags (status, category, etc.). */ tags?: string[]; /** Short body shown indented under the event. */ description?: string; } export interface CreationEnumerator { /** Module id, e.g. "rcal". */ module: string; /** Human label shown in the rPast chip bar / filter UI. */ label: string; /** Emoji for chip + section header. */ icon: string; /** Color for the module tag in frontmatter. */ color?: string; /** Walk owned docs and emit one Creation per record. */ enumerate(ctx: { space: string; from?: number; to?: number }): Promise; } const enumerators: Map = new Map(); export function registerCreationEnumerator(e: CreationEnumerator): void { enumerators.set(e.module, e); } export function listCreationEnumerators(): CreationEnumerator[] { return [...enumerators.values()]; } export async function enumerateCreations( space: string, opts: { modules?: string[]; from?: number; to?: number } = {}, ): Promise { const chosen = opts.modules ? opts.modules.map(m => enumerators.get(m)).filter((e): e is CreationEnumerator => !!e) : [...enumerators.values()]; const sources: MwSource[] = []; for (const e of chosen) { try { const creations = await e.enumerate({ space, from: opts.from, to: opts.to }); if (creations.length === 0) continue; const events: MwEvent[] = creations.map(c => ({ start: c.createdAt, // A creation is a point in time by default. If updatedAt is // meaningfully later, span it so the timeline can show the // "alive" range of the record. end: c.updatedAt && c.updatedAt - c.createdAt > 24 * 3600_000 ? c.updatedAt : undefined, title: `${c.title}`, description: c.description, tags: [c.recordType, ...(c.tags ?? [])], href: c.href, sourceId: `${e.module}:${c.recordType}:${c.recordId}`, })); sources.push({ id: e.module, label: `${e.icon} ${e.label}`, tag: e.module, color: e.color, events, }); } catch (err) { console.warn(`[rpast] enumerator ${e.module} failed:`, err); } } return sources; }