Something went wrong. Try again.
A local-first event pipeline for independent agents, built on Jazz.
Something went wrong. Try again.
5.1 kB · 62 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263import type { JsonObject } from "../core/json.js";import type { ThoughtEvent } from "../events/types.js";import type { JazzThoughtStore } from "../jazz/store.js";import { readVerifiedBlob } from "./blob.js";import type { ArtifactBlobReference, ArtifactMediaType, ArtifactPayload } from "./types.js";import { ARTIFACT_EVENT_TYPE } from "./types.js";
export interface ArtifactCatalogEntry { eventId: string; artifactId: string; artifactVersion: number; kind: string; title: string; summary: string; mediaType: string; bodySha256: string; bodySizeBytes: number; blobPath: string; provenance: { source: string; label: string }; relations: Array<{ type: string; eventId: string }>; visibility: string; publicationEligible: boolean; superseded: boolean; supersedesArtifactEventId?: string; occurredAt: string; source: string; actor: string; privacy: string;}export interface ArtifactCatalogProjection { total: number; active: number; superseded: number; entries: ArtifactCatalogEntry[] }export type ArtifactDetail = { event: ThoughtEvent; bodySha256: string; byteCount: number; mediaType: ArtifactMediaType; text?: string; contentPath?: string };
export async function buildArtifactCatalog(store: JazzThoughtStore): Promise<ArtifactCatalogProjection> { const events = await store.listEvents({ types: [ARTIFACT_EVENT_TYPE] }); const supersededIds = new Set(events.map((event) => optionalString(event.payload.supersedesArtifactEventId)).filter(Boolean) as string[]); const entries = events.map((event) => entryFromEvent(event, supersededIds.has(event.id))).sort((a, b) => a.artifactId.localeCompare(b.artifactId) || a.artifactVersion - b.artifactVersion || a.eventId.localeCompare(b.eventId)); return { total: entries.length, active: entries.filter((entry) => !entry.superseded).length, superseded: entries.filter((entry) => entry.superseded).length, entries };}export async function rebuildArtifactCatalog(store: JazzThoughtStore): Promise<ArtifactCatalogProjection> { const catalog = await buildArtifactCatalog(store); await store.upsertProjection({ id: "artifact-catalog", payload: JSON.parse(JSON.stringify(catalog)) as JsonObject, lastEventId: catalog.entries.at(-1)?.eventId ?? "none", projectionVersion: 1, updatedAt: new Date().toISOString() }); return catalog;}export async function getArtifactCatalog(store: JazzThoughtStore): Promise<ArtifactCatalogProjection> { const projection = await store.getProjection("artifact-catalog"); const payload = projection?.payload; if ( projection?.projectionVersion === 1 && payload && Number.isSafeInteger(payload.total) && Number.isSafeInteger(payload.active) && Number.isSafeInteger(payload.superseded) && Array.isArray(payload.entries) ) return payload as unknown as ArtifactCatalogProjection; return rebuildArtifactCatalog(store);}export async function getArtifactBody(store: JazzThoughtStore, eventId: string): Promise<ArtifactDetail | undefined> { const resolved = await getArtifactContent(store, eventId); if (!resolved) return undefined; const { event, bytes, payload } = resolved; return payload.mediaType.startsWith("image/") ? { event, bodySha256: payload.blob.sha256, byteCount: bytes.byteLength, mediaType: payload.mediaType, contentPath: `/api/artifacts/${encodeURIComponent(event.id)}/content` } : (() => { const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes); return { event, bodySha256: payload.blob.sha256, byteCount: bytes.byteLength, mediaType: payload.mediaType, text }; })();}export async function getArtifactContent(store: JazzThoughtStore, eventId: string): Promise<{ event: ThoughtEvent; payload: ArtifactPayload; bytes: Buffer } | undefined> { const event = await store.getEvent(eventId); if (!event || event.type !== ARTIFACT_EVENT_TYPE) return undefined; const payload = event.payload as unknown as ArtifactPayload; const bytes = await readVerifiedBlob(store.getArtifactRoot(), payload.blob as ArtifactBlobReference, payload.mediaType as ArtifactMediaType); return { event, payload, bytes };}function entryFromEvent(event: ThoughtEvent, superseded: boolean): ArtifactCatalogEntry { const payload = event.payload as unknown as ArtifactPayload; return { eventId: event.id, artifactId: payload.artifactId, artifactVersion: payload.artifactVersion, kind: payload.kind, title: payload.title, summary: payload.summary, mediaType: payload.mediaType, bodySha256: payload.blob.sha256, bodySizeBytes: payload.blob.byteCount, blobPath: payload.blob.relativePath, provenance: payload.provenance, relations: payload.relations, visibility: payload.visibility, publicationEligible: payload.publicationEligible, superseded, ...(payload.supersedesArtifactEventId ? { supersedesArtifactEventId: payload.supersedesArtifactEventId } : {}), occurredAt: event.occurredAt, source: event.source, actor: event.actor, privacy: event.privacy };}function optionalString(value: unknown): string | undefined { return typeof value === "string" && value.length > 0 ? value : undefined }