| 1 | --- |
| 2 | title: Schemaless RPC |
| 3 | description: Typed RPC from a Lambda Function into a MicroVM with no schema — declare the image's Shape, connect, get the client. |
| 4 | sidebar: |
| 5 | order: 2 |
| 6 | --- |
| 7 | |
| 8 | The pattern — what a Shape member may be, how the typed client arises, what |
| 9 | crosses the wire — lives at [Schemaless RPC](/apis/schemaless). This page |
| 10 | covers the AWS pairings. There is one today: **Lambda → Firecracker MicroVM**, |
| 11 | riding the generic fetch transport — each call is a |
| 12 | `POST https://<endpoint>/__rpc__/{name}`. |
| 13 | |
| 14 | ## The image declares its Shape |
| 15 | |
| 16 | ```typescript |
| 17 | // sandbox.ts — the MicroVM image |
| 18 | import * as AWS from "alchemy/AWS"; |
| 19 | import * as Effect from "effect/Effect"; |
| 20 | import * as HttpServerResponse from "effect/http/HttpServerResponse"; |
| 21 | |
| 22 | export class Sandbox extends AWS.Lambda.MicrovmImage< |
| 23 | Sandbox, |
| 24 | { |
| 25 | hello: (message: string) => Effect.Effect<string>; |
| 26 | } |
| 27 | >()("MicrovmSandbox") {} |
| 28 | |
| 29 | export default Sandbox.make( |
| 30 | { main: import.meta.filename /* buildRole, resources, cpuConfigurations */ }, |
| 31 | Effect.gen(function* () { |
| 32 | return { |
| 33 | fetch: Effect.gen(function* () { |
| 34 | return HttpServerResponse.text("hello from effectful microvm"); |
| 35 | }), |
| 36 | hello: (message: string) => Effect.succeed(`hello, ${message}!`), |
| 37 | }; |
| 38 | }), |
| 39 | ); |
| 40 | ``` |
| 41 | |
| 42 | Like Containers on Cloudflare, the MicroVM image declares its Shape as an |
| 43 | explicit type parameter — the caller reaches it over the network, so nothing |
| 44 | can be inferred from a binding. The constructor Effect returns `{ fetch, ...rpcs }`; |
| 45 | the in-VM runtime serves the RPC methods over `/__rpc__/*` and falls through |
| 46 | to `fetch` for everything else, so the same image answers both call surfaces. |
| 47 | |
| 48 | ## Provision a VM and mint a token |
| 49 | |
| 50 | ```typescript |
| 51 | // orchestrator.ts — the Lambda binds the instance operations to the image |
| 52 | const runMicrovm = yield* AWS.Lambda.RunMicrovm(Sandbox); |
| 53 | const getMicrovm = yield* AWS.Lambda.GetMicrovm(Sandbox); |
| 54 | const createAuthToken = yield* AWS.Lambda.CreateAuthToken(Sandbox); |
| 55 | const terminateMicrovm = yield* AWS.Lambda.TerminateMicrovm(Sandbox); |
| 56 | |
| 57 | // ...inside a handler: run, wait for RUNNING, mint a token |
| 58 | const vm = yield* runMicrovm({ |
| 59 | idlePolicy: { |
| 60 | maxIdleDurationSeconds: 900, |
| 61 | suspendedDurationSeconds: 300, |
| 62 | autoResumeEnabled: true, |
| 63 | }, |
| 64 | }); |
| 65 | |
| 66 | yield* getMicrovm({ microvmIdentifier: vm.microvmId }).pipe( |
| 67 | Effect.flatMap((m) => |
| 68 | m.state === "RUNNING" |
| 69 | ? Effect.void |
| 70 | : Effect.fail(new Error(`microvm ${m.state}`)), |
| 71 | ), |
| 72 | Effect.retry({ schedule: Schedule.spaced("2 seconds"), times: 30 }), |
| 73 | ); |
| 74 | |
| 75 | const { authToken } = yield* createAuthToken({ |
| 76 | microvmIdentifier: vm.microvmId, |
| 77 | expirationInMinutes: 5, |
| 78 | allowedPorts: [{ port: 8080 }], |
| 79 | }); |
| 80 | ``` |
| 81 | |
| 82 | Unlike a service binding, a MicroVM has a lifecycle: the Lambda binds the |
| 83 | instance operations (`RunMicrovm`, `GetMicrovm`, `CreateAuthToken`, |
| 84 | `TerminateMicrovm`), launches an instance, polls with a bounded retry until it |
| 85 | reaches `RUNNING`, and mints a short-lived auth token scoped to the RPC port. |
| 86 | Wrap the launched instance in `Effect.ensuring(terminateMicrovm(...))` so a |
| 87 | failing step never leaks a running VM. The full lifecycle — suspend, resume, |
| 88 | list, the raw `fetch` route via `microvmAuthHeaders` — is in |
| 89 | [MicroVMs](/aws/compute/microvms#launch-and-drive-microvms-from-a-lambda). |
| 90 | |
| 91 | ## Connect and call |
| 92 | |
| 93 | ```typescript |
| 94 | const sandbox = yield* AWS.Lambda.connectMicrovm(Sandbox, { |
| 95 | endpoint: vm.endpoint, |
| 96 | authToken, |
| 97 | }); |
| 98 | const reply = yield* sandbox.hello(message).pipe( |
| 99 | Effect.retry({ |
| 100 | schedule: Schedule.exponential("500 millis"), |
| 101 | times: 8, |
| 102 | }), |
| 103 | ); |
| 104 | // reply === `hello, ${message}!` |
| 105 | ``` |
| 106 | |
| 107 | `connectMicrovm` returns the typed stub inferred from `Sandbox` — each method |
| 108 | call is a `POST https://<endpoint>/__rpc__/<method>` with the auth-token |
| 109 | entries set as request headers, the mirror of the server running inside the |
| 110 | VM. Retry the first call: the endpoint takes a few seconds to start serving |
| 111 | after the VM reports `RUNNING`. |
| 112 | |
| 113 | ## What AWS doesn't have yet |
| 114 | |
| 115 | There is no schemaless stub between Lambda Functions — Lambda-to-Lambda calls |
| 116 | go through the `AWS.Lambda.InvokeFunction` binding (see |
| 117 | [Lambda](/aws/compute/lambda)) or a Function URL, and typed cross-Lambda APIs |
| 118 | should use [Effect RPC](/apis/effect-rpc). There is also no typed connector |
| 119 | for [ECS Tasks](/aws/compute/ecs) yet — a Task returns the same |
| 120 | `{ fetch, ...rpcs }` Shape, but with no `connectMicrovm` equivalent you reach |
| 121 | it over plain HTTP through its load balancer or serve Effect RPC from its |
| 122 | `fetch` handler. |
| 123 | |
| 124 | ## When you need schemas |
| 125 | |
| 126 | Schemaless calls do no runtime validation — nothing decodes or sanitizes what |
| 127 | arrives. At a trust boundary, or when class identity must survive the hop, use |
| 128 | [Effect RPC](/apis/effect-rpc). |
| 129 | |
| 130 | ## Where next |
| 131 | |
| 132 | - [Schemaless RPC](/apis/schemaless) — the pattern: allowed members, the wire |
| 133 | codec, streams, errors |
| 134 | - [MicroVMs](/aws/compute/microvms) — build images, drive instances, both call |
| 135 | surfaces |
| 136 | - [Effect RPC](/apis/effect-rpc) — schema-first RPC for trust boundaries |
| 137 | - [Cloudflare Schemaless RPC](/cloudflare/apis/schemaless-rpc) — the Cloudflare |
| 138 | pairings: Workers, Durable Objects, Containers |