Effing 0.40.0: render, hold the server
We've added a render command that turns a fn into a file with zero setup, a canonical annie reader, and instance-scoped fn runtimes.
Effing 0.40.0 is out. The headline is a new CLI command, effing render,
that turns a fn into a file with one command and zero setup.
Render straight to a file
Until now, getting a fn’s output onto disk meant having things running:
start effing dev and save what the preview serves, or mint a signed URL
with effing url — which requires a SECRET_KEY in your env — and fetch
it. Fine once a project is set up, but a lot of ceremony when you just want
to see the thing.
Now this does it all in one shot:
npx --no effing render effie my-video -o out.mp4
Under the hood it spins up an ephemeral dev server on a free loopback port,
resolves the fn with its previewProps (or whatever you pass via
--props), writes the artifact, and tears the server down again. Images
come out as PNG or JPEG following the fn’s encoding, annies as a TAR of
frames, and effies as an MP4 — that last one delegates to the project-local
ffs render, so effies need @effing/ffs installed.
The zero-setup part extends to secrets. A missing SECRET_KEY is replaced
by a throwaway key for the duration of the render, and effing dev now
does the same, so neither command needs any env setup at all. The only cost
in dev is that copied URLs don’t survive a restart, which the startup log
calls out. effing url and the built production server still require a
configured key, since their URLs must stay valid beyond a single process.
If you point coding agents at your effing project, this is the command to
teach them: it’s the zero-setup way to check what a fn actually produces,
and effing manual now says exactly that.
One annie reader, owned by the format
An annie is a TAR archive of image frames, and @effing/annie has always
known how to write one. Reading one back was another story: the package
that owns the format had no reader, so @effing/ffs hand-rolled one on
tar-stream and @effing/annie-player hand-rolled another on untar.js,
each re-deriving the frame naming and ordering rules on its own.
0.40.0 adds annieFrames(), a canonical reader that async-iterates the
frames of an annie:
import { annieFrames } from "@effing/annie";
for await (const frame of annieFrames(source)) {
console.log(frame.index, frame.contentType, frame.data.length);
}
The source can be bytes, an (async) iterable of byte chunks — a Node.js
Readable works as-is — or a Web ReadableStream. Frames arrive in
ascending index order, each carrying its raw bytes and a content type
sniffed from the frame’s magic bytes, since the archive itself doesn’t
record the image format. Malformed archives — truncated, corrupt
checksums, no frames at all — are rejected with clear errors instead of
producing half an animation.
Both former hand-rolled readers are gone. For the player this is more than
a cleanup: the archive now streams from the response body instead of being
buffered whole, frames are ordered by their actual index rather than a name
sort (which mis-ordered unpadded names), and each frame’s Blob carries its
sniffed content type. In ffs, entry names are now validated against the
canonical frame_<digits> pattern before being used as filenames, closing
a path-traversal window for crafted archives.
Instance-scoped fn runtimes
@effing/fn kept its module loader and URL builder in module-global state
set by initFnRuntime(), so a process could hold exactly one fn runtime
configuration — hidden state that parallel tests and multi-tenant servers
had to serialize around.
The new createFnRuntime() returns a handle that captures its loader and
builder in a closure, so independent runtimes can coexist in one process:
import { createFnRuntime } from "@effing/fn";
const runtime = createFnRuntime({ moduleLoader, urlBuilder });
const module = await runtime.fnModule("image", "my-image");
The global API — initFnRuntime(), fnModule(), fnUrl() and friends —
is unchanged and now delegates to a global runtime under the hood, so
existing code keeps working as-is.
Upgrading
Bump your @effing/* packages to 0.40.0. Everything above is additive
or backward compatible, with one edge: the annie player now accepts only
canonical annies, so archives with entries not named frame_<digits> are
rejected. If your annies come from @effing/annie — and they almost
certainly do — nothing changes. The full per-package changelogs are in
the release PR.