Skip to content

fix(supabase): shrink Edge Function deploy 314MB→8MB via pre-bundled adapter + import-map redirect#5366

Merged
jog1t merged 1 commit into
mainfrom
claude/edge-bundle-slim
Jul 6, 2026
Merged

fix(supabase): shrink Edge Function deploy 314MB→8MB via pre-bundled adapter + import-map redirect#5366
jog1t merged 1 commit into
mainfrom
claude/edge-bundle-slim

Conversation

@jog1t

@jog1t jog1t commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Problem

Deploying a trivial state-only RivetKit actor to Supabase Edge Functions produces a ~314 MB Deno eszip and fails with HTTP 413. Supabase's edge-runtime bundle snapshots the entire declared npm dependency closure (not the reachable import graph), so importing rivetkit drags in its native packages — @rivetkit/engine-cli (~117 MB engine binary), @rivetkit/rivetkit-napi (~74 MB), and @rivet-dev/agent-os-core (secure-exec V8) — none of which the wasm runtime ever executes. That's ~245 MB (78%) of native ELF the edge deploy can't use.

Fix — pre-bundled adapter + import-map redirect (no source API change)

  • @rivetkit/supabase now bundles rivetkit's wasm-path code into its own dist and re-exports the authoring API (export * from "rivetkit"), declaring only @rivetkit/rivetkit-wasm (plus pino/cbor-x) at runtime — never the native packages.
  • The Supabase function's deno.json import map points "rivetkit"@rivetkit/supabase. User source stays import { actor } from "rivetkit"; Deno just resolves it to the native-free adapter.
  • rivetkit itself is unchanged → the Node / native runtime path is unaffected (zero regression).

Two Deno-bundling issues fixed along the way: pino's dynamic require("os") (kept external so Deno node-compat loads it), and unprefixed Node builtin imports (esbuild plugin rewrites them to node:).

