Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions vendor/dynamic-apps/docs/content/docs/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ title: "Authentication"
description: "Authenticate requests to deployed Dynamic Apps with Hono middleware, route guards, and application-level authorization before handlers run."
---

## Authentication

Use normal Hono middleware to authenticate requests before they reach deployed
apps:

Expand Down
46 changes: 46 additions & 0 deletions vendor/dynamic-apps/docs/content/docs/custom-storage.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
title: "Custom Storage"
description: "Implement durable release publication, loading, and invalidation for Dynamic Apps Core."
skill: true
---

Use `@rivet-dev/dynamic-apps-core` when your host owns release persistence and
notifications:

```ts
import { createDynamicApps } from "@rivet-dev/dynamic-apps-core";

const dynamicApps = createDynamicApps({
async publishRelease(input) {
// Persist input.artifact.bytes, then atomically make this release active.
await store.putArtifact(input.buildId, input.artifact.bytes);
await store.activate(input.appId, input.buildId, input);
return { appId: input.appId, release: input.buildId };
},
async loadActiveRelease(appId) {
// Return one coherent metadata + complete-artifact snapshot.
return store.loadActive(appId);
},
async watchActiveRelease(appId, invalidate) {
// Resolve only after the subscription is live.
return store.subscribe(appId, invalidate);
},
});
```

## Hook guarantees

- **Publish a release:** make the verified artifact durable before atomically
activating it. Do not resolve until a load can observe the new release. A
failed publish must leave the previous release active.
- **Load the active release:** return coherent metadata and complete bytes in
one logical operation. Core copies and independently verifies the bytes.
- **Watch for updates:** subscribe before resolving, invalidate after every
activation, and invalidate after a disconnect that may have missed events.
Duplicate invalidations are safe.

The watcher is required. A no-op watcher is safe only when an app ID cannot
change for the entire lifetime of every serving process.

