faceless-photolib

@faceless-photolib/engine-api

The main public entry point for the faceless-photolib engine: open a document, edit its layer stack with named commands, and render/export through a pluggable backend (CPU reference today, GPU later).

The main public entry point for faceless-photolib — a headless, color-managed, GPU-accelerated image-editing engine.

createEngine() gives you the engine capability surface: open a stored document, apply named commands to its layer stack, render a viewport region on a backend, export the document to an encoded image, subscribe to committed changes, and run headless crop/transform interactions. Every operation returns a canonical Result/Resource and never throws across the boundary — each request is safeParsed before any side effect. The same surface is also exposed over a JSON-RPC wire via createWireTransport(). In this layer only the CPU reference backend is wired in-process; requesting "webgpu" or "webgl2" returns backend-unavailable (never blank or substituted pixels).

Install

pnpm add @faceless-photolib/engine-api

Usage

import { createEngine, type Command } from "@faceless-photolib/engine-api";

const engine = createEngine();

// Validate + load a stored document payload (versioned, migrated, asset-checked).
const opened = await engine.openDocument(storedDocumentJson);
if (opened.kind !== "ok") throw new Error(`open failed: ${opened.kind}`);
const doc = opened.value;

// Edit the layer stack with a named command (immer produces a fresh document value).
const setOpacity: Command = { op: "setOpacity", layerId, opacity: 0.5 };
const edited = await engine.applyCommand(doc.id, setOpacity);

// Render a viewport region on the CPU reference backend; observe via a Resource.
const render = engine.renderRegion(doc.id, { x: 0, y: 0, width: 512, height: 512, scale: 1 }, "cpu");
if (render.status === "ready") {
  // render.value is the RenderResult
}

// Export the full canvas to an encoded image.
const exported = await engine.exportImage(doc.id, { format: "png" });
if (exported.kind === "ok") {
  // exported.value is the encoded Uint8Array
}

API

ExportDescription
createEngine(deps?)Constructs the in-process engine. Optional EngineDeps inject a resolveSource, codec, assetStore, textRasterizer, or surface.
EngineThe capability surface: openDocument, applyCommand, renderRegion, exportImage, subscribe, beginInteraction.
EngineDepsOptional dependencies wired into the engine (source resolver, codec, asset store, text rasterizer, surface).
ChangeEvent / SubscriptionEvents on the subscribe stream (documentChanged / renderProgress) and the unsubscribe handle.
InteractionSessionA headless crop/transform/straighten/resize session from beginInteraction (state / send / commit / cancel).
Command / CommandSchemaThe named document op-set (addLayer, removeLayer, reorder, setBlendMode, setOpacity, setFill, transform, addAdjustment, group, clip, setMask) plus its Zod schema.
createWireTransport(engine, opts?)Wraps an Engine as a JSON-RPC wire transport (WireMessage in, WireResponse out).
WIRE_VERSION / WireMessage / TransportRequest (+ schemas)The wire protocol version and message/request envelopes.
ok / notFound / invalidRequest / validationError / rejected / conflict / backendUnavailableCanonical result constructors, re-exported from the authority.
Result / Resource / Issue / RejectReasonThe canonical result vocabulary types.

engine-api is the semantic authority for the canonical result vocabulary; the Zod shapes live in @faceless-photolib/schemas and are re-exported here so callers import them from one place.

License

MIT

API reference

24 public exports · 20 documented · generated from source.

createEnginefunction
createEngine(deps?: EngineDeps): Engine

Construct the in-process engine. Optional {@link EngineDeps} inject the source resolver / codec / surface; the zero-arg `createEngine()` call stays valid (an image render then fails loud for want of resolvable pixels, never faked).

createWireTransportfunction
createWireTransport(engine: Engine, options?: WireTransportOptions): WireTransport

Create the wire transport over an existing in-process engine. If no `onNotify` is supplied, change events for wire `subscribe`-d documents have no channel to be pushed through — so the default forwarder fires a `warnNotImplemented` beacon naming the missing server→client notification message (the frozen `WireMessage` schema defines none) rather than dropping the event silently. Listed in notImplementedRemaining as the wire-notification channel.

resolveImageSourcefunction
resolveImageSource(source: { t: "content"; ref: { algo: "b3"; digest: string & $brand<"HexDigest">; mediaType: string & $brand<"MediaType">; role: "master" | "preview" | "cache" | "document" | "history"; bytes: number; }; } | { ...; }, store: ContentStore | undefined): Promise<...>

Resolve a V2 image-layer source to its bytes (native-file-format D7: the content-store resolver of the asset-resolution port). - `{ t: "content" }` resolves through the injected `ContentStore` by blob address; a digest absent from the store is the canonical `not-found` NAMING the missing ref (`v1:b3:<hex>`), never a placeholder or empty asset. - `{ t: "external" }` has no engine-side fetcher in v0 — loud `not-implemented` with a beacon, never silently skipped.