Result (measured with Supabase's exact bundler; deployed + booting)

metric before after
eszip 314.91 MB 8.17 MB (−97%)
deploy "script size" 72 MB → HTTP 413 1.5 MB → Deployed
runtime boot HTTP 200
user source imports from "rivetkit" unchanged

Approaches measured and rejected: devDependencies (33 MB but breaks Node), optionalDependencies (619 MB — Deno snapshots optionals), export conditions (617 MB — conditions pick the entry module, not the installed deps).

Guard rails — CI job RivetKit / Edge Bundle Budget

The edge deploy can regress two independent ways; the job guards both (plus a size budget):

  1. Dependency closurescripts/ci/check-edge-native-closure.mjs fails if rivetkit-napi / engine-cli / agent-os-core re-enter the adapter's production dependency tree.
  2. Literal native import in the bundle — rivetkit-core loads these via a computed import(["@rivetkit","rivetkit-napi"].join("/")) that Deno can't statically resolve. If that's ever "simplified" to a literal import("@rivetkit/rivetkit-napi"), Deno's eszip would statically snapshot it again — a regression neither the closure check nor size-limit can see (the literal import stays external, changing no dependencies field and not inflating the bundle). The same script now also greps the built dist/mod.mjs and fails on any literal import()/require/from of a forbidden package. The array-join sites in napi-runtime.ts/native.ts carry LOAD-BEARING comments so the obfuscation isn't cleaned up by mistake. (Added in response to the automated review.)
  3. Size budgetsize-limit on dist/mod.mjs.

Notes

  • The same adapter pattern extends to the vercel / firebase / cloudflare-workers adapters (not included here).
  • rivetkit is untouched, so the Node/native/self-host path is unaffected.

🤖 Generated with Claude Code

@railway-app railway-app Bot temporarily deployed to rivet-frontend / rivet-pr-5366 July 3, 2026 17:19 Destroyed
@claude

claude Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review

This PR shrinks the Supabase Edge Function deploy by pre-bundling rivetkit's wasm-path code into @rivetkit/supabase's own dist (via a custom tsup.config.ts) and moving rivetkit from a runtime dependency to a devDependency, so Deno's eszip bundler never snapshots the native packages (rivetkit-napi, engine-cli, agent-os-core). It adds a CI job (RivetKit / Edge Bundle Budget) that runs size-limit on the bundle plus a custom denylist script (check-edge-native-closure.mjs), and updates the Supabase docs with the required deno.json import map.

Overall this is a well-reasoned fix with good measurement in the PR description. One structural concern surfaced from tracing the actual module graph:

rivetkit-typescript/packages/supabase/tsup.config.ts:26-30, scripts/ci/check-edge-native-closure.mjs — the guard rails protect the wrong layer; the real protection is an unasserted implementation detail in rivetkit-core.

Tracing the static import graph from rivetkit's mod.ts, none of the three forbidden packages are actually reachable via a static import today, but not because of the external: [...] list added in this PR. registry/napi-runtime.ts:835 and registry/native.ts:624-625 load @rivetkit/rivetkit-napi / @rivetkit/engine-cli via import(["@rivetkit", "rivetkit-napi"].join("/")), a computed specifier that's opaque to esbuild's/Deno's static analysis. @rivet-dev/agent-os-core is simply unreachable from mod.ts (it's a separate subpath export). The external entries for these three packages in tsup.config.ts are therefore currently a no-op: esbuild never encounters them as static specifiers to bundle or leave external in the first place.

This means the actual invariant this whole PR depends on is "rivetkit-core's dynamic imports of these native packages stay non-literal/obfuscated," and that invariant isn't asserted anywhere. If a future change to napi-runtime.ts or native.ts rewrites import(["@rivetkit","rivetkit-napi"].join("/")) to the more natural import("@rivetkit/rivetkit-napi") (a plausible "cleanup" a future contributor might make, since the array-join looks unmotivated without this context), esbuild would still leave it as a dynamic import() with a literal string, which Deno's eszip bundler (like most deploy-time bundlers) can and typically does statically resolve and snapshot, silently reintroducing the exact 314 MB / 413 regression this PR fixes.

Neither of this PR's two new guards would catch that regression before it reaches a user's deploy:

  • check-edge-native-closure.mjs walks pnpm ls --prod's declared dependency graph, which only catches the problem if a forbidden package is re-added to some package's dependencies field. A literal dynamic import inside rivetkit's existing (devDependency, prod-tree-invisible) source wouldn't change any dependencies field, so this check would still pass.
  • size-limit budgets dist/mod.mjs's bundled byte size. A literal dynamic import() left as external isn't inlined into the bundle, so the file size wouldn't meaningfully change either.

Worth considering a guard that directly asserts the thing that matters: grep the built dist/mod.mjs (or dist/mod.js) for the literal package names (@rivetkit/rivetkit-napi, @rivetkit/engine-cli, @rivet-dev/agent-os-core) and fail if found inside an import(...) call, or at minimum a comment at the array-join sites in napi-runtime.ts/native.ts explaining that the obfuscation is load-bearing for edge bundling and must not be "simplified" to a literal specifier.

Minor

  • The PR description mentions regenerated BARE codecs (client-protocol v3/v4, inspector v2-v4) as included "along the way," but no such changes appear in the diff for this PR. Worth double-checking the description matches the final pushed commits before merge.

Nothing else surfaced that looked like a live bug. The dependency reshuffling in package.json/pnpm-lock.yaml, the node:-prefix esbuild plugin (correctly scoped to bare specifiers matching node:module's builtinModules, not all bare imports), and the docs updates all look correct and self-consistent with the stated approach.

@jog1t jog1t force-pushed the claude/edge-bundle-slim branch from 95afa32 to fb63356 Compare July 3, 2026 17:44
@railway-app railway-app Bot temporarily deployed to rivet-frontend / rivet-pr-5366 July 3, 2026 17:44 Destroyed
@railway-app

railway-app Bot commented Jul 3, 2026

Copy link
Copy Markdown

🚅 Deployed to the rivet-pr-5366 environment in rivet-frontend

Service Status Web Updated (UTC)
website 😴 Sleeping (View Logs) Web Jul 3, 2026 at 9:46 pm
kitchen-sink 😴 Sleeping (View Logs) Web Jul 3, 2026 at 9:43 pm
frontend-cloud 😴 Sleeping (View Logs) Web Jul 3, 2026 at 9:34 pm
mcp-hub ✅ Success (View Logs) Web Jul 3, 2026 at 9:34 pm
frontend-inspector 😴 Sleeping (View Logs) Web Jul 3, 2026 at 9:30 pm
ladle ✅ Success (View Logs) Web Jul 3, 2026 at 8:36 pm

@jog1t jog1t force-pushed the claude/edge-bundle-slim branch from fb63356 to a6c7339 Compare July 3, 2026 18:09
@railway-app railway-app Bot temporarily deployed to rivet-frontend / rivet-pr-5366 July 3, 2026 18:09 Destroyed
@jog1t jog1t force-pushed the claude/edge-bundle-slim branch from a6c7339 to ef9730e Compare July 3, 2026 18:35
@railway-app railway-app Bot temporarily deployed to rivet-frontend / rivet-pr-5366 July 3, 2026 18:35 Destroyed
@jog1t jog1t force-pushed the claude/edge-bundle-slim branch from ef9730e to e115953 Compare July 3, 2026 19:11
@railway-app railway-app Bot temporarily deployed to rivet-frontend / rivet-pr-5366 July 3, 2026 19:11 Destroyed
@jog1t jog1t force-pushed the claude/edge-bundle-slim branch from e115953 to b06e8d0 Compare July 3, 2026 19:39
@railway-app railway-app Bot temporarily deployed to rivet-frontend / rivet-pr-5366 July 3, 2026 19:39 Destroyed
@jog1t jog1t force-pushed the claude/edge-bundle-slim branch from b06e8d0 to c6cd3c1 Compare July 3, 2026 20:05
@railway-app railway-app Bot temporarily deployed to rivet-frontend / rivet-pr-5366 July 3, 2026 20:06 Destroyed
@jog1t jog1t force-pushed the claude/edge-bundle-slim branch from c6cd3c1 to fe26d34 Compare July 3, 2026 20:35
@railway-app railway-app Bot temporarily deployed to rivet-frontend / rivet-pr-5366 July 3, 2026 20:35 Destroyed
@jog1t jog1t force-pushed the claude/edge-bundle-slim branch from fe26d34 to 0aa573b Compare July 3, 2026 21:23
@railway-app railway-app Bot temporarily deployed to rivet-frontend / rivet-pr-5366 July 3, 2026 21:23 Destroyed
@jog1t jog1t changed the title chore(rivetkit): move native-only deps to devDependencies (edge bundle measurement) fix(supabase): shrink Edge Function deploy 314MB→8MB via pre-bundled adapter + import-map redirect Jul 3, 2026
@jog1t jog1t force-pushed the claude/edge-bundle-slim branch from 0aa573b to af1d3da Compare July 3, 2026 21:34
@railway-app railway-app Bot temporarily deployed to rivet-frontend / rivet-pr-5366 July 3, 2026 21:34 Destroyed
@jog1t

jog1t commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Parking this to prioritize slimming the engine binary/frontend first (the engine binary is ~70% embedded dashboard, ~47% sourcemaps). Branch claude/edge-bundle-slim is preserved and can be reopened; note it also carries a standalone fix regenerating stale BARE codecs that unblocks a clean rivetkit build on main.

@jog1t jog1t closed this Jul 3, 2026
@jog1t jog1t reopened this Jul 6, 2026
@jog1t jog1t force-pushed the claude/edge-bundle-slim branch from af1d3da to 47c7ce4 Compare July 6, 2026 18:40
@railway-app railway-app Bot temporarily deployed to rivet-frontend / rivet-pr-5366 July 6, 2026 18:40 Destroyed
…ee edge deploy) + edge bundle-budget CI + docs; regen stale BARE codecs
@jog1t jog1t force-pushed the claude/edge-bundle-slim branch from 47c7ce4 to 14a9ad2 Compare July 6, 2026 19:10
@railway-app railway-app Bot temporarily deployed to rivet-frontend / rivet-pr-5366 July 6, 2026 19:11 Destroyed
@jog1t jog1t merged commit 8f76147 into main Jul 6, 2026
14 of 18 checks passed
@jog1t jog1t deleted the claude/edge-bundle-slim branch July 6, 2026 19:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant