Quickstart
This creates an App, a public route, and one Release whose artifact is a small HTML page, then promotes the Release and reads the page back through the route. It runs in Node with the in-memory store and a ten-line artifact reader:
import { createHash } from "node:crypto";import { createAppReleaseCatalog, parseAppId, parseArtifactRef, parseIdempotencyKey, parsePublicSurfaceId, parseReleaseId, parseSha256Digest, parseSourceRevision, parseStableRoute, type ArtifactMediaType, type ArtifactReader, type ArtifactRef,} from "@fungi.computer/app-release";import { createDeterministicMemoryAppReleaseStore } from "@fungi.computer/app-release/effect";
const sha256 = (bytes: Uint8Array) => parseSha256Digest( `sha256:${createHash("sha256").update(bytes).digest("hex")}`, );
// Two artifacts: the HTML page, and the App root that points at it.const html = new TextEncoder().encode("<!doctype html><h1>Hello</h1>");const htmlRef = parseArtifactRef("artifact:hello.ui");const root = new TextEncoder().encode( JSON.stringify({ format: "fungi-app-v1", ui: { artifactRef: htmlRef, digest: sha256(html), version: "v1", byteLength: html.byteLength, mediaType: "text/html; charset=utf-8", }, }),);const rootRef = parseArtifactRef("artifact:hello.release");
// A tiny artifact reader: look the bytes up, hash them, compare digests.const files = new Map< ArtifactRef, { bytes: Uint8Array; mediaType: ArtifactMediaType }>([ [htmlRef, { bytes: html, mediaType: "text/html; charset=utf-8" }], [rootRef, { bytes: root, mediaType: "application/vnd.fungi.app+json" }],]);const artifactReader: ArtifactReader = { read: async ({ artifactRef, expectedDigest }) => { const file = files.get(artifactRef); if (file === undefined) return { kind: "missing" }; const digest = sha256(file.bytes); if (digest !== expectedDigest) return { kind: "digest-mismatch", actualDigest: digest }; return { kind: "verified", digest, mediaType: file.mediaType, byteLength: file.bytes.byteLength, bytes: { byteLength: file.bytes.byteLength, copy: () => file.bytes.slice(), }, }; },};
const catalog = createAppReleaseCatalog({ artifactReader, store: createDeterministicMemoryAppReleaseStore(),});
const appId = parseAppId("app_hello");const publicSurfaceId = parsePublicSurfaceId("ps_hello");const releaseId = parseReleaseId("rel_hello1");const route = parseStableRoute("/hello");
await catalog.createApp({ appId });await catalog.createPublicSurface({ publicSurfaceId, appId, stableRoute: route,});await catalog.createRelease({ releaseId, appId, artifact: { artifactRef: rootRef, artifactDigest: sha256(root), provenance: { sourceRevision: parseSourceRevision(`git:${"a".repeat(40)}`), sourceTreeDigest: sha256(new TextEncoder().encode("source tree")), buildRecipeDigest: sha256(new TextEncoder().encode("build recipe")), }, },});await catalog.verifyReleaseArtifact(releaseId);await catalog.transition({ kind: "promotion", publicSurfaceId, releaseId, idempotencyKey: parseIdempotencyKey("idem_hello-promote-1"),});
const website = await catalog.resolvePublicWebsite(route);console.log(new TextDecoder().decode(website.bytes.copy()));// <!doctype html><h1>Hello</h1>