BackendFactoryContextinterface
interface BackendFactoryContext

What a backend factory receives from the engine when its backend is first needed. Carries the engine's injected source resolver so the registered backend resolves the SAME pixels the rest of the engine does, and the injected text rasterizer so a registered backend that supports `text` passes (the CPU reference in Node) rasterizes through the SAME port `exportImage` always used (`undefined` when the host injected none — a source/mask/text pass then fails loud, exactly as before registration existed).

Engineinterface
interface Engine

The engine capability surface. Identical in-process and over JSON-RPC. Every operation returns a canonical `Result`/`Resource`, never throws across the boundary, and validates its request with `safeParse` before any side effect.

EngineCacheStatsinterface
interface EngineCacheStats

Statistics exposed for tests (render-pipeline-caching spec: observability).

EngineDepsinterface
interface EngineDeps

Dependencies the in-process engine wires together. All are optional so the frozen zero-arg `createEngine()` call sites stay source-compatible (mirroring `backend-cpu`'s `createBackend(resolveSource?)` — the sanctioned injection pattern for inputs the frozen signatures cannot carry). The frozen `GpuBackend` port and the `Engine` surface carry no asset-bytes ingestion point (CAS references assets by hash), and `codec.decode` is async while the CPU `SourceResolver` is synchronous — so source pixels must be injected up front. With no resolver, an image-layer render fails loud (`error`) exactly like `unresolvedSource()`, never fabricated pixels.

EngineRenderResultinterface
interface EngineRenderResult

A render outcome as the engine surfaces it: the backend's pixels stamped with the registered kind that ACTUALLY produced them (gpu-preview-pipeline D2/D3: dispatch is explicit, never substituted, and the active backend is observable). The engine is the single construction point of the stamp — a backend cannot claim another kind's identity.

InteractionSessioninterface
interface InteractionSession

An in-progress interaction session opened by `beginInteraction`.

Subscriptioninterface
interface Subscription

Unsubscribe handle returned by `subscribe`.

WireTransportinterface
interface WireTransport

The wire transport: dispatch an untrusted message against the wrapped engine.

WireTransportOptionsinterface
interface WireTransportOptions

Options for {@link createWireTransport}.

BackendFactorytype
type BackendFactory

Constructs the backend for a registered kind. Called lazily on the FIRST render that requests the kind and memoized for the life of the engine (or until the kind is re-registered) — backend construction is part of the render-pipeline cache, not per-frame work.

ChangeEventtype
type ChangeEvent

A change event emitted on the `subscribe` stream.

Commandtype
type Command
InsertPositiontype
type InsertPosition
TransportRequesttype
type TransportRequest
WireMessagetype
type WireMessage
WireResponsetype
type WireResponse

A wire-dispatch outcome. Synchronous operations (`renderRegion`, `subscribe`, `beginInteraction`) and async ones (`openDocument`, `applyCommand`, `exportImage`) all flow through a single `Promise`-returning dispatch, so the transport surface is uniform. The payload is the canonical `Result`/`Resource` the matching `Engine` method returns; a session/ subscription handle is non-serializable, so the wire transport reports the establishment outcome as a canonical `Result` carrying the live handle for the in-process embedder (a network transport would substitute a token).

CommandSchemaconst
const CommandSchema

The named command op-set as a discriminated union keyed on `op`, mirroring document-model's `DocumentOp` 1:1 (engine-api spec: "ApplyCommand Exposes the Named Document Op-Set" originally listed 11 ops). The original frozen stub carried only the seven scalar ops whose payloads need no full `Layer`/`InsertPosition` shape; the four structural ops (`addLayer`, `reorder`, `addAdjustment`, `group`) were added so the typed `Command` covers the whole spec op-set and the acceptance test's `applyCommand(addLayer)` is expressible; `setAdjustment`/`setCanvasSize` followed for the interactive editor; `setVisible`/`renameLayer`/`setLocks`/ `setIsolation` (layer-props-editing) round out the layer-property surface. Each addition leaves every prior constructor unchanged.

InsertPositionSchemaconst
const InsertPositionSchema

Where to insert a layer relative to the stack/group (index 0 = bottom). The Zod mirror of document-model's `InsertPosition` so the structural-add ops (`addLayer`, `addAdjustment`) and `reorder` carry a `safeParse`-able target.

TransportRequestSchemaconst
const TransportRequestSchema

The full request union accepted at a transport boundary.

WIRE_VERSIONconst
WIRE_VERSION: 1

The wire protocol version this build advertises.

WireMessageSchemaconst
const WireMessageSchema

An envelope wrapping a request with the wire version for negotiation.