diff --git a/vendor/dynamic-apps/docs/content/docs/authentication.mdx b/vendor/dynamic-apps/docs/content/docs/authentication.mdx index ad0799f0..02fc6e54 100644 --- a/vendor/dynamic-apps/docs/content/docs/authentication.mdx +++ b/vendor/dynamic-apps/docs/content/docs/authentication.mdx @@ -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: diff --git a/vendor/dynamic-apps/docs/content/docs/custom-storage.mdx b/vendor/dynamic-apps/docs/content/docs/custom-storage.mdx new file mode 100644 index 00000000..347526e1 --- /dev/null +++ b/vendor/dynamic-apps/docs/content/docs/custom-storage.mdx @@ -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. diff --git a/vendor/dynamic-apps/docs/content/docs/deploy.mdx b/vendor/dynamic-apps/docs/content/docs/deploy.mdx index 35517f36..041ea6ee 100644 --- a/vendor/dynamic-apps/docs/content/docs/deploy.mdx +++ b/vendor/dynamic-apps/docs/content/docs/deploy.mdx @@ -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 @@ -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`. @@ -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 | diff --git a/vendor/dynamic-apps/docs/content/docs/index.mdx b/vendor/dynamic-apps/docs/content/docs/index.mdx index 5f66d460..872b4bbf 100644 --- a/vendor/dynamic-apps/docs/content/docs/index.mdx +++ b/vendor/dynamic-apps/docs/content/docs/index.mdx @@ -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. Dynamic Apps is in preview and its API is subject to change. + + + Start the host server, deploy a generated app, and visit it. + + + Deploy files or a directory, repair build errors, and configure apps. + + + Serve an HTML, CSS, and JavaScript site. + + + Store durable relational data in an actor-owned database. + + + Share realtime state between every connected client. + + + Run durable multi-step jobs that sleep and resume. + + + ## 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. + + + + + + + + + OS + + + + + Request + Agent · Browser · API + + -Deployment and request serving are deliberately separate: + + Your Hono server + + appsRouter + auth · route -```text -deployApp -> per-app state actor -> AgentOS build VM -> immutable AOSP release + -first HTTP request -> state actor -> verified artifact -> snapshot/isolate cache -cache-hit HTTP request -> local V8 isolate -> response (zero actor calls) -``` + + agentOS VM + + + User's app + serves the response + -`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. -`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. diff --git a/vendor/dynamic-apps/docs/content/docs/logging.mdx b/vendor/dynamic-apps/docs/content/docs/logging.mdx new file mode 100644 index 00000000..e7185785 --- /dev/null +++ b/vendor/dynamic-apps/docs/content/docs/logging.mdx @@ -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. diff --git a/vendor/dynamic-apps/docs/content/docs/multiplayer.mdx b/vendor/dynamic-apps/docs/content/docs/multiplayer.mdx new file mode 100644 index 00000000..65a13435 --- /dev/null +++ b/vendor/dynamic-apps/docs/content/docs/multiplayer.mdx @@ -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. + + + + + + +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. diff --git a/vendor/dynamic-apps/docs/content/docs/quickstart-core.mdx b/vendor/dynamic-apps/docs/content/docs/quickstart-core.mdx new file mode 100644 index 00000000..ab14bdfa --- /dev/null +++ b/vendor/dynamic-apps/docs/content/docs/quickstart-core.mdx @@ -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 | + + +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. + + + + + + +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 +``` + + + + + +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. + + + + + + + +Pass the listening host on the command line: + +```sh +npx tsx src/server.ts --host 0.0.0.0 +``` + + + + + +```sh +curl http://localhost:3000/apps/hello/ +# Hello from Dynamic Apps Core! +``` + + + + + +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. diff --git a/vendor/dynamic-apps/docs/content/docs/quickstart.mdx b/vendor/dynamic-apps/docs/content/docs/quickstart.mdx index c8c83d6b..9b3e9271 100644 --- a/vendor/dynamic-apps/docs/content/docs/quickstart.mdx +++ b/vendor/dynamic-apps/docs/content/docs/quickstart.mdx @@ -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 --- @@ -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. -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 ``` @@ -46,7 +46,7 @@ agent, an upload endpoint, or another trusted control-plane process. ```sh -node --import tsx src/deploy.ts +npx tsx src/deploy.ts ``` diff --git a/vendor/dynamic-apps/docs/content/docs/realtime.mdx b/vendor/dynamic-apps/docs/content/docs/realtime.mdx deleted file mode 100644 index 6c34f1cb..00000000 --- a/vendor/dynamic-apps/docs/content/docs/realtime.mdx +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: "Realtime Events" -description: "Subscribe to events from an app-defined RivetKit actor." ---- - -App-defined actors expose the standard RivetKit realtime client. Subscribe to -an event on a connected actor handle: - -```ts -const counter = await client.counter.getOrCreate(["main"]).connect(); - -const unsubscribe = counter.on("changed", (count) => { - console.log("count changed", count); -}); - -await counter.add(1); -unsubscribe(); -await counter.dispose(); -``` - -Dynamic Apps packages the actor registry and configures its stable runner pool; -it does not wrap or replace RivetKit's action, event, or connection APIs. diff --git a/vendor/dynamic-apps/docs/content/docs/reference.mdx b/vendor/dynamic-apps/docs/content/docs/reference.mdx deleted file mode 100644 index 75cd82ec..00000000 --- a/vendor/dynamic-apps/docs/content/docs/reference.mdx +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: "Reference" -description: "Build and repair Dynamic Apps with AI-generated files, deployment diagnostics, and a practical roadmap for extending generated backends." ---- - -## Build Apps with AI - -Give an agent the app requirements, let it generate the project files, and pass -those files to `deployApp()`. If the build returns TypeScript diagnostics, give -them back to the agent and deploy its repaired files again. - -[View the complete AI App Builder example](https://github.com/rivet-dev/dynamic-apps/tree/main/examples/apps-ai-builder). - -## Planned Improvements - -- Automatically include agent skills based on the - [Actors Learn guides](https://rivet.dev/actors/learn/) for better app generation. -- Billing API for tracking and charging for app usage. -- Built-in error reporting for generated apps. diff --git a/vendor/dynamic-apps/docs/content/docs/routing.mdx b/vendor/dynamic-apps/docs/content/docs/routing.mdx index edb805e3..6bebd4cf 100644 --- a/vendor/dynamic-apps/docs/content/docs/routing.mdx +++ b/vendor/dynamic-apps/docs/content/docs/routing.mdx @@ -5,8 +5,7 @@ skill: true --- The package root exports one router. Mount ordinary application traffic at any -prefix and explicitly dispatch the private `/api/rivet` callback to the same -router: +prefix and forward the private `/api/rivet/*` callback to the same router: ```ts import { appsRouter } from "@rivet-dev/dynamic-apps"; @@ -14,14 +13,7 @@ import { Hono } from "hono"; const server = new Hono(); -const dispatchRegistry = (request: Request) => { - const headers = new Headers(request.headers); - headers.set("x-agentos-app-registry-dispatch", "1"); - return appsRouter.fetch(new Request(request, { headers })); -}; - -server.all("/api/rivet", (c) => dispatchRegistry(c.req.raw)); -server.all("/api/rivet/*", (c) => dispatchRegistry(c.req.raw)); +server.all("/api/rivet/*", (c) => appsRouter.fetch(c.req.raw)); server.route("/apps", appsRouter); ``` @@ -30,6 +22,3 @@ This serves `/:appId/*` relative to the mount. For example, `/api/items?q=1`. A bare `/apps/example` request redirects to `/apps/example/`. -There is no `createAppsRouter` or router-specific client option. `appsRouter` -creates and reuses its private control client lazily; cache-hit HTTP requests do -not call that actor. diff --git a/vendor/dynamic-apps/docs/content/docs/sqlite.mdx b/vendor/dynamic-apps/docs/content/docs/sqlite.mdx new file mode 100644 index 00000000..00b20165 --- /dev/null +++ b/vendor/dynamic-apps/docs/content/docs/sqlite.mdx @@ -0,0 +1,20 @@ +--- +title: "SQLite" +description: "Store durable relational data in an actor-owned SQLite database." +--- + +Apps that depend on `rivetkit` can define actors. Each actor owns its own +SQLite database, so a generated app gets durable relational data without any +extra infrastructure. [View the complete example](https://github.com/rivet-dev/dynamic-apps/tree/main/examples/apps-sqlite). + +The server is the generated app. The client shows how your own system connects +to the actors inside it. + + + + + + +`deployApp()` returns the endpoint, namespace, pool, and token the ordinary +RivetKit client needs. See [SQLite in Rivet Actors](https://rivet.dev/actors/docs/sqlite/) +for the full database API. diff --git a/vendor/dynamic-apps/docs/content/docs/state-and-data.mdx b/vendor/dynamic-apps/docs/content/docs/state-and-data.mdx deleted file mode 100644 index 0b7b6ebd..00000000 --- a/vendor/dynamic-apps/docs/content/docs/state-and-data.mdx +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: "State & Actors" -description: "Add durable RivetKit actors to an application while keeping ordinary HTTP on the direct-isolate path." ---- - -An application can combine ordinary HTTP with app-defined RivetKit actors. -Declare `rivetkit` in the application's dependencies, export its registry, and -keep the normal `registry.start()` call: - -```ts -import { actor, event, setup } from "rivetkit"; - -const counter = actor({ - state: { count: 0 }, - events: { changed: event() }, - actions: { - add(c, amount = 1) { - c.state.count += amount; - c.broadcast("changed", c.state.count); - return c.state.count; - }, - }, -}); - -export const registry = setup({ use: { counter } }); -registry.start(); - -export default () => new Response("ordinary HTTP still runs locally"); -``` - -`deployApp()` returns the stable namespace and runner-pool names needed by the -ordinary RivetKit client: - -```ts -import { createClient } from "rivetkit/client"; - -const deployment = await deployApp({ appId: "counter", source }); -const client = createClient({ - namespace: deployment.namespace, - poolName: deployment.pool, -}); - -const handle = await client.counter.getOrCreate(["main"]).connect(); -console.log(await handle.add(1)); -``` - -Actor actions, state, events, connections, and streaming actor responses use -normal Rivet routing. Requests to the application's default HTTP export still -use the process-local direct-isolate path. diff --git a/vendor/dynamic-apps/docs/content/docs/static-websites.mdx b/vendor/dynamic-apps/docs/content/docs/static-websites.mdx new file mode 100644 index 00000000..f265d613 --- /dev/null +++ b/vendor/dynamic-apps/docs/content/docs/static-websites.mdx @@ -0,0 +1,21 @@ +--- +title: "Static Websites" +description: "Serve an HTML, CSS, and JavaScript site from a Dynamic App." +--- + +An app is a directory with a `package.json` and a server entrypoint. A static +site is the same directory plus a `public/` folder and a handler that serves +it. [View the complete example](https://github.com/rivet-dev/dynamic-apps/tree/main/examples/apps-static-website). + + + + + + + +Deploy the directory and open `/apps/static-website/`: + + + +Directories that contain only static files are rejected. Every app needs a +`package.json` and an entrypoint that default-exports a `fetch` handler. diff --git a/vendor/dynamic-apps/docs/content/docs/workflows.mdx b/vendor/dynamic-apps/docs/content/docs/workflows.mdx new file mode 100644 index 00000000..9f595ac7 --- /dev/null +++ b/vendor/dynamic-apps/docs/content/docs/workflows.mdx @@ -0,0 +1,19 @@ +--- +title: "Workflows" +description: "Run durable multi-step jobs that sleep, scale to zero, and resume." +--- + +An actor's `run` workflow executes when the actor is created. Each step is +durable, so the job survives restarts and the app scales to zero while it +sleeps. [View the complete example](https://github.com/rivet-dev/dynamic-apps/tree/main/examples/apps-workflows). + +The server is the generated app. The client shows how your own system connects +to the actors inside it. + + + + + + +See [Workflows in Rivet Actors](https://rivet.dev/actors/docs/workflows/) for +steps, loops, queues, and error handling. diff --git a/vendor/dynamic-apps/docs/sidebar.json b/vendor/dynamic-apps/docs/sidebar.json index e1dcb81b..2616afda 100644 --- a/vendor/dynamic-apps/docs/sidebar.json +++ b/vendor/dynamic-apps/docs/sidebar.json @@ -12,6 +12,11 @@ "title": "Quickstart", "href": "/dynamic-apps/docs/quickstart", "icon": "faForwardFast" + }, + { + "title": "Core Quickstart", + "href": "/dynamic-apps/docs/quickstart-core", + "icon": "faForwardFast" } ] }, @@ -25,6 +30,10 @@ { "title": "Routing", "href": "/dynamic-apps/docs/routing" + }, + { + "title": "Custom Storage", + "href": "/dynamic-apps/docs/custom-storage" } ] }, @@ -32,12 +41,20 @@ "title": "Capabilities", "pages": [ { - "title": "State & Actors", - "href": "/dynamic-apps/docs/state-and-data" + "title": "Static Websites", + "href": "/dynamic-apps/docs/static-websites" + }, + { + "title": "SQLite", + "href": "/dynamic-apps/docs/sqlite" + }, + { + "title": "Multiplayer", + "href": "/dynamic-apps/docs/multiplayer" }, { - "title": "Realtime Events", - "href": "/dynamic-apps/docs/realtime" + "title": "Workflows", + "href": "/dynamic-apps/docs/workflows" } ] }, @@ -49,8 +66,8 @@ "href": "/dynamic-apps/docs/authentication" }, { - "title": "Reference", - "href": "/dynamic-apps/docs/reference" + "title": "Collecting logs", + "href": "/dynamic-apps/docs/logging" } ] } diff --git a/vendor/dynamic-apps/examples/apps-ai-builder/fixtures/app/package.json b/vendor/dynamic-apps/examples/apps-ai-builder/fixtures/app/package.json index f866b0c1..7174e192 100644 --- a/vendor/dynamic-apps/examples/apps-ai-builder/fixtures/app/package.json +++ b/vendor/dynamic-apps/examples/apps-ai-builder/fixtures/app/package.json @@ -8,7 +8,12 @@ "build": "tsc", "check-types": "tsc --noEmit" }, + "dependencies": { + "@hono/node-server": "2.0.11", + "hono": "4.12.9" + }, "devDependencies": { + "@types/node": "22.19.15", "typescript": "5.7.3" } } diff --git a/vendor/dynamic-apps/examples/apps-ai-builder/fixtures/app/src/index.ts b/vendor/dynamic-apps/examples/apps-ai-builder/fixtures/app/src/index.ts index c369cd29..88dc11ad 100644 --- a/vendor/dynamic-apps/examples/apps-ai-builder/fixtures/app/src/index.ts +++ b/vendor/dynamic-apps/examples/apps-ai-builder/fixtures/app/src/index.ts @@ -1,8 +1,15 @@ -export default { - fetch(request: Request) { - return Response.json({ - message: "Replace this seed with the generated application.", - path: new URL(request.url).pathname, - }); - }, -}; +import { serve } from "@hono/node-server"; +import { Hono } from "hono"; + +const app = new Hono(); + +app.get("/", (context) => + context.json({ + message: "Replace this seed with the generated application.", + }), +); + +serve({ + fetch: app.fetch, + port: Number(process.env.PORT ?? 3000), +}); diff --git a/vendor/dynamic-apps/examples/apps-ai-builder/package.json b/vendor/dynamic-apps/examples/apps-ai-builder/package.json index 3d35ce66..cab81a21 100644 --- a/vendor/dynamic-apps/examples/apps-ai-builder/package.json +++ b/vendor/dynamic-apps/examples/apps-ai-builder/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "start": "node --no-node-snapshot --import tsx src/server.ts", + "start": "npx tsx src/server.ts", "check-types": "tsc --noEmit" }, "dependencies": { diff --git a/vendor/dynamic-apps/examples/apps-ai-builder/src/server.ts b/vendor/dynamic-apps/examples/apps-ai-builder/src/server.ts index 509a0a68..fd08a980 100644 --- a/vendor/dynamic-apps/examples/apps-ai-builder/src/server.ts +++ b/vendor/dynamic-apps/examples/apps-ai-builder/src/server.ts @@ -1,7 +1,12 @@ import { readFile } from "node:fs/promises"; import { anthropic } from "@ai-sdk/anthropic"; import { serve } from "@hono/node-server"; -import { appsRouter, deployApp } from "@rivet-dev/dynamic-apps"; +import { + appsRouter, + deployApp, + rivetActorsSkill, + webServerSkill, +} from "@rivet-dev/dynamic-apps"; import { generateText } from "ai"; import { Hono } from "hono"; @@ -53,9 +58,11 @@ async function revise( model: anthropic(process.env.AI_MODEL ?? "claude-sonnet-4-5"), maxOutputTokens: 8_000, prompt: [ + // These skills tell the model how to structure, build, and serve the generated code. + webServerSkill, + rivetActorsSkill, 'Return JSON only as {"files":{"path":"content"}}.', `You may edit only: ${editablePaths.join(", ")}.`, - "The app must export a default object with fetch(request) returning a Web Response.", `User request: ${prompt}`, diagnostics ? `Previous build diagnostics:\n${diagnostics}` : "", `Current files:\n${JSON.stringify(files)}`, @@ -80,7 +87,7 @@ async function generateApp(appId: string, prompt: string) { error !== null && "code" in error && typeof error.code === "string" && - error.code.startsWith("agentos_apps_"); + error.code.startsWith("dynamic_apps_"); if (!appsError || attempt === maxRepairs) { throw error; } @@ -101,13 +108,7 @@ async function generateApp(appId: string, prompt: string) { } const server = new Hono(); -const dispatchRegistry = (request: Request) => { - const headers = new Headers(request.headers); - headers.set("x-agentos-app-registry-dispatch", "1"); - return appsRouter.fetch(new Request(request, { headers })); -}; -server.all("/api/rivet", (c) => dispatchRegistry(c.req.raw)); -server.all("/api/rivet/*", (c) => dispatchRegistry(c.req.raw)); +server.all("/api/rivet/*", (c) => appsRouter.fetch(c.req.raw)); // An agent or any other part of the system can call this route. A generic // deployment endpoint could accept multipart files; this example generates the diff --git a/vendor/dynamic-apps/examples/apps-core-quickstart/package.json b/vendor/dynamic-apps/examples/apps-core-quickstart/package.json new file mode 100644 index 00000000..e0ae2bb2 --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-core-quickstart/package.json @@ -0,0 +1,20 @@ +{ + "name": "@rivet-dev/dynamic-apps-example-core-quickstart", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "start": "npx tsx src/server.ts --host 0.0.0.0", + "check-types": "tsc --noEmit" + }, + "dependencies": { + "@hono/node-server": "^2.0.11", + "@rivet-dev/dynamic-apps-core": "workspace:*", + "hono": "^4.12.9" + }, + "devDependencies": { + "@types/node": "^22.19.15", + "tsx": "^4.20.6", + "typescript": "^5.7.3" + } +} diff --git a/vendor/dynamic-apps/examples/apps-core-quickstart/src/server.ts b/vendor/dynamic-apps/examples/apps-core-quickstart/src/server.ts new file mode 100644 index 00000000..a801a78f --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-core-quickstart/src/server.ts @@ -0,0 +1,98 @@ +import { serve } from "@hono/node-server"; +import { + type ActiveRelease, + createDynamicApps, +} from "@rivet-dev/dynamic-apps-core"; +import { Hono } from "hono"; + +// Development only: releases disappear on restart and updates cannot reach +// another process. Use durable storage and cross-process invalidation in production. +const active = new Map(); +const listeners = new Map void>>(); + +const dynamicApps = createDynamicApps({ + async publishRelease(input) { + const release: ActiveRelease = { + appId: input.appId, + release: input.buildId, + artifact: { + ...input.artifact, + bytes: new Uint8Array(input.artifact.bytes), + }, + regions: input.regions ?? ["local"], + scaling: { + minReplicas: input.scaling?.minReplicas ?? 0, + maxReplicas: input.scaling?.maxReplicas ?? 1, + targetConcurrency: input.scaling?.targetConcurrency ?? 8, + }, + maxRequestBytes: 1024 * 1024, + maxResponseBytes: 4 * 1024 * 1024, + }; + // The complete artifact is stored before this single active-map update. + active.set(input.appId, release); + for (const invalidate of listeners.get(input.appId) ?? []) invalidate(); + return { appId: input.appId, release: release.release }; + }, + async loadActiveRelease(appId) { + const release = active.get(appId); + return release + ? { + ...release, + regions: [...release.regions], + scaling: { ...release.scaling }, + artifact: { + ...release.artifact, + bytes: new Uint8Array(release.artifact.bytes), + }, + } + : undefined; + }, + async watchActiveRelease(appId, invalidate) { + const appListeners = listeners.get(appId) ?? new Set(); + appListeners.add(invalidate); + listeners.set(appId, appListeners); + return () => { + appListeners.delete(invalidate); + if (appListeners.size === 0) listeners.delete(appId); + }; + }, +}); + +const app = new Hono(); +app.route("/apps", dynamicApps.appsRouter); + +let server: ReturnType | undefined; +let shuttingDown = false; +const shutdown = async () => { + if (shuttingDown) return; + shuttingDown = true; + await dynamicApps.dispose(); + server?.close(); +}; +process.once("SIGINT", () => void shutdown()); +process.once("SIGTERM", () => void shutdown()); + +await dynamicApps.deployApp({ + appId: "hello", + files: { + "package.json": JSON.stringify({ + private: true, + type: "module", + main: "index.js", + }), + "index.js": ` + export default { + fetch() { + return new Response("Hello from Dynamic Apps Core!"); + }, + }; + `, + }, +}); + +const hostIndex = process.argv.indexOf("--host"); +const hostname = hostIndex >= 0 ? process.argv[hostIndex + 1] : "127.0.0.1"; +if (!hostname) throw new Error("--host requires a value"); +const port = Number(process.env.PORT ?? 3000); +server = serve({ fetch: app.fetch, hostname, port }); +console.log(`Dynamic Apps Core listening on http://${hostname}:${port}`); diff --git a/vendor/dynamic-apps/examples/apps-core-quickstart/tsconfig.json b/vendor/dynamic-apps/examples/apps-core-quickstart/tsconfig.json new file mode 100644 index 00000000..dbe86e31 --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-core-quickstart/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "noEmit": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/vendor/dynamic-apps/examples/apps-hello-world/fixtures/app/src/index.ts b/vendor/dynamic-apps/examples/apps-hello-world/fixtures/app/src/index.ts index 625e3a4a..428ee0ab 100644 --- a/vendor/dynamic-apps/examples/apps-hello-world/fixtures/app/src/index.ts +++ b/vendor/dynamic-apps/examples/apps-hello-world/fixtures/app/src/index.ts @@ -14,7 +14,7 @@ app.get("/", (c) => {

Hello from Dynamic Apps

-

This HTML is served by an HTTP app running inside a V8 isolate.

+

This HTML is served by an HTTP app running inside agentOS.

Call the JSON API

diff --git a/vendor/dynamic-apps/examples/apps-hello-world/package.json b/vendor/dynamic-apps/examples/apps-hello-world/package.json index 9c5ea687..993787fd 100644 --- a/vendor/dynamic-apps/examples/apps-hello-world/package.json +++ b/vendor/dynamic-apps/examples/apps-hello-world/package.json @@ -4,8 +4,8 @@ "private": true, "type": "module", "scripts": { - "start": "node --no-node-snapshot --import tsx src/server.ts", - "deploy": "node --import tsx src/deploy.ts", + "start": "npx tsx src/server.ts", + "deploy": "npx tsx src/deploy.ts", "check-types": "tsc --noEmit" }, "dependencies": { diff --git a/vendor/dynamic-apps/examples/apps-hello-world/src/server.ts b/vendor/dynamic-apps/examples/apps-hello-world/src/server.ts index 811fdead..b13cf593 100644 --- a/vendor/dynamic-apps/examples/apps-hello-world/src/server.ts +++ b/vendor/dynamic-apps/examples/apps-hello-world/src/server.ts @@ -4,13 +4,7 @@ import { Hono } from "hono"; const server = new Hono(); -const dispatchRegistry = (request: Request) => { - const headers = new Headers(request.headers); - headers.set("x-agentos-app-registry-dispatch", "1"); - return appsRouter.fetch(new Request(request, { headers })); -}; -server.all("/api/rivet", (c) => dispatchRegistry(c.req.raw)); -server.all("/api/rivet/*", (c) => dispatchRegistry(c.req.raw)); +server.all("/api/rivet/*", (c) => appsRouter.fetch(c.req.raw)); // Mount every deployed application at /apps/:appId. server.route("/apps", appsRouter); diff --git a/vendor/dynamic-apps/examples/apps-multiplayer/fixtures/app/package.json b/vendor/dynamic-apps/examples/apps-multiplayer/fixtures/app/package.json new file mode 100644 index 00000000..7da6914f --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-multiplayer/fixtures/app/package.json @@ -0,0 +1,12 @@ +{ + "name": "multiplayer-room-app", + "version": "0.0.0", + "private": true, + "type": "module", + "main": "src/index.ts", + "dependencies": { + "@hono/node-server": "2.1.1", + "hono": "4.13.3", + "rivetkit": "2.3.11" + } +} diff --git a/vendor/dynamic-apps/examples/apps-multiplayer/fixtures/app/src/index.ts b/vendor/dynamic-apps/examples/apps-multiplayer/fixtures/app/src/index.ts new file mode 100644 index 00000000..9be651b4 --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-multiplayer/fixtures/app/src/index.ts @@ -0,0 +1,44 @@ +import { serve } from "@hono/node-server"; +import { Hono } from "hono"; +import { actor, event, setup } from "rivetkit"; + +type Position = { x: number; y: number }; + +// One actor per room. State is shared by every connected client. +const room = actor({ + state: { players: {} as Record }, + events: { changed: event() }, + actions: { + join(c, player: string) { + c.state.players[player] ??= { x: 0, y: 0 }; + c.broadcast("changed", c.state.players); + return c.state.players; + }, + move(c, player: string, x: number, y: number) { + c.state.players[player] = { x, y }; + c.broadcast("changed", c.state.players); + return c.state.players; + }, + }, +}); + +export const registry = setup({ use: { room } }); + +const app = new Hono(); +app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +app.get("/", (c) => + c.json({ message: "Use the RivetKit client to join a room." }), +); + +// Dynamic Apps runs the actors in serverless mode and waits for this listener. +if (process.env.RIVETKIT_RUNTIME_MODE === "serverless") { + await new Promise((resolve, reject) => { + const server = serve( + { fetch: app.fetch, port: Number(process.env.PORT), hostname: "0.0.0.0" }, + () => resolve(), + ); + server.once("error", reject); + }); +} + +export default app; diff --git a/vendor/dynamic-apps/examples/apps-multiplayer/package.json b/vendor/dynamic-apps/examples/apps-multiplayer/package.json new file mode 100644 index 00000000..dad4ae8f --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-multiplayer/package.json @@ -0,0 +1,22 @@ +{ + "name": "@rivet-dev/dynamic-apps-example-multiplayer", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "start": "npx tsx src/server.ts", + "client": "npx tsx src/client.ts", + "check-types": "tsc --noEmit" + }, + "dependencies": { + "@hono/node-server": "^2.0.11", + "@rivet-dev/dynamic-apps": "workspace:*", + "hono": "^4.12.9", + "rivetkit": "2.3.11" + }, + "devDependencies": { + "@types/node": "^22.19.15", + "tsx": "^4.20.6", + "typescript": "^5.7.3" + } +} diff --git a/vendor/dynamic-apps/examples/apps-multiplayer/src/client.ts b/vendor/dynamic-apps/examples/apps-multiplayer/src/client.ts new file mode 100644 index 00000000..a8abb747 --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-multiplayer/src/client.ts @@ -0,0 +1,32 @@ +import type { deployApp } from "@rivet-dev/dynamic-apps"; +import { createClient } from "rivetkit/client"; +import type { registry } from "../fixtures/app/src/index.js"; + +// Deploy the app through the host server and read back its connection details. +const host = process.env.HOST_URL ?? "http://localhost:3000"; +const response = await fetch(`${host}/deploy/multiplayer-room`, { + method: "POST", +}); +if (!response.ok) throw new Error(`deploy failed: ${await response.text()}`); +const deployment = (await response.json()) as Awaited< + ReturnType +>; + +// docs:start client +const client = createClient({ + endpoint: deployment.endpoint, + namespace: deployment.namespace, + poolName: deployment.pool, + token: deployment.token, +}); + +// Open a realtime connection and receive every broadcast from the room. +const room = client.room.getOrCreate(["lobby"]).connect(); +room.on("changed", (players) => console.log("players", players)); + +await room.join("alice"); +await room.move("alice", 4, 8); +await room.dispose(); +// docs:end client + +await client.dispose(); diff --git a/vendor/dynamic-apps/examples/apps-multiplayer/src/server.ts b/vendor/dynamic-apps/examples/apps-multiplayer/src/server.ts new file mode 100644 index 00000000..32e26acc --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-multiplayer/src/server.ts @@ -0,0 +1,21 @@ +import { serve } from "@hono/node-server"; +import { appsRouter, deployApp } from "@rivet-dev/dynamic-apps"; +import { Hono } from "hono"; + +const server = new Hono(); +server.all("/api/rivet/*", (c) => appsRouter.fetch(c.req.raw)); + +// An agent or upload endpoint would pass generated files here. This example +// deploys its checked-in fixture and returns the deployment to the caller. +server.post("/deploy/:name", async (c) => + c.json( + await deployApp({ + appId: c.req.param("name"), + source: new URL("../fixtures/app/", import.meta.url), + }), + ), +); + +server.route("/apps", appsRouter); + +serve({ fetch: server.fetch, port: Number(process.env.PORT ?? 3000) }); diff --git a/vendor/dynamic-apps/examples/apps-multiplayer/tsconfig.json b/vendor/dynamic-apps/examples/apps-multiplayer/tsconfig.json new file mode 100644 index 00000000..888653f3 --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-multiplayer/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "noEmit": true, + "skipLibCheck": true + }, + "include": ["src", "fixtures/app/src"] +} diff --git a/vendor/dynamic-apps/examples/apps-sqlite/fixtures/app/package.json b/vendor/dynamic-apps/examples/apps-sqlite/fixtures/app/package.json new file mode 100644 index 00000000..52522ca5 --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-sqlite/fixtures/app/package.json @@ -0,0 +1,12 @@ +{ + "name": "sqlite-notes-app", + "version": "0.0.0", + "private": true, + "type": "module", + "main": "src/index.ts", + "dependencies": { + "@hono/node-server": "2.1.1", + "hono": "4.13.3", + "rivetkit": "2.3.11" + } +} diff --git a/vendor/dynamic-apps/examples/apps-sqlite/fixtures/app/src/index.ts b/vendor/dynamic-apps/examples/apps-sqlite/fixtures/app/src/index.ts new file mode 100644 index 00000000..553a9efe --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-sqlite/fixtures/app/src/index.ts @@ -0,0 +1,47 @@ +import { serve } from "@hono/node-server"; +import { Hono } from "hono"; +import { actor, setup } from "rivetkit"; +import { db } from "rivetkit/db"; + +// Each actor owns its own SQLite database. +const notes = actor({ + db: db({ + async onMigrate(database) { + await database.execute(` + CREATE TABLE IF NOT EXISTS notes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + body TEXT NOT NULL + ) + `); + }, + }), + actions: { + async add(c, body: string) { + await c.db.execute("INSERT INTO notes (body) VALUES (?)", body); + }, + async list(c) { + return c.db.execute("SELECT id, body FROM notes ORDER BY id"); + }, + }, +}); + +export const registry = setup({ use: { notes } }); + +const app = new Hono(); +app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +app.get("/", (c) => + c.json({ message: "Use the RivetKit client to add notes." }), +); + +// Dynamic Apps runs the actors in serverless mode and waits for this listener. +if (process.env.RIVETKIT_RUNTIME_MODE === "serverless") { + await new Promise((resolve, reject) => { + const server = serve( + { fetch: app.fetch, port: Number(process.env.PORT), hostname: "0.0.0.0" }, + () => resolve(), + ); + server.once("error", reject); + }); +} + +export default app; diff --git a/vendor/dynamic-apps/examples/apps-sqlite/package.json b/vendor/dynamic-apps/examples/apps-sqlite/package.json new file mode 100644 index 00000000..84bdb1db --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-sqlite/package.json @@ -0,0 +1,22 @@ +{ + "name": "@rivet-dev/dynamic-apps-example-sqlite", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "start": "npx tsx src/server.ts", + "client": "npx tsx src/client.ts", + "check-types": "tsc --noEmit" + }, + "dependencies": { + "@hono/node-server": "^2.0.11", + "@rivet-dev/dynamic-apps": "workspace:*", + "hono": "^4.12.9", + "rivetkit": "2.3.11" + }, + "devDependencies": { + "@types/node": "^22.19.15", + "tsx": "^4.20.6", + "typescript": "^5.7.3" + } +} diff --git a/vendor/dynamic-apps/examples/apps-sqlite/src/client.ts b/vendor/dynamic-apps/examples/apps-sqlite/src/client.ts new file mode 100644 index 00000000..245c6511 --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-sqlite/src/client.ts @@ -0,0 +1,29 @@ +import type { deployApp } from "@rivet-dev/dynamic-apps"; +import { createClient } from "rivetkit/client"; +import type { registry } from "../fixtures/app/src/index.js"; + +// Deploy the app through the host server and read back its connection details. +const host = process.env.HOST_URL ?? "http://localhost:3000"; +const response = await fetch(`${host}/deploy/sqlite-notes`, { + method: "POST", +}); +if (!response.ok) throw new Error(`deploy failed: ${await response.text()}`); +const deployment = (await response.json()) as Awaited< + ReturnType +>; + +// docs:start client +// Connect to the actors inside the app's own Rivet namespace. +const client = createClient({ + endpoint: deployment.endpoint, + namespace: deployment.namespace, + poolName: deployment.pool, + token: deployment.token, +}); + +const notes = client.notes.getOrCreate(["shared"]); +await notes.add("Hello from the RivetKit client"); +console.log(await notes.list()); +// docs:end client + +await client.dispose(); diff --git a/vendor/dynamic-apps/examples/apps-sqlite/src/server.ts b/vendor/dynamic-apps/examples/apps-sqlite/src/server.ts new file mode 100644 index 00000000..32e26acc --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-sqlite/src/server.ts @@ -0,0 +1,21 @@ +import { serve } from "@hono/node-server"; +import { appsRouter, deployApp } from "@rivet-dev/dynamic-apps"; +import { Hono } from "hono"; + +const server = new Hono(); +server.all("/api/rivet/*", (c) => appsRouter.fetch(c.req.raw)); + +// An agent or upload endpoint would pass generated files here. This example +// deploys its checked-in fixture and returns the deployment to the caller. +server.post("/deploy/:name", async (c) => + c.json( + await deployApp({ + appId: c.req.param("name"), + source: new URL("../fixtures/app/", import.meta.url), + }), + ), +); + +server.route("/apps", appsRouter); + +serve({ fetch: server.fetch, port: Number(process.env.PORT ?? 3000) }); diff --git a/vendor/dynamic-apps/examples/apps-sqlite/tsconfig.json b/vendor/dynamic-apps/examples/apps-sqlite/tsconfig.json new file mode 100644 index 00000000..888653f3 --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-sqlite/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "noEmit": true, + "skipLibCheck": true + }, + "include": ["src", "fixtures/app/src"] +} diff --git a/vendor/dynamic-apps/examples/apps-static-website/fixtures/app/package.json b/vendor/dynamic-apps/examples/apps-static-website/fixtures/app/package.json new file mode 100644 index 00000000..2119d84b --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-static-website/fixtures/app/package.json @@ -0,0 +1,10 @@ +{ + "name": "static-website-app", + "version": "0.0.0", + "private": true, + "type": "module", + "main": "src/index.ts", + "dependencies": { + "hono": "4.13.3" + } +} diff --git a/vendor/dynamic-apps/examples/apps-static-website/fixtures/app/public/app.js b/vendor/dynamic-apps/examples/apps-static-website/fixtures/app/public/app.js new file mode 100644 index 00000000..f304d933 --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-static-website/fixtures/app/public/app.js @@ -0,0 +1,2 @@ +document.querySelector("#status").textContent = + "Served from a Dynamic Apps release inside agentOS."; diff --git a/vendor/dynamic-apps/examples/apps-static-website/fixtures/app/public/index.html b/vendor/dynamic-apps/examples/apps-static-website/fixtures/app/public/index.html new file mode 100644 index 00000000..4c9fae5c --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-static-website/fixtures/app/public/index.html @@ -0,0 +1,16 @@ + + + + + + Static site on Dynamic Apps + + + +
+

Static sites scale to zero too.

+

Loading JavaScript…

+
+ + + diff --git a/vendor/dynamic-apps/examples/apps-static-website/fixtures/app/public/styles.css b/vendor/dynamic-apps/examples/apps-static-website/fixtures/app/public/styles.css new file mode 100644 index 00000000..0bc9f4b4 --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-static-website/fixtures/app/public/styles.css @@ -0,0 +1,5 @@ +body { + font-family: system-ui, sans-serif; + margin: 4rem auto; + max-width: 40rem; +} diff --git a/vendor/dynamic-apps/examples/apps-static-website/fixtures/app/src/index.ts b/vendor/dynamic-apps/examples/apps-static-website/fixtures/app/src/index.ts new file mode 100644 index 00000000..f56526ae --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-static-website/fixtures/app/src/index.ts @@ -0,0 +1,28 @@ +import { readFile } from "node:fs/promises"; +import { Hono } from "hono"; + +const contentTypes: Record = { + ".html": "text/html; charset=utf-8", + ".css": "text/css; charset=utf-8", + ".js": "text/javascript; charset=utf-8", +}; + +const app = new Hono(); + +// Serve every file under public/. "/" maps to public/index.html. The build +// bundles this entrypoint next to public/, so paths resolve from the bundle. +app.get("/*", async (c) => { + const path = c.req.path.endsWith("/") + ? `${c.req.path}index.html` + : c.req.path; + const type = contentTypes[path.slice(path.lastIndexOf("."))]; + if (!type || path.includes("..")) return c.notFound(); + try { + const file = await readFile(new URL(`./public${path}`, import.meta.url)); + return c.body(file, 200, { "content-type": type }); + } catch { + return c.notFound(); + } +}); + +export default app; diff --git a/vendor/dynamic-apps/examples/apps-static-website/package.json b/vendor/dynamic-apps/examples/apps-static-website/package.json new file mode 100644 index 00000000..3e6e165b --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-static-website/package.json @@ -0,0 +1,20 @@ +{ + "name": "@rivet-dev/dynamic-apps-example-static-website", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "start": "npx tsx src/server.ts", + "check-types": "tsc --noEmit" + }, + "dependencies": { + "@hono/node-server": "^2.0.11", + "@rivet-dev/dynamic-apps": "workspace:*", + "hono": "^4.12.9" + }, + "devDependencies": { + "@types/node": "^22.19.15", + "tsx": "^4.20.6", + "typescript": "^5.7.3" + } +} diff --git a/vendor/dynamic-apps/examples/apps-static-website/src/server.ts b/vendor/dynamic-apps/examples/apps-static-website/src/server.ts new file mode 100644 index 00000000..d08f4a02 --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-static-website/src/server.ts @@ -0,0 +1,18 @@ +import { serve } from "@hono/node-server"; +import { appsRouter, deployApp } 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); + +// docs:start deploy +// Deploy the site directory. Its package.json and src/index.ts serve public/. +await deployApp({ + appId: "static-website", + source: new URL("../fixtures/app/", import.meta.url), +}); +// docs:end deploy + +serve({ fetch: server.fetch, port: Number(process.env.PORT ?? 3000) }); +console.log("Open http://localhost:3000/apps/static-website/"); diff --git a/vendor/dynamic-apps/examples/apps-static-website/tsconfig.json b/vendor/dynamic-apps/examples/apps-static-website/tsconfig.json new file mode 100644 index 00000000..888653f3 --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-static-website/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "noEmit": true, + "skipLibCheck": true + }, + "include": ["src", "fixtures/app/src"] +} diff --git a/vendor/dynamic-apps/examples/apps-workflows/fixtures/app/package.json b/vendor/dynamic-apps/examples/apps-workflows/fixtures/app/package.json new file mode 100644 index 00000000..fd1f12ca --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-workflows/fixtures/app/package.json @@ -0,0 +1,12 @@ +{ + "name": "order-workflow-app", + "version": "0.0.0", + "private": true, + "type": "module", + "main": "src/index.ts", + "dependencies": { + "@hono/node-server": "2.1.1", + "hono": "4.13.3", + "rivetkit": "2.3.11" + } +} diff --git a/vendor/dynamic-apps/examples/apps-workflows/fixtures/app/src/index.ts b/vendor/dynamic-apps/examples/apps-workflows/fixtures/app/src/index.ts new file mode 100644 index 00000000..3b5439e9 --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-workflows/fixtures/app/src/index.ts @@ -0,0 +1,48 @@ +import { serve } from "@hono/node-server"; +import { Hono } from "hono"; +import { actor, setup } from "rivetkit"; +import { workflow } from "rivetkit/workflow"; + +type Status = "placed" | "paid" | "shipped" | "delivered"; + +// The workflow runs when the actor is created. Each step is durable, so the +// actor can sleep, scale to zero, and resume exactly where it left off. +const order = actor({ + state: { status: "placed" as Status }, + actions: { + status: (c) => c.state.status, + }, + run: workflow(async (wf) => { + await wf.step("charge", async (c) => { + c.state.status = "paid"; + }); + await wf.step("ship", async (c) => { + c.state.status = "shipped"; + }); + await wf.sleep("in transit", 2_000); + await wf.step("deliver", async (c) => { + c.state.status = "delivered"; + }); + }), +}); + +export const registry = setup({ use: { order } }); + +const app = new Hono(); +app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +app.get("/", (c) => + c.json({ message: "Use the RivetKit client to read orders." }), +); + +// Dynamic Apps runs the actors in serverless mode and waits for this listener. +if (process.env.RIVETKIT_RUNTIME_MODE === "serverless") { + await new Promise((resolve, reject) => { + const server = serve( + { fetch: app.fetch, port: Number(process.env.PORT), hostname: "0.0.0.0" }, + () => resolve(), + ); + server.once("error", reject); + }); +} + +export default app; diff --git a/vendor/dynamic-apps/examples/apps-workflows/package.json b/vendor/dynamic-apps/examples/apps-workflows/package.json new file mode 100644 index 00000000..aa1d6191 --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-workflows/package.json @@ -0,0 +1,22 @@ +{ + "name": "@rivet-dev/dynamic-apps-example-workflows", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "start": "npx tsx src/server.ts", + "client": "npx tsx src/client.ts", + "check-types": "tsc --noEmit" + }, + "dependencies": { + "@hono/node-server": "^2.0.11", + "@rivet-dev/dynamic-apps": "workspace:*", + "hono": "^4.12.9", + "rivetkit": "2.3.11" + }, + "devDependencies": { + "@types/node": "^22.19.15", + "tsx": "^4.20.6", + "typescript": "^5.7.3" + } +} diff --git a/vendor/dynamic-apps/examples/apps-workflows/src/client.ts b/vendor/dynamic-apps/examples/apps-workflows/src/client.ts new file mode 100644 index 00000000..19204111 --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-workflows/src/client.ts @@ -0,0 +1,34 @@ +import type { deployApp } from "@rivet-dev/dynamic-apps"; +import { createClient } from "rivetkit/client"; +import type { registry } from "../fixtures/app/src/index.js"; + +// Deploy the app through the host server and read back its connection details. +const host = process.env.HOST_URL ?? "http://localhost:3000"; +const response = await fetch(`${host}/deploy/order-workflow`, { + method: "POST", +}); +if (!response.ok) throw new Error(`deploy failed: ${await response.text()}`); +const deployment = (await response.json()) as Awaited< + ReturnType +>; + +// docs:start client +const client = createClient({ + endpoint: deployment.endpoint, + namespace: deployment.namespace, + poolName: deployment.pool, + token: deployment.token, +}); + +// Creating the actor starts its workflow. Poll until it finishes. +const order = client.order.getOrCreate(["order-1042"]); +let status = await order.status(); +while (status !== "delivered") { + console.log("status", status); + await new Promise((resolve) => setTimeout(resolve, 500)); + status = await order.status(); +} +console.log("status", status); +// docs:end client + +await client.dispose(); diff --git a/vendor/dynamic-apps/examples/apps-workflows/src/server.ts b/vendor/dynamic-apps/examples/apps-workflows/src/server.ts new file mode 100644 index 00000000..32e26acc --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-workflows/src/server.ts @@ -0,0 +1,21 @@ +import { serve } from "@hono/node-server"; +import { appsRouter, deployApp } from "@rivet-dev/dynamic-apps"; +import { Hono } from "hono"; + +const server = new Hono(); +server.all("/api/rivet/*", (c) => appsRouter.fetch(c.req.raw)); + +// An agent or upload endpoint would pass generated files here. This example +// deploys its checked-in fixture and returns the deployment to the caller. +server.post("/deploy/:name", async (c) => + c.json( + await deployApp({ + appId: c.req.param("name"), + source: new URL("../fixtures/app/", import.meta.url), + }), + ), +); + +server.route("/apps", appsRouter); + +serve({ fetch: server.fetch, port: Number(process.env.PORT ?? 3000) }); diff --git a/vendor/dynamic-apps/examples/apps-workflows/tsconfig.json b/vendor/dynamic-apps/examples/apps-workflows/tsconfig.json new file mode 100644 index 00000000..888653f3 --- /dev/null +++ b/vendor/dynamic-apps/examples/apps-workflows/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "noEmit": true, + "skipLibCheck": true + }, + "include": ["src", "fixtures/app/src"] +}