@elacity-js/ddrm-reader-ir-schema
v0.2.1
Published
Render-IR contract shared by backend and ui. The only data shape crossing the trust boundary.
Downloads
122
Readme
@elacity-js/ddrm-reader-ir-schema
The contract of the dDRM reader. It defines the render-IR — the decoded, displayable shape the backend streams to the client — plus the channel messages, typed error results, and the codec interface. Every other package (backend, UI, every decoder) depends on this one, and on nothing of each other.
Workflow role
encrypted bytes ──▶ backend (decrypt + decode) ──▶ render-IR ──▶ UI renders
▲
defined hereRender-IR is the only data shape that crosses the trust boundary. It carries no path back to the encrypted file or the content key. The client dispatches on the IR kind tag — never on the original mime — which is what keeps the client content-agnostic. Changing anything here is a breaking change for both sides; version it deliberately.
Install
pnpm add @elacity-js/ddrm-reader-ir-schemaThe render-IR union
RenderIr is a tagged union — dispatch on kind:
| kind | Type | Produced for | Client renders with |
| ----------------- | --------------- | ------------------------------------- | -------------------------- |
| raster-tile-set | RasterTileSet | images, comics, font specimens | canvas/WebGL pan-zoom |
| text-doc | TextDoc | text/code/markdown, archive listings | sanitized HTML in the DOM |
| mesh-set | MeshSet | 3D models | three.js |
| doc-file | DocFile | PDF / EPUB | pdf.js / epub.js |
import type { RenderIr } from "@elacity-js/ddrm-reader-ir-schema";
function describe(ir: RenderIr): string {
switch (ir.kind) {
case "raster-tile-set":
return `${ir.pages.length} page(s)`;
case "text-doc":
return `${ir.documents.length} sanitized document(s)`;
case "mesh-set":
return `3D model (${ir.format})`;
case "doc-file":
return `${ir.format.toUpperCase()} document`;
}
}Typed errors
Every failure crosses the channel as a DdrmErrorResult — never a silent fallback.
import { DdrmError } from "@elacity-js/ddrm-reader-ir-schema";
throw new DdrmError("unsupported_type", "no decoder for application/x-foo");
// codes: access_denied | decrypt_failed | unsupported_type
// | session_invalid | session_expired | ipfs_unavailable
const result = new DdrmError("access_denied", "not the owner").toResult();
// → { kind: "error", code: "access_denied", message: "not the owner" }Channel codec
Render-IR holds typed arrays (Uint8Array) that don't survive plain JSON, so the wire format goes through a ChannelCodec. Backend and UI share this one interface.
import { createChannelCodec } from "@elacity-js/ddrm-reader-ir-schema";
const codec = createChannelCodec();
const frame = { kind: "render-ir", requestId, ir, final: true } as const;
const bytes = codec.encode(frame); // → Uint8Array (over the wire)
const message = codec.decode(bytes); // ← back to a ChannelMessageBase64 helpers for binary fields are also exported (encodeBase64 / decodeBase64).