Call `await dynamicApps.dispose()` during shutdown to release subscriptions,
build resources, cached runtimes, and agentOS contexts.
18 changes: 15 additions & 3 deletions vendor/dynamic-apps/docs/content/docs/deploy.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@ await deployApp({
```

The direct entrypoint must default-export a function or an object with
`fetch(request)`. Static-only directories, Node builtins, and native addons are
not supported in this release candidate.
`fetch(request)`. Code runs inside agentOS with filesystem, process,
environment, and network permissions, and supported Node builtins are
available. Directories that contain only static files are rejected; see
[Static Websites](/dynamic-apps/docs/static-websites). Native addons are not
supported.

## Build repair and rollback

Expand All @@ -56,10 +59,19 @@ for (let attempt = 0; attempt < 3; attempt++) {
}
```

Include `webServerSkill` and `rivetActorsSkill` from `@rivet-dev/dynamic-apps`
in the model prompt. They describe the supported TypeScript server layout and
Rivet actor integration. [View the complete AI App Builder example](https://github.com/rivet-dev/dynamic-apps/tree/main/examples/apps-ai-builder).

A failed build or incomplete artifact write never replaces the active release.
A successful call returns only after the immutable artifact is persisted and
activated; it does not mean a request-serving replica was warmed.

With core, this is the `publishRelease` guarantee: make the complete artifact
durable first, atomically replace the active release second, and resolve only
when `loadActiveRelease` can read it. The default adapter provides those
semantics through its per-app Rivet actor.

`appId` must contain 1–63 lowercase letters, numbers, or hyphens. Pass exactly
one of `source` or `files`.

Expand All @@ -81,7 +93,7 @@ await deployApp({
| Option | Default | Meaning |
| --- | --- | --- |
| `regions` | State actor's current region | Stored compatibility metadata; it does not move direct HTTP execution |
| `createNamespace` | | Deprecated compatibility option; every app always receives its own stable namespace |
| `createNamespace` | none | Deprecated compatibility option; every app always receives its own stable namespace |
| `scaling.minReplicas` | `0` | App-defined actor runner setting; compatibility metadata for direct HTTP |
| `scaling.maxReplicas` | `128` | App-defined actor runner setting; compatibility metadata for direct HTTP |
| `scaling.targetConcurrency` | `8` | App-defined actor runner setting; compatibility metadata for direct HTTP |
Expand Down
95 changes: 72 additions & 23 deletions vendor/dynamic-apps/docs/content/docs/index.mdx
Original file line number Diff line number Diff line change
@@ -1,43 +1,92 @@
---
title: "Dynamic Apps"
description: "Build user-generated HTTP apps and serve them from bounded process-local V8 isolates."
description: "Deploy user-generated applications in isolated agentOS VMs with SQLite, workflows, multiplayer, and static sites out of the box."
skill: true
---

Dynamic Apps builds user-generated HTTP applications in a sandboxed deployment
VM, stores immutable releases in a per-app Rivet actor, and serves ordinary HTTP
from V8 isolates in your own server process.
Dynamic Apps runs user-generated HTTP applications inside your own Node.js
server. Each app runs in an isolated agentOS VM and can add durable SQLite
state, workflows, realtime multiplayer, and static sites.

<Note>
Dynamic Apps is in preview and its API is subject to change.
</Note>

<CardGroup>
<Card title="Quickstart" href="/dynamic-apps/docs/quickstart">
Start the host server, deploy a generated app, and visit it.
</Card>
<Card title="Deploying Apps" href="/dynamic-apps/docs/deploy">
Deploy files or a directory, repair build errors, and configure apps.
</Card>
<Card title="Static Websites" href="/dynamic-apps/docs/static-websites">
Serve an HTML, CSS, and JavaScript site.
</Card>
<Card title="SQLite" href="/dynamic-apps/docs/sqlite">
Store durable relational data in an actor-owned database.
</Card>
<Card title="Multiplayer" href="/dynamic-apps/docs/multiplayer">
Share realtime state between every connected client.
</Card>
<Card title="Workflows" href="/dynamic-apps/docs/workflows">
Run durable multi-step jobs that sleep and resume.
</Card>
</CardGroup>

## Architecture

**Dynamic Apps is a library, not a hosted app deployment platform.** You own the
Hono server, its authentication, and the URL on which applications are mounted.
**Dynamic Apps is a library, not a hosted app deployment platform.** You own
the Hono server, its authentication, and the URL on which applications are
mounted.

Requests reach your Hono server, where `appsRouter` executes them in a cached
agentOS VM holding the app's active release. Warm requests never touch storage
or a Rivet actor.

<svg viewBox="0 0 620 190" role="img" aria-label="An HTTP request reaches your Hono server, where appsRouter executes it inside an agentOS VM serving the user's app." style="width:100%;height:auto;max-width:620px;display:block;margin:2.5rem auto 1rem;">
<defs>
<marker id="apps-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#1b1916" />
</marker>
<symbol id="apps-os" viewBox="0 0 100 100">
<rect x="8" y="8" width="84" height="84" rx="26" fill="none" stroke="#1b1916" stroke-width="8" />
<text x="50" y="50" text-anchor="middle" dominant-baseline="central" font-family="var(--sl-font)" font-weight="700" font-size="38" fill="#1b1916">OS</text>
</symbol>
</defs>

<rect x="12" y="66" width="126" height="58" rx="12" fill="#ffffff" stroke="#1b1916" stroke-width="1.5" />
<text x="75" y="91" text-anchor="middle" font-family="var(--sl-font)" font-size="14" font-weight="600" fill="#1b1916">Request</text>
<text x="75" y="110" text-anchor="middle" font-family="var(--sl-font)" font-size="10.5" fill="#56524a">Agent · Browser · API</text>

<line x1="140" y1="95" x2="184" y2="95" stroke="#1b1916" stroke-width="1.5" marker-end="url(#apps-arrow)" />

Deployment and request serving are deliberately separate:
<rect x="190" y="42" width="174" height="106" rx="14" fill="#faf8f3" stroke="#1b1916" stroke-width="1.5" />
<text x="206" y="65" font-family="var(--sl-font)" font-size="13" font-weight="600" fill="#1b1916">Your Hono server</text>
<rect x="206" y="78" width="142" height="50" rx="8" fill="#ffffff" stroke="#1b1916" stroke-width="1.2" />
<text x="277" y="99" text-anchor="middle" font-family="var(--sl-font)" font-size="12" font-weight="600" fill="#1b1916">appsRouter</text>
<text x="277" y="116" text-anchor="middle" font-family="var(--sl-font)" font-size="10" fill="#56524a">auth · route</text>

```text
deployApp -> per-app state actor -> AgentOS build VM -> immutable AOSP release
<line x1="366" y1="95" x2="410" y2="95" stroke="#1b1916" stroke-width="1.5" marker-end="url(#apps-arrow)" />

first HTTP request -> state actor -> verified artifact -> snapshot/isolate cache
cache-hit HTTP request -> local V8 isolate -> response (zero actor calls)
```
<rect x="416" y="42" width="192" height="106" rx="14" fill="#faf8f3" stroke="#1b1916" stroke-width="1.5" />
<text x="432" y="65" font-family="var(--sl-font)" font-size="13" font-weight="600" fill="#1b1916">agentOS VM</text>
<rect x="432" y="78" width="160" height="50" rx="8" fill="#ffffff" stroke="#1b1916" stroke-width="1.2" />
<use href="#apps-os" x="444" y="88" width="30" height="30" />
<text x="485" y="99" font-family="var(--sl-font)" font-size="11.5" font-weight="600" fill="#1b1916">User's app</text>
<text x="485" y="116" font-family="var(--sl-font)" font-size="10" fill="#56524a">serves the response</text>
</svg>

`appsRouter` executes each request in a clean JavaScript context. The default
mode keeps a small bounded pool of native isolates and restores a clean context
from a cached V8 snapshot after every request. Snapshot-only and fully fresh
isolate modes are also available.
Deployment is separate from serving. `deployApp()` builds the files in a
sandboxed agentOS build VM and publishes an immutable release. The default
`@rivet-dev/dynamic-apps` package stores releases in a per-app Rivet actor;
`@rivet-dev/dynamic-apps-core` lets you supply another store.

An application may additionally export a RivetKit registry. Those app-defined
actors use normal Rivet routing for durable state, actions, events, and
connections, while the same application's ordinary HTTP handler still runs in
the direct-isolate path.
An app may also export a RivetKit registry. Those app-defined actors use normal
Rivet routing for durable state, actions, events, and connections, while the
same app's ordinary HTTP handler still runs through the agentOS VM.

<Note>
`isolated-vm` is a V8 isolation primitive, not a complete hostile multi-tenant
sandbox. Run one trust domain per container and rely on container isolation for
mutually untrusted tenants.
agentOS provides the filesystem, process, environment, and network permission
boundary for direct requests. App-defined actor workers share the host process,
so run one trust domain per container for mutually untrusted tenants.
</Note>
37 changes: 37 additions & 0 deletions vendor/dynamic-apps/docs/content/docs/logging.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
title: "Collecting logs"
description: "Forward Dynamic Apps output and runtime events to your logging provider."
---

Dynamic Apps turns application `console.log` and stdout, application
`console.error` and stderr, actor output, build progress, and enabled request
summaries into structured events.

For Cloud Run or Rivet Compute, write each event as one JSON line so the
platform can collect it:

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

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

You can also enqueue events into a synchronous or buffered logger:

```ts
setDynamicAppsLogHandler((event) => {
logger.log(event.level, event.message, event);
});
```

Each event includes a version, timestamp, level, source, and message. When
available, it also includes the app, release, request, actor, and output stream,
plus bounded metadata. Messages are limited to 64 KiB and carry
`metadata.truncated: true` when shortened.

Delivery is best effort and happens synchronously. Do not make blocking network
requests in the callback; enqueue into your logging SDK instead. Request and
response bodies, authorization headers, environment variables, callback
secrets, endpoint credentials, and build source are excluded.
19 changes: 19 additions & 0 deletions vendor/dynamic-apps/docs/content/docs/multiplayer.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: "Multiplayer"
description: "Share realtime state between every client connected to an app."
---

An actor holds the shared state for one room and broadcasts events to every
connected client. [View the complete example](https://github.com/rivet-dev/dynamic-apps/tree/main/examples/apps-multiplayer).

The server is the generated app. The client shows how your own system connects
to the actors inside it.

<CodeGroup>
<CodeSnippet file="examples/apps-multiplayer/fixtures/app/src/index.ts" title="Server" />
<CodeSnippet file="examples/apps-multiplayer/src/client.ts" region="client" title="Client" />
</CodeGroup>

Dynamic Apps does not wrap RivetKit's action, event, or connection APIs. See
[Events](https://rivet.dev/actors/docs/events/) and
[Connections](https://rivet.dev/actors/docs/connections/) in Rivet Actors.
88 changes: 88 additions & 0 deletions vendor/dynamic-apps/docs/content/docs/quickstart-core.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
title: "Core Quickstart"
description: "Build and serve a Dynamic App with a development-only in-memory release store."
skill: true
---

[View the complete Core Quick Start example on GitHub](https://github.com/rivet-dev/dynamic-apps/tree/main/examples/apps-core-quickstart).

Use Core when your application owns release storage and deployment lifecycle.
Use the standard [Dynamic Apps Quick Start](/dynamic-apps/docs/quickstart) when
you want Rivet to provide those pieces.

## Choosing between Core and the Rivet-backed package

| | Core | Rivet-backed package |
|-|---|---|
| Package | `@rivet-dev/dynamic-apps-core` | `@rivet-dev/dynamic-apps` |
| Build artifact storage | Provide upload and download handlers | Stored automatically |
| Cache invalidation after updates | Manual notification with `watchActiveRelease` | Handled automatically |
| Rivet namespace per app | Bring your own integration | Created and connected automatically |
| Regions and scaling | Managed by your host | Managed through Rivet deployment options |
| Lifecycle | Explicit `dispose()` | Managed by the package |
| Best for | Custom infrastructure | Batteries included and scalable |

<Note>
This in-memory store is **development-only**. It loses releases on restart and
cannot invalidate another process. Use durable storage and cross-process
notifications in production.
</Note>

<Steps>

<Step title="Install">

Use Node.js 22 or newer:

```sh
npm add @rivet-dev/dynamic-apps-core @hono/node-server hono
npm add --save-dev tsx
npm pkg set type=module
```

</Step>

<Step title="Create the host and release store">

The example keeps a `Map` of active releases and a `Map` of update listeners.
Its hooks atomically publish a complete copied artifact, load a copied active
release, and register an update watcher that returns an unsubscribe function.
It then mounts the router, deploys a complete generated two-file app, and
disposes the instance during shutdown.

<CodeSnippet file="examples/apps-core-quickstart/src/server.ts" title="src/server.ts" />

</Step>

<Step title="Run the server">

Pass the listening host on the command line:

```sh
npx tsx src/server.ts --host 0.0.0.0
```

</Step>

<Step title="Call the generated app">

```sh
curl http://localhost:3000/apps/hello/
# Hello from Dynamic Apps Core!
```

</Step>

</Steps>

The request lifecycle is compact:

```text
agentOS build -> publishRelease
first request -> watchActiveRelease + loadActiveRelease
warm request -> cached agentOS VM (zero hooks)
```

Continue with [Custom Storage](/dynamic-apps/docs/custom-storage) for durable,
multi-process hooks. Use the ordinary [Quick Start](/dynamic-apps/docs/quickstart)
for the batteries-included Rivet actor-backed package.
10 changes: 5 additions & 5 deletions vendor/dynamic-apps/docs/content/docs/quickstart.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: "Quickstart"
description: "Start the host server, deploy a generated app, and serve it from a local V8 isolate."
description: "Start the host server, deploy a generated app, and serve it through agentOS."
skill: true
---

Expand All @@ -26,14 +26,14 @@ npm pkg set type=module

Mount the private Rivet callback separately from application traffic. The
callback keeps the deployment state actor available; ordinary app requests are
served by `appsRouter` from local isolates.
served by `appsRouter` through a cached agentOS VM.

<CodeSnippet file="examples/apps-hello-world/src/server.ts" title="src/server.ts" />

Run the server with Node snapshots disabled, as required by `isolated-vm`:
Run the server normally:

```sh
node --no-node-snapshot --import tsx src/server.ts
npx tsx src/server.ts
```

</Step>
Expand All @@ -46,7 +46,7 @@ agent, an upload endpoint, or another trusted control-plane process.
<CodeSnippet file="examples/apps-hello-world/src/deploy.ts" title="src/deploy.ts" />

```sh
node --import tsx src/deploy.ts
npx tsx src/deploy.ts
```

</Step>
Expand Down
Loading
Loading