npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rivet-dev/dynamic-apps

v0.3.1

Published

Build and run user-generated HTTP applications in agentOS with durable Rivet state.

Downloads

1,101

Readme

@rivet-dev/dynamic-apps

This package is the batteries-included, Rivet-backed adapter for Dynamic Apps. For storage you control, use @rivet-dev/dynamic-apps-core.

Dynamic Apps exposes three runtime values:

  • deployApp(input, options?) builds, persists, and activates an immutable app release through the per-app Rivet state actor.
  • appsRouter is a Hono router that serves the active release at /:appId/* through cached agentOS VMs.
  • setDynamicAppsLogHandler(handler) receives structured application, actor, build, and runtime logs.

Deployment builds through the storage-independent core, then the adapter stores the AOSP artifact through its per-app Rivet actor with atomic activation, rollback behavior, and a releaseActivated event. After the first load, a cache-hit request makes no actor or storage call.

Direct HTTP contract

An app must default-export an object with fetch(request) or a fetch function:

export default {
	async fetch(request: Request): Promise<Response> {
		return Response.json({ path: new URL(request.url).pathname });
	},
};

The first preview buffers request and response bodies. Direct apps run in a Node 22 agentOS sandbox, so supported Node builtins and the standard Node Web APIs are available. Native addons, WebSockets, and streaming ordinary HTTP bodies are not supported.

App-defined actors

An app that declares rivetkit mounts the registry handler in its exported fetch router. Dynamic Apps owns the HTTP listener and uses the same cached agentOS process for ordinary routes and Rivet callbacks:

import { actor, setup } from "rivetkit";
import { Hono } from "hono";

const counter = actor({
	state: { value: 0 },
	actions: {
		increment(c) {
			return ++c.state.value;
		},
	},
});

const registry = setup({ use: { counter } });
const app = new Hono();
app.all("/api/rivet/*", (c) => registry.handler(c.req.raw));
app.all("*", () => new Response("ok"));
export default app;

Do not call serve(), registry.start(), or bind a port in application code. Dynamic Apps starts the platform listener and waits for its listening callback; it does not poll an application health route.

Use the unchanged deployApp result to create the app client:

import { createClient } from "rivetkit/client";

const deployment = await deployApp({ appId: "counter", source: appSource });
const client = createClient({
	endpoint: deployment.endpoint,
	namespace: deployment.namespace,
	poolName: deployment.pool,
	token: deployment.token,
});

Every app is deployed to its own stable Rivet namespace. On Rivet Cloud, set RIVET_CLOUD_TOKEN to a server-side cloud_api_* project token. Dynamic Apps uses it to provision the namespace and namespace-scoped access, secret, and publishable credentials. The management credential is never returned or passed to app code; deployApp() returns only the app's publishable token.

The deploy CLI's cached management credential is not injected into the app. Pass the token as a server-side Compute environment variable:

npx @rivetkit/cli deploy \
  --namespace <namespace> \
  --env PORT=3000 \
  --env RIVET_CLOUD_TOKEN="$RIVET_CLOUD_TOKEN"

By default, the app actor callback enters through the host app actor's Rivet gateway and authenticates with the publishable credential from RIVET_PUBLIC_ENDPOINT. This keeps the child app namespace separate from the host registry namespace. DYNAMIC_APPS_CALLBACK_URL is only for a custom receiver that accepts child-namespace lifecycle callbacks; Dynamic Apps appends /api/rivet to that origin.

Actor requests follow the normal Rivet Engine path. The app's serverless callback mounts its verified artifact in agentOS and runs the bundled RivetKit WebAssembly runtime in serverless mode. State, actions, events, connections, and streaming actor responses are handled by RivetKit inside the sandbox. The same listener serves the app's ordinary HTTP routes without importing the app a second time.

Rivet Engine requires /api/rivet/start to return a long-lived SSE control stream containing the real runner-init packet and keepalive pings. Actor traffic does not travel in this response; it uses RivetKit's outbound WebSocket. Dynamic Apps forwards the server's response with agentOS's native VM HTTP stream, including backpressure and cancellation. No app code executes in the host.

Dynamic Apps does not impose a separate actor-callback concurrency gate. The configured actor runtime count controls retained idle entries only; active AgentOS runtimes are not rejected or evicted because that target is full.

Host integration

Mount application traffic and the private Rivet callback separately:

import { serve } from "@hono/node-server";
import { appsRouter } from "@rivet-dev/dynamic-apps";
import { Hono } from "hono";

const server = new Hono();

server.all("/api/rivet/*", (c) => appsRouter.fetch(c.req.raw));
server.route("/apps", appsRouter);

serve({ fetch: server.fetch, port: 3000 });

Run Node 22 or newer normally:

node dist/server.js

Deploying an app keeps the existing call shape:

import { deployApp } from "@rivet-dev/dynamic-apps";

const deployment = await deployApp({
	appId: "hello-world",
	source: new URL("./generated-app/", import.meta.url),
});

Execution modes

DYNAMIC_APPS_EXECUTION_MODE selects the request execution strategy:

These modes apply to apps without RivetKit actors. Actor-enabled apps use one cached server process for both ordinary HTTP and Rivet callbacks.

| Mode | Cached object | Cache-hit request | | --- | --- | --- | | pooled (default) | verified artifact, one agentOS VM, and up to N retained contexts | lease a context, evaluate once, reset and reinitialize it, then return it to the pool | | ephemeral | verified artifact and one agentOS VM | evaluate in a fresh context managed by agentOS |

The context pool is a cache, not a capacity limit. A burst beyond the pool uses ephemeral evaluations under the global execution limit; only the configured number of retained contexts remain idle afterward.

| Variable | Default | | --- | ---: | | DYNAMIC_APPS_CONTEXT_POOL_SIZE | 2 | | DYNAMIC_APPS_CONTEXT_POOL_MAX_TOTAL | 8 | | DYNAMIC_APPS_CONTEXT_IDLE_TTL_MS | 30000 | | DYNAMIC_APPS_CONTEXT_HEAP_LIMIT_MB | 64 | | DYNAMIC_APPS_RUNTIME_CACHE_MAX_ENTRIES | 16 | | DYNAMIC_APPS_RUNTIME_CACHE_MAX_BYTES | 268435456 | | DYNAMIC_APPS_RUNTIME_CACHE_IDLE_TTL_MS | 900000 | | DYNAMIC_APPS_MEMORY_HIGH_WATER_PERCENT | 70 | | DYNAMIC_APPS_EXECUTION_CONCURRENCY | available CPU count | | DYNAMIC_APPS_EXECUTION_QUEUE_SIZE | 64 | | DYNAMIC_APPS_EXECUTION_QUEUE_WAIT_MS | 5000 | | DYNAMIC_APPS_EXECUTION_TIMEOUT_MS | 30000 | | DYNAMIC_APPS_ACTOR_WORKER_MAX_ENTRIES | 4 | | DYNAMIC_APPS_ACTOR_WORKER_HEAP_LIMIT_MB | 96 | | DYNAMIC_APPS_ACTOR_WORKER_START_TIMEOUT_MS | 30000 | | DYNAMIC_APPS_ACTOR_WORKER_IDLE_TTL_MS | 30000 | | DYNAMIC_APPS_ACTOR_START_PAYLOAD_MAX_BYTES | 16777216 | | DYNAMIC_APPS_ACTOR_REQUEST_TIMEOUT_MS | 30000 |

Inside a finite cgroup, direct execution concurrency and both direct context-pool limits are upper bounds. At startup they are reduced when necessary to keep the configured heap cost below DYNAMIC_APPS_MEMORY_HIGH_WATER_PERCENT, with host and payload headroom. They are unchanged when the cgroup has no finite memory limit. Effective direct concurrency appears as executionConcurrency in executor diagnostics. The actor workerLimit diagnostic is the retained-idle target, not admission.

DYNAMIC_APPS_CONTROL_TOKEN optionally overrides only the token used by Dynamic Apps Engine control-plane requests when running against a local or self-hosted Engine. Rivet Cloud namespace provisioning uses RIVET_CLOUD_TOKEN instead. Neither credential is passed to app code or used by the external app actor client.

For idempotent multi-region deploys, deployApp resolves an existing app actor with get() when the supplied RivetKit client supports it, then falls back to getOrCreate() only when that actor is absent. Structural clients that expose only getOrCreate() retain the original behavior.

DYNAMIC_APPS_TIMING_HEADERS=1 adds benchmark-only phase headers. DYNAMIC_APPS_LOG_REQUESTS=1 emits structured request timing records through the configured log handler without request/response bodies or credentials.

Collecting logs

Register one synchronous handler during host startup:

import { setDynamicAppsLogHandler } from "@rivet-dev/dynamic-apps";

setDynamicAppsLogHandler((event) => {
	process.stdout.write(`${JSON.stringify(event)}\n`);
});

The handler receives application stdout/stderr, sandboxed actor output, build phases, and runtime events. Enqueue into a logging SDK rather than performing a blocking network request in the callback.

Memory and trust boundary

Each immutable release owns one bounded agentOS VM with a read-only mounted artifact. Apps without actors use resettable evaluation contexts. Actor-enabled apps use one cached server process so ordinary HTTP and Rivet's streaming callback share a single module instance.

agentOS supplies the filesystem, process, environment, and network permission boundary for both direct HTTP and app-defined actors. RivetKit actors use the bundled WASM runtime; uploaded app code is never imported into the Dynamic Apps host process.