@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-apiUsage
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
| Export | Description |
|---|---|
createEngine(deps?) | Constructs the in-process engine. Optional EngineDeps inject a resolveSource, codec, assetStore, textRasterizer, or surface. |
Engine | The capability surface: openDocument, applyCommand, renderRegion, exportImage, subscribe, beginInteraction. |
EngineDeps | Optional dependencies wired into the engine (source resolver, codec, asset store, text rasterizer, surface). |
ChangeEvent / Subscription | Events on the subscribe stream (documentChanged / renderProgress) and the unsubscribe handle. |
InteractionSession | A headless crop/transform/straighten/resize session from beginInteraction (state / send / commit / cancel). |
Command / CommandSchema | The 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 / backendUnavailable | Canonical result constructors, re-exported from the authority. |
Result / Resource / Issue / RejectReason | The 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.
createEnginefunctioncreateEngine(deps?: EngineDeps): EngineConstruct 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).
createWireTransportfunctioncreateWireTransport(engine: Engine, options?: WireTransportOptions): WireTransportCreate 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.
resolveImageSourcefunctionresolveImageSource(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.
BackendFactoryContextinterfaceinterface BackendFactoryContextWhat 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).
Engineinterfaceinterface EngineThe 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.
EngineCacheStatsinterfaceinterface EngineCacheStatsStatistics exposed for tests (render-pipeline-caching spec: observability).
EngineDepsinterfaceinterface EngineDepsDependencies 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.
EngineRenderResultinterfaceinterface EngineRenderResultA 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.
InteractionSessioninterfaceinterface InteractionSessionAn in-progress interaction session opened by `beginInteraction`.
Subscriptioninterfaceinterface SubscriptionUnsubscribe handle returned by `subscribe`.
WireTransportinterfaceinterface WireTransportThe wire transport: dispatch an untrusted message against the wrapped engine.
WireTransportOptionsinterfaceinterface WireTransportOptionsOptions for {@link createWireTransport}.
BackendFactorytypetype BackendFactoryConstructs 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.
ChangeEventtypetype ChangeEventA change event emitted on the `subscribe` stream.
Commandtypetype CommandInsertPositiontypetype InsertPositionTransportRequesttypetype TransportRequestWireMessagetypetype WireMessageWireResponsetypetype WireResponseA 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).
CommandSchemaconstconst CommandSchemaThe 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.
InsertPositionSchemaconstconst InsertPositionSchemaWhere 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.
TransportRequestSchemaconstconst TransportRequestSchemaThe full request union accepted at a transport boundary.
WIRE_VERSIONconstWIRE_VERSION: 1The wire protocol version this build advertises.
WireMessageSchemaconstconst WireMessageSchemaAn envelope wrapping a request with the wire version for negotiation.