{"id":48355163,"url":"https://github.com/lofcz/1tube","last_synced_at":"2026-06-26T01:01:06.175Z","repository":{"id":349314661,"uuid":"1186690041","full_name":"lofcz/1tube","owner":"lofcz","description":"Serverless with zero vendor lock-in.","archived":false,"fork":false,"pushed_at":"2026-06-25T22:54:10.000Z","size":1424,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-25T23:05:11.043Z","etag":null,"topics":["aws-lambda","bun","deno","edge-functions","lambda-functions","serverless","serverless-functions","supabase","supabase-functions","vercel"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lofcz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-19T22:22:32.000Z","updated_at":"2026-06-25T22:54:14.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/lofcz/1tube","commit_stats":null,"previous_names":["lofcz/1tube"],"tags_count":61,"template":false,"template_full_name":null,"purl":"pkg:github/lofcz/1tube","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lofcz%2F1tube","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lofcz%2F1tube/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lofcz%2F1tube/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lofcz%2F1tube/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lofcz","download_url":"https://codeload.github.com/lofcz/1tube/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lofcz%2F1tube/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34798183,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-25T02:00:05.521Z","response_time":101,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws-lambda","bun","deno","edge-functions","lambda-functions","serverless","serverless-functions","supabase","supabase-functions","vercel"],"created_at":"2026-04-05T11:01:38.844Z","updated_at":"2026-06-26T01:01:06.141Z","avatar_url":"https://github.com/lofcz.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![NPM Version](https://img.shields.io/npm/v/1tube)\n![NuGet Version](https://img.shields.io/nuget/v/OneTube?v=2\u0026icon=nuget)\n\n# 1tube\n\nSelf-hosted Supabase Edge Functions gateway. Runs your Deno edge functions\nlocally or behind a .NET host with zero-copy YARP proxying — no cold starts, no\nversion lock, no Supabase compute dependency.\n\n## How it works\n\n1tube discovers edge function modules from a `supabase/functions/` directory and\nhosts them in a single Deno HTTP server. Each function's `serve()` call\nregisters a handler in a global registry instead of starting a separate\n`Deno.serve()`. The gateway then routes requests, handles JWT auth, CORS, rate\nlimiting, and structured logging.\n\nIndividual edge functions require **zero changes** — only the shared\n`_shared/handler.ts` wrapper needs a small shim (4 lines) to detect the 1tube\nregistry.\n\n## Quick start (local dev)\n\n```bash\n# Install dependencies\nbun install\n\n# Copy and fill in env vars\ncp .env.example .env\n\n# Start with auto-restart on file changes\nbun run dev -- --functions ../sciobot-next/supabase/functions\n```\n\nThe gateway starts on `http://localhost:3100`. Functions are available at\n`http://localhost:3100/functions/v1/\u003cname\u003e`.\n\nIn dev mode, 1tube also watches the functions directory and reloads handlers\nin-process on filesystem changes (including newly created function folders).\n\n## npm CLI\n\nThe published npm package exposes a `1tube` binary. It is a tiny Node shim that\nlaunches this repo's Deno CLI, so machines using it need Deno on `PATH` (set\n`DENO_BIN` to point at a specific `deno` if it isn't). **Prefer this launcher\nover a hand-rolled `deno run … src/server.ts` line** — it applies 1tube's\nrecommended defaults (see [minimum dependency age](#minimum-dependency-age)\nbelow) before Deno starts, which a flag added _inside_ the gateway can no longer\ndo.\n\n```bash\n# Show CLI help\nnpx 1tube\n\n# Build and sign a firmware payload in one command\nnpx 1tube package --functions supabase/functions --out fw.1tube --sign-key \"$1TUBE_PACKAGE_SIGN_KEY\"\n\n# Keep/update the intermediate dist/ artifact as well\nnpx 1tube package --functions supabase/functions --in dist --out fw.1tube --sign-key \"$1TUBE_PACKAGE_SIGN_KEY\"\n```\n\nThis replaces local-source invocations such as\n`deno run -A ../1tube/src/cli.ts ...` in downstream CI pipelines.\n\n### Passing deno flags through the launcher\n\nArguments before a standalone `--` are 1tube CLI args; everything **after** `--`\nis forwarded verbatim to `deno run` (inserted before the entrypoint). This lets\nthe launcher fully replace a project's bespoke `deno run … src/server.ts`\ncommand — including project-level deno flags like `--config`, `--env-file`,\n`--node-modules-dir` and `--no-lock`:\n\n```bash\nnpx 1tube serve --functions ./supabase/functions --dev --hmr \\\n  -- --no-lock --env-file=.env --config ./supabase/functions/deno.json --node-modules-dir=false\n```\n\n- If your passthrough includes its own `--config`, the launcher does **not** add\n  1tube's bundled `deno.json` — your import map wins (the gateway's own sources\n  use fully-qualified `npm:`/`jsr:` specifiers, so they resolve regardless).\n- Prefer keeping the command line clean? Set the same flags in\n  `1TUBE_DENO_ARGS` (space-separated) instead of after `--`. CLI passthrough\n  takes precedence on any duplicate flag.\n\n### Minimum dependency age\n\nDeno 2.9 turns on a **24-hour minimum dependency age** by default: a function\nthat pins a just-published npm version fails to resolve locally with\n_\"… is newer than the specified minimum dependency date\"_. Because Deno\nsnapshots npm config at process startup, this can only be changed by the\nlauncher, not from inside the running gateway.\n\nThe `1tube` launcher (and the .NET host) therefore export\n`NPM_CONFIG_MIN_RELEASE_AGE=0` for the Deno child **unless you've already set\nit**. That env var is the lowest explicit tier in Deno's precedence chain\n(`--minimum-dependency-age` \u003e `deno.json` `minimumDependencyAge` \u003e `.npmrc`\n`min-release-age` \u003e this env var \u003e the 24h built-in default), so it cancels\n_only_ the new built-in default — any age you configure through `.npmrc`,\n`deno.json`, or the CLI flag still wins. To re-enable a guard, set\n`NPM_CONFIG_MIN_RELEASE_AGE` (e.g. `24h`) yourself or use any higher-precedence\nmechanism.\n\n### Deno npm lifecycle scripts\n\nDeno does not run npm `postinstall` / build scripts unless you allow them.\n`deno.lock` may still list transitive packages such as `protobufjs`; the `check`\nscript uses `--allow-scripts=npm:protobufjs` so `bun run check` / `npm publish`\n(via `prepublishOnly`) stays warning-free. That script is only needed for\ndependency install — **1tube’s runtime graph is just Hono + JSR std**.\n\n## Endpoints\n\n| Path                       | Description                                                                                                                                         |\n| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `GET /`                    | Liveness probe (`{\"status\":\"ok\"}`) — intentionally minimal so unauthenticated callers don't see the function map.                                   |\n| `POST /functions/v1/:name` | Invoke an edge function                                                                                                                             |\n| `GET /health`              | Auth-gated health (`Authorization: Bearer $INTERNAL_KEY`); without auth returns the same minimal `{\"status\":\"ok\"}`.                                 |\n| `GET /metrics`             | Auth-gated Prometheus exposition (same scheme).                                                                                                     |\n| `GET /1tube/warmup`        | Boot progress (deferred boot): `{ready, total, loaded, loading, queued, failed}`. CORS-enabled so frontends can poll it to drive a warm-up overlay. |\n\n## Configuration\n\nAll knobs default to safe-but-backwards-compatible values. The TS gateway\n(`src/server.ts`) reads:\n\n| Env / flag                                                  | Default                | Notes                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |\n| ----------------------------------------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `--port` / `PORT`                                           | `3100`                 | Listen port                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |\n| `--host` / `1TUBE_HOST`                                     | `127.0.0.1`            | Loopback by default — pass `--host 0.0.0.0` to expose.                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |\n| `--functions` / `FUNCTIONS_PATH`                            | `./supabase/functions` | Functions root                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |\n| `--timeout` / `FUNCTION_TIMEOUT_MS`                         | `150000`               | Per-request wall-clock cap (also overridable per function)                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |\n| `--dev` / `1TUBE_DEV`                                       | off                    | Applies the well-known local Supabase JWT/secrets. **Refuses to start in prod when JWT_SECRET / SUPABASE_SERVICE_ROLE_KEY are missing or are the public dev defaults.**                                                                                                                                                                                                                                                                                                                                                          |\n| `--hmr` / `1TUBE_HMR`                                       | off                    | File-watch + per-function reload (dev only).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |\n| `--defer-boot` / `1TUBE_DEFER_BOOT`                         | on in dev/HMR          | Deferred boot (Deno backend only): the gateway accepts requests immediately while function Workers spawn in the background. Requests for cold functions jump the boot queue and get `503` + `X-1tube-Warming: 1` until ready. Disable with `--no-defer-boot` / `1TUBE_DEFER_BOOT=0`. See [Deferred boot \u0026 warm-up overlay](#deferred-boot--warm-up-overlay).                                                                                                                                                                     |\n| `--warmup-grace-ms` / `1TUBE_WARMUP_GRACE_MS`               | `250`                  | How long the dispatcher waits for a warming function before answering with the warming 503. Keeps fast-loading functions from flashing the frontend overlay.                                                                                                                                                                                                                                                                                                                                                                     |\n| `1TUBE_HMR_FRESH_WAIT_MS`                                   | `2500`                 | How long a request waits for an in-flight HMR respawn of its function before falling back to the previous (stale) worker. Fresh code wins by default (Vite-style \"request stalls until the rebuild lands\"). Stale fallbacks carry `X-1tube-Stale: 1`. `0` = never wait.                                                                                                                                                                                                                                                          |\n| `1TUBE_BODY_LIMIT_MB`                                       | `30`                   | Hono `bodyLimit`; matches Supabase. Returns 413 before the handler runs.                                                                                                                                                                                                                                                                                                                                                                                                                                                         |\n| `1TUBE_BODY_READ_MS`                                        | `30000`                | Slow-loris guard. Max idle gap (ms) between body chunks before the request is aborted with **408**. NOT a total body-read deadline — large but fast uploads pass through. Set `0` to disable.                                                                                                                                                                                                                                                                                                                                    |\n| `1TUBE_CORS_ORIGIN`                                         | `*` (dev only)         | Comma-separated allowlist or `*`. In prod, leaving unset disables CORS.                                                                                                                                                                                                                                                                                                                                                                                                                                                          |\n| `1TUBE_CORS_ALLOW_HEADERS`                                  | Supabase defaults      | Comma-separated `Access-Control-Allow-Headers`. Replaces the built-in default list (`authorization, x-client-info, apikey, content-type`, …) when set.                                                                                                                                                                                                                                                                                                                                                                            |\n| `1TUBE_CORS_ALLOW_METHODS`                                  | Supabase defaults      | Comma-separated `Access-Control-Allow-Methods` (default `GET, POST, PUT, PATCH, DELETE, OPTIONS`).                                                                                                                                                                                                                                                                                                                                                                                                                                |\n| `1TUBE_CORS_EXPOSE_HEADERS`                                 | 1tube internals        | Comma-separated `Access-Control-Expose-Headers`. **Merged with** (not replacing) the headers 1tube must always expose (e.g. `X-1tube-Warming`).                                                                                                                                                                                                                                                                                                                                                                                   |\n| `1TUBE_CORS_MAX_AGE`                                        | unset                  | Seconds for `Access-Control-Max-Age` (preflight cache). Omitted when unset.                                                                                                                                                                                                                                                                                                                                                                                                                                                       |\n| `1TUBE_CORS_ALLOW_CREDENTIALS`                              | off                    | `1`/`true` emits `Access-Control-Allow-Credentials: true`. Per the Fetch spec this is incompatible with `Origin: *`, so when credentials are on, a literal `*` allowlist is rejected — set an explicit origin.                                                                                                                                                                                                                                                                                                                     |\n| `--route-prefix` / `1TUBE_ROUTE_PREFIX`                     | `/functions/v1`        | URL prefix functions are mounted under. Change it to e.g. `/api` to serve at `/api/\u003cname\u003e`. Normalized to a leading-slash, no-trailing-slash form; logging and rate-limiting follow it automatically.                                                                                                                                                                                                                                                                                                                              |\n| `1TUBE_TRUSTED_PROXIES`                                     | empty                  | Comma-separated list of remote IPs whose `X-Forwarded-For` is honored. Anything else uses the raw socket address — XFF spoofing no longer mints fresh rate-limit buckets.                                                                                                                                                                                                                                                                                                                                                        |\n| `1TUBE_SHUTDOWN_GRACE_MS`                                   | `10000`                | SIGINT/SIGTERM drain budget.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |\n| `INTERNAL_KEY`                                              | unset                  | Required to read detailed `/health` and `/metrics`. Header-only: `Authorization: Bearer $INTERNAL_KEY`.                                                                                                                                                                                                                                                                                                                                                                                                                          |\n| `--backend` / `1TUBE_BACKEND`                               | `deno`                 | Function execution engine. `deno` (default) imports each function as a module in this process — same as 1tube has always done. `workerd` bundles each function and serves it from a Cloudflare-style workerd subprocess for hard isolation. See [Workerd backend](#workerd-backend) below.                                                                                                                                                                                                                                       |\n| `--workerd-bin` / `1TUBE_WORKERD_BIN`                       | `workerd` (PATH)       | Path to the workerd binary. Only consulted with `--backend workerd`.                                                                                                                                                                                                                                                                                                                                                                                                                                                             |\n| `--workerd-env` / `1TUBE_WORKERD_ENV`                       | unset (= forward all)  | Comma-separated allowlist of env var names to forward to bundled functions under `--backend workerd`. Each name reaches the bundle via `Deno.env.get(name)` and the worker's `env` binding. Values stay in the gateway's process env — they are NEVER written into `config.capnp`. **When unset (or `*`), every env var the gateway can see is forwarded** — bundled functions inherit their parent's environment like a regular `deno run` child. Pass an explicit list to narrow the surface for shared/multi-tenant workerds. |\n| `--kill-stale-workerd` / `1TUBE_KILL_STALE_WORKERD`         | off                    | When the boot-time port preflight finds one of workerd's socket ports already busy, automatically run `taskkill /F /IM workerd.exe` (Windows) or `pkill -9 -x workerd` (Unix), wait briefly, and re-probe. Only ever targets processes named `workerd` — non-workerd processes holding the port still surface the normal preflight error. Off by default; recommended for dev where leftover workerds from prior runs are the common cause of flaky boots.                                                                       |\n| `--inspector` / `--inspector-addr=ADDR` / `1TUBE_INSPECTOR` | off                    | Launch workerd with the V8 inspector bound to `ADDR` (default `127.0.0.1:9229` when `--inspector` has no value). Lets Chrome DevTools / Node-style debuggers attach for breakpoints inside bundled functions. **Local dev only** — opens an unauthenticated debug port. See [V8 inspector / debugger access](#v8-inspector--debugger-access) below.                                                                                                                                                                              |\n| `1TUBE_SHUTDOWN_GRACE_MS`                                   | `10000`                | Total wall-clock budget (ms) for shutdown, split between (1) draining gateway in-flight requests and (2) tearing down the workerd subprocess. See [Graceful shutdown](#graceful-shutdown) below.                                                                                                                                                                                                                                                                                                                                 |\n| `1TUBE_DEFAULT_RPM`                                         | `120`                  | Override the gateway-wide default rate limit (requests per minute, per IP, per function). Per-function `1tube.json#rpm` still wins.                                                                                                                                                                                                                                                                                                                                                                                              |\n| `1TUBE_DISABLE_RATE_LIMIT`                                  | unset                  | Set to `1` to bypass rate limiting entirely. **Load-test / dev only** — production deployments must keep the limiter on. The gateway prints a clear warning at boot when this is enabled.                                                                                                                                                                                                                                                                                                                                        |\n| `NPM_CONFIG_MIN_RELEASE_AGE`                               | `0` (set by launcher)  | Deno 2.9's minimum dependency age. The `1tube` launcher and .NET host export `0` for the Deno child unless you set it, cancelling only Deno's new 24h default. See [Minimum dependency age](#minimum-dependency-age). Must be set _before_ Deno starts — a gateway flag can't change it.                                                                                                                                                                                                                                            |\n\n````\n## Deferred boot \u0026 warm-up overlay\n\nOn the Deno backend, eager boot pays the full cost of spawning one Worker per\nfunction (each transpiling + evaluating its whole import graph) **before** the\ngateway accepts a single request. With dozens of functions importing heavy\ndeps, that's tens of seconds of staring at a boot log.\n\nDeferred boot (default whenever `--dev` or `--hmr` is on) flips the order:\n\n1. Functions are **discovered instantly** and registered as known-but-queued —\n   the gateway starts serving right away. Time-to-first-request drops from\n   \"sum of all worker spawns\" to effectively zero.\n2. Workers spawn in the background with the usual bounded concurrency\n   (`--deno-worker-concurrency`, default 8), **most-recently-used first**:\n   the queue is ordered by each function's last invocation time from the\n   invocation log store, so the functions you were actually hitting before\n   the restart are warm within the first few seconds and the long tail of\n   rarely-called functions loads last. First run (empty store) falls back\n   to name order.\n3. A request for a function whose Worker isn't ready yet **bumps it to the\n   front of the queue** and spawns it immediately, out-of-band. If it becomes\n   ready within the grace window (`--warmup-grace-ms`, default 250 ms) the\n   request is served normally and the client never notices. Otherwise the\n   gateway answers with the **warming contract** below, and the rest of the\n   queue keeps loading in the background.\n\n### The warming contract\n\n```http\nHTTP/1.1 503 Service Unavailable\nX-1tube-Warming: 1\nRetry-After: 1\n\n{\"error\":\"Backend is warming up\",\"reason\":\"function_warming\",\"function\":\"hello\",\n \"state\":\"loading\",\"warmup\":{\"total\":53,\"ready\":12,\"loading\":8,\"queued\":33,\"failed\":0}}\n```\n\n`X-1tube-Warming: 1` is the discriminator — a plain 503 (circuit breaker,\noverload) never carries it. It's in `Access-Control-Expose-Headers`, so browser\nJS can read it cross-origin. This is what lets a frontend distinguish \"the\nbackend is still warming up\" from real latency or a real outage and show an\nhonest overlay instead of a spinner.\n\n`GET /1tube/warmup` reports overall progress (also CORS-enabled), e.g. for a\nprogress bar inside the overlay.\n\n### Frontend helper\n\nThe npm package ships a tiny client (`1tube/warmup-client`, no dependencies):\n\n```ts\nimport { createWarmupFetch } from \"1tube/warmup-client\";\n\nconst warmFetch = createWarmupFetch({\n  onWarmingChange: (warming) =\u003e setShowWarmupOverlay(warming), // drive the overlay\n  retryDelayMs: 400,\n  maxWaitMs: 120_000,\n});\n\n// Drop-in fetch replacement: retries through warm-up transparently.\nconst resp = await warmFetch(`${GATEWAY}/functions/v1/hello`, { method: \"POST\" });\n```\n\n`isWarmingResponse(resp)` and `fetchWarmupStatus(baseUrl)` are exported for\nhand-rolled integrations.\n\n### Smart HMR reloads\n\nThe same queue machinery powers HMR on the Deno backend, so a save costs as\nlittle as possible:\n\n- **No-op saves are dropped.** Changed files are content-hashed; a re-save\n  with identical bytes (format-on-save no-ops, `touch`, agent tooling\n  re-writing the same file) never respawns a Worker.\n- **Leading-edge debounce.** The first fs event of a quiet period flushes\n  after ~40 ms instead of the full 200 ms trailing window — a single editor\n  save starts reloading almost immediately. Stragglers still coalesce.\n- **MRU-first respawns.** When one edit affects more functions than there are\n  spawn lanes (shared-module edits), the functions you've hit most recently\n  respawn first; idle ones go last.\n- **Request-driven priority + freshness.** A request for a function whose\n  respawn is still queued/in-flight bumps it to the front (out-of-band, above\n  the concurrency bound) and waits up to `1TUBE_HMR_FRESH_WAIT_MS` (default\n  2500 ms) so save → refresh returns the **new** code, like Vite. If the\n  respawn takes longer, the previous worker answers and the response is marked\n  `X-1tube-Stale: 1` (CORS-exposed) so tooling can tell the difference.\n- **Targeted re-discovery.** Reloading one function stats only that function's\n  dir instead of re-scanning the whole functions root.\n- **Local-only dep graphs.** The module graph that maps file changes to\n  affected functions stops at the local-file boundary: `npm:`/`jsr:`/`https:`\n  subtrees are marked external instead of being fetched and parsed. Only\n  `file://` modules can fire watcher events, so nothing is lost — and graph\n  builds for npm-heavy functions drop from seconds to milliseconds.\n- **One batch graph per deferred boot.** With shared modules active, deferred\n  boot builds a single module graph for all functions in the background\n  instead of letting every spawn crawl its own (which re-parsed the same\n  `_shared` files once per function).\n\nNotes:\n\n- Deferred boot is **off by default in prod** (eager boot keeps the \"everything\n  is ready before traffic\" contract); opt in with `--defer-boot`.\n- Functions that fail to boot answer `503 {\"reason\":\"function_boot_failed\"}`\n  (not a warming response — retrying won't help).\n- Workerd backend is unaffected: it boots all functions as a single bundle\n  generation.\n\n## Workerd backend\n\n1tube can optionally run each function inside a [workerd](https://github.com/cloudflare/workerd) subprocess instead of importing it directly into the gateway. This gives:\n\n- **Hard isolation** between functions and the gateway — a runaway function cannot wedge the gateway, leak memory into peer functions, or read peer functions' globals.\n- **Cloudflare-Workers-style runtime** — `nodejs_compat`, `process.env`, `node:fs`/`node:os`/`node:http`, `AsyncLocalStorage`, the modern `fetch`/`Streams`/`URL` spec fixes, etc.\n- **Zero changes to function code** — the same `serve(handler, { public: ... })` shim works on both backends. `Deno.env.get(...)` still works (we shim it onto the `env` binding). The bundler resolves `npm:`, `jsr:`, `https:`, and `file:` specifiers via Deno's own loader.\n\nPer-function manifests, the circuit breaker, and per-function timeouts apply on the workerd path too — see [Per-function manifests](#per-function-manifests-on-workerd) below. Live HMR (`--hmr`) also works: a save triggers a re-bundle of just the changed function, a *new* workerd is spawned on shifted ports, the gateway atomically swaps to it, and the old workerd is torn down — all without dropping in-flight requests. See [HMR on workerd](#hmr-on-workerd) below for details and limits.\n\n### Run it\n\n```bash\n# Make sure workerd is on PATH, or set 1TUBE_WORKERD_BIN.\nworkerd --version\n\n# Boot 1tube with the workerd backend pointed at the playground.\ndeno run --allow-all src/server.ts \\\n  --backend workerd \\\n  --functions ./playground \\\n  --port 3100 \\\n  --dev\n````\n\nYou should see something like:\n\n```\n[1tube] mode=dev hmr=off lazy=off backend=workerd ...\n[1tube] Bundling functions from: ./playground\n[1tube] workerd backend ready (v1.20260415.0) · 3 function(s) in 740ms\n[1tube] Functions: echo, hello, whoami\n```\n\nThen verify end-to-end:\n\n```bash\n# Public hello\ncurl http://127.0.0.1:3100/functions/v1/hello\n# → {\"message\":\"hello, hello\",...}\n\n# Public echo with body + query + custom header\ncurl -X POST 'http://127.0.0.1:3100/functions/v1/echo?x=1' \\\n  -H 'content-type: application/json' \\\n  -H 'x-test: abc' \\\n  -d '{\"hello\":\"world\"}'\n\n# Unknown function fast-fails before auth/rate-limit\ncurl -i http://127.0.0.1:3100/functions/v1/no-such-fn\n# → HTTP/1.1 404 Not Found\n```\n\nThe full e2e test (`tests/workerd-e2e.test.ts`) automates exactly the same\nchecks against a real workerd subprocess; run\n`deno test --allow-all tests/workerd-e2e.test.ts` for one-shot verification.\n\n### Compatibility date\n\nWorkerd applies a `compatibilityDate` to enable behaviour-change opt-ins. 1tube\ndefaults to today's date, then **clamps it down** to whatever the installed\nworkerd binary actually accepts — derived from the binary's version string\n(`1.YYYYMMDD.N`), since workerd refuses dates later than its build date. You\nonly see a clamp warning if you explicitly pinned a date the binary can't\nhonour.\n\n### Forwarding env vars (secrets, API keys)\n\n**Default: every env var the gateway sees is forwarded.** Bundled functions\ninherit their parent's environment the same way a\n`deno run ./functions/foo/index.ts` child would — so\n`Deno.env.get(\"OPENAI_API_KEY\")` Just Works without any extra wiring. This is\nthe developer-friendly default; use it for solo projects and trusted code.\n\nPass an explicit allowlist when you care about isolation (shared multi-tenant\nworkerds, reproducible build artifacts, etc.):\n\n```bash\n# CLI flag — narrows to exactly these names\ndeno run --allow-all src/server.ts \\\n  --backend workerd --functions ./functions \\\n  --workerd-env=OPENAI_API_KEY,POSTHOG_HOST,STRIPE_SECRET_KEY\n\n# Env var (handy when 1tube is launched by a process manager)\n1TUBE_WORKERD_ENV=OPENAI_API_KEY,POSTHOG_HOST,STRIPE_SECRET_KEY \\\n  deno run --allow-all src/server.ts --backend workerd --functions ./functions\n\n# `*` is the explicit form of the default (forward everything)\n--workerd-env=*\n```\n\nEach listed name becomes a `(name = X, fromEnvironment = X)` binding in the\ngenerated `config.capnp`. Workerd reads the **value** from its own process env\nat boot; the on-disk capnp file contains nothing but the var names. This means:\n\n- Operators can rotate secrets by restarting the gateway with new env values; no\n  cache rebuild needed.\n- A leaked `config.capnp` cannot leak secrets — it never had them.\n- Vars listed but absent from the gateway env get a single grouped warning at\n  boot, then are simply not exposed (workerd would otherwise refuse to start).\n\n\u003e **A note on the default.** Earlier versions of 1tube defaulted to _deny-all_\n\u003e and required explicit opt-in. That was friction for the 90%-case (single-app\n\u003e deploys where the gateway and the functions trust each other) and surprised\n\u003e people whose `.env` already worked everywhere else. The deny-all stance is\n\u003e still one keystroke away — `--workerd-env=A,B,C` — and the per-function\n\u003e `permissions.env` manifest knob still narrows further under\n\u003e `1TUBE_ENFORCE_MANIFEST=1`.\n\n### Bundle sizes at boot\n\nWorkerd boot prints a sorted table so unintended npm bloat is visible\nimmediately:\n\n```\n[1tube] workerd bundle sizes (sorted, largest first):\n  zod-deps    443.4KB  (612ms)\n  stream       12.0KB  (89ms)\n  echo          9.9KB  (74ms)\n  ...\n  total       492.6KB  (1131ms across 7 fns)\n```\n\nIf a bundle is much bigger than expected (50MB+), look for an unintended deep\nimport — `nodejs_compat` shims most of `node:*` automatically and you rarely\nneed to import a \"browser polyfill\" version of an npm package.\n\n### Compatibility date\n\nWorkerd applies a `compatibilityDate` to enable behaviour-change opt-ins. 1tube\ndefaults to today's date, then **clamps it down** to whatever the installed\nworkerd binary actually accepts — derived from the binary's version string\n(`1.YYYYMMDD.N`), since workerd refuses dates later than its build date. You\nonly see a clamp warning if you explicitly pinned a date the binary can't\nhonour.\n\n### Per-function manifests on workerd\n\nEach `\u003cfunction\u003e/1tube.json` is loaded at workerd boot and applied the same way\nthe Deno backend uses it:\n\n- **`timeoutMs`** — per-function dispatch timeout. The gateway aborts the\n  request and returns 504 if the bundled handler doesn't respond within this\n  window. Falls back to `--timeout` when unset.\n- **`rpm`** — per-function rate limit. The gateway-wide rate limiter consults\n  the workerd manifest the same way it consults Deno-side ones; manifest values\n  override `1TUBE_DEFAULT_RPM`.\n- **`recycle`** — circuit breaker. After `errorWindow` requests with at least\n  `errorRate` of them returning 5xx (or aborting), the breaker opens for\n  `cooldownMs`. While open, requests short-circuit with\n  `503 Retry-After: \u003cseconds\u003e` and a `{\"reason\": \"circuit_breaker_open\"}` body —\n  workerd is never touched. The breaker half-opens automatically once the\n  cool-down elapses.\n- **`permissions.env`** — per-function env scoping. Default behaviour: every\n  function sees the full `--workerd-env` allowlist (manifest entries are\n  documentation only). Set `1TUBE_ENFORCE_MANIFEST=1` to switch to strict mode,\n  where each function only sees the _intersection_ of the gateway allowlist and\n  its declared `permissions.env`. Use `[\"*\"]` to opt back into the full surface\n  under enforcement.\n\nFunctions without a `1tube.json` get the defaults from `defaultManifest()` —\nwide-open env, gateway-default timeout/rpm, errorRate=0.5 over a 20-request\nwindow with a 30s cool-down.\n\n#### Example — tight breaker\n\n```json\n// playground/boom/1tube.json\n{\n  \"permissions\": { \"net\": [], \"env\": [], \"read\": [], \"write\": [] },\n  \"timeoutMs\": 5000,\n  \"recycle\": { \"errorRate\": 1.0, \"errorWindow\": 3, \"cooldownMs\": 5000 }\n}\n```\n\nThree consecutive 500s trip the breaker; the gateway logs:\n\n```\n[1tube] Circuit breaker OPEN for \"boom\" (workerd) — refusing requests for 5000ms (errorRate \u003e= 1).\n```\n\nThe 4th request returns 503 with `Retry-After: 5` without ever reaching the\nworkerd subprocess. Verified end-to-end in `tests/workerd-e2e.test.ts`.\n\n### HMR on workerd\n\n`--hmr` (or `1TUBE_HMR=1`) enables live reload on the workerd backend. The\ngateway watches the functions directory and on each save:\n\n1. Classifies the changed path. A change inside a function dir (e.g.\n   `playground/hello/index.ts` or `playground/hello/1tube.json`) only re-bundles\n   that one function; a change to anything starting with `_` (`_shared/`,\n   `_internal/`, etc.) triggers a full re-bundle since shared code may be\n   imported by any function.\n2. Coalesces a 200ms burst of save events (editor format-on-save typically\n   writes the file 2–3 times within ~50ms).\n3. Re-bundles the affected function(s) using the long-lived esbuild worker.\n   Bundles for unchanged functions stay on disk.\n4. Generates a fresh `config.gen-{0|1}.capnp` on shifted ports (alternating\n   between `basePort` and `basePort+500`) so the new workerd never collides with\n   the still-serving old one.\n5. Spawns the new workerd, waits for TCP readiness, then atomically swaps the\n   gateway's route table.\n6. Stops the old workerd. In-flight requests against the old process keep their\n   captured origin URL and complete naturally; only NEW requests go to the new\n   workerd. There is no request-drop window.\n\nA typical incremental swap completes in **200–800ms** on a warm cache\n(single-function rebundle + workerd cold start). Manifests (`1tube.json`) are\nre-read on every reload and propagated into the registry / supervisor before the\nnew workerd starts serving, so an `rpm` or `timeoutMs` edit applies to the very\nfirst post-reload request.\n\nFailures are non-fatal: a syntax error in your source code aborts the swap, the\nold workerd keeps serving, and the next save retries. The reloader logs:\n\n```\n[1tube] HMR 1 function(s) changed: hello\n[1tube] HMR reload ok in 412ms (gen=3; rebundled=hello)\n```\n\nor, on failure:\n\n```\n[1tube] HMR reload FAILED — keeping previous workerd alive. Error: …\n```\n\nEnd-to-end coverage in `tests/workerd-e2e.test.ts`\n(`HMR rebundles + swaps without dropping requests`).\n\n### Memory enforcement \u0026 crash recovery\n\n**The honest version up front**: workerd's open-source binary does **not**\nsupport per-isolate memory limits in its capnp schema — that's a\nCloudflare-platform feature, not something the OSS runtime exposes. So 1tube\ncannot kill an individual misbehaving function; it can only recycle the whole\nworkerd process. Two mechanisms keep things stable:\n\n1. **Memory watchdog** — periodically reads the workerd PID's resident set size\n   (RSS) and recycles the entire process when it stays over budget for `N`\n   consecutive samples.\n2. **Crash auto-recovery** — if workerd dies unexpectedly (OOM, SIGKILL,\n   segfault), the gateway re-bundles and respawns it automatically.\n\nBoth reuse the M4 dual-process swap, so recovery is non-disruptive: in-flight\nrequests against the old workerd complete on their existing socket, the gateway\natomically points new requests at the new process, and only then is the\ndead/over-budget process torn down.\n\n#### Watchdog\n\nOpt-in via `1TUBE_WORKERD_MAX_RSS_MB` (an absolute cap in MB). When unset, the\ngateway derives a recommended budget from `sum(manifest.memoryMB) × 1.5 + 64MB`\nand uses that automatically. If no manifest declares `memoryMB` either, the\nwatchdog stays off — no surprises for existing deployments.\n\n| Env var                         | Default | Notes               |\n| ------------------------------- | ------- | ------------------- |\n| `1TUBE_WORKERD_MAX_RSS_MB`      | unset   | Hard RSS cap in MB. |\n| `1TUBE_WORKERD_RSS_INTERVAL_MS` | `5000`  | Poll interval.      |\n\nHysteresis is fixed at **3 consecutive over-budget samples** before tripping (a\nsingle GC pause spike won't recycle), and a **10-second cooldown** runs after\neach recycle to let the new process settle. Watchdog stats (last RSS, configured\nbudget, total recycles, current generation) are surfaced on the authenticated\n`/health` endpoint under a `workerd` block:\n\n```json\n{\n  \"status\": \"ok\",\n  \"workerd\": {\n    \"pid\": 1234,\n    \"generation\": 2,\n    \"recycles\": 1,\n    \"rss_bytes\": 134217728,\n    \"budget_bytes\": 268435456\n  }\n}\n```\n\nRSS reading is cross-platform: Linux reads `/proc/{pid}/status`, macOS shells\nout to `ps`, Windows to `tasklist`. The numbers approximate (working set vs\nresident vs incl. shared pages differ by platform) — treat the budget as a rough\ncap, not a precise quota.\n\n#### Crash recovery\n\n`WorkerdProcess.onExit` is wired to trigger an automatic recycle. A bounded\nretry budget — **5 crashes in 30 seconds** — protects against hot-looping on a\nfundamentally broken bundle:\n\n```\n[1tube] workerd crashed (gen=2, code=137, crashes=1/5 in 30s) — auto-recycling...\n[1tube] workerd crashed (gen=3, code=137, crashes=2/5 in 30s) — auto-recycling...\n…\n[1tube] workerd crashed (gen=6, code=137, crashes=6/5 in 30s) — GIVING UP, manual restart required.\n```\n\nAfter the budget is exhausted the gateway returns `502 workerd backend error`\nfor every request until the operator restarts it (or, in dev, an HMR save which\nclears the counter and resumes recovery). End-to-end coverage in\n`tests/workerd-e2e.test.ts`\n(`gateway auto-recovers when workerd is killed externally`).\n\n### Observability on workerd\n\nBoth `/health` (JSON) and `/metrics` (Prometheus exposition) report\nworkerd-specific state when the backend is active. Both endpoints require\n`Authorization: Bearer $INTERNAL_KEY`.\n\n`/health` adds a `workerd` block:\n\n```json\n{\n  \"workerd\": {\n    \"pid\": 4242,\n    \"generation\": 3,\n    \"recycles\": 0,\n    \"rss_bytes\": 134217728,\n    \"budget_bytes\": 268435456,\n    \"last_reload_duration_ms\": 412.5,\n    \"bundle_bytes\": { \"hello\": 1024, \"echo\": 998 }\n  }\n}\n```\n\n`/metrics` exposes the same data as scrapeable gauges, plus per-function\ncircuit-breaker view that mirrors `supervisor.allStats()`:\n\n| Gauge                                                  | Meaning                                                                                   |\n| ------------------------------------------------------ | ----------------------------------------------------------------------------------------- |\n| `onetube_workerd_up`                                   | `1` if workerd is currently running, `0` between processes (mid-recycle, post-crash gap). |\n| `onetube_workerd_pid`                                  | PID of the live workerd, `0` when not running.                                            |\n| `onetube_workerd_generation`                           | Increments on every reload (HMR / watchdog / crash recovery).                             |\n| `onetube_workerd_recycles_total`                       | Number of memory-watchdog-triggered recycles.                                             |\n| `onetube_workerd_rss_bytes`                            | Most recent RSS sample.                                                                   |\n| `onetube_workerd_budget_bytes`                         | RSS budget enforced by the watchdog (when set).                                           |\n| `onetube_workerd_last_reload_duration_ms`              | Wall-clock duration of the most recent successful reload.                                 |\n| `onetube_workerd_bundle_bytes{function=\"...\"}`         | esbuild output size per function.                                                         |\n| `onetube_function_breaker_open{function=\"...\"}`        | `1` while the supervisor's breaker is open for this function.                             |\n| `onetube_function_error_rate{function=\"...\"}`          | Rolling error rate inside the supervisor's window (0..1).                                 |\n| `onetube_function_recycle_recommended{function=\"...\"}` | `1` once `recycle.maxRequests` has been reached.                                          |\n\nThe breaker gauges work on the Deno backend too — the supervisor is shared\nbetween paths.\n\n### V8 inspector / debugger access\n\nPass `--inspector` (or `--inspector-addr=host:port`, `1TUBE_INSPECTOR=1`) to\nlaunch workerd with `--inspector-addr=...`. Default address is `127.0.0.1:9229`:\n\n```bash\ndeno run --allow-all src/server.ts --backend workerd --inspector\n# → [1tube] workerd V8 inspector enabled at 127.0.0.1:9229 (open chrome://inspect or attach via DevTools)\n```\n\nThen open `chrome://inspect` in a Chromium browser; the workerd target appears\nunder \"Remote Target\" once a function is invoked. Set breakpoints, step through\nbundled code, inspect locals — everything you'd expect from V8.\n\nCaveats:\n\n- **Unauthenticated debug port.** `--inspector` opens an unauthenticated V8\n  protocol port. The gateway warns loudly when the bind address is non-loopback.\n  Don't enable in production.\n- **HMR port shift.** When `--hmr` is also enabled, every reload spawns a _new_\n  workerd on the next port slot. The inspector port shifts by one (`9229` →\n  `9230` → `9229` → …) to avoid colliding with the still-listening predecessor.\n  The boot log calls out the actual port for each generation.\n- **Workerd-only.** The flag is a no-op on `--backend deno`; debug Deno-side\n  functions with the standard `--inspect` Deno flag instead.\n\n### Graceful shutdown\n\nOn `SIGINT` / `SIGTERM` the gateway runs a three-phase shutdown bounded by\n`1TUBE_SHUTDOWN_GRACE_MS` (default `10000`):\n\n1. **Stop watchers** — memory watchdog, hot reloader. Prevents a poll racing the\n   shutdown from triggering a doomed recycle.\n2. **Drain the listener** — `Deno.serve`'s abort signal stops accepting new TCP\n   connections. Already-accepted requests keep running against the still-live\n   workerd until they complete or grace expires.\n3. **Stop workerd** — only after the drain has finished (or grace expired).\n   Workerd's own SIGTERM-then-SIGKILL ladder gets the larger of\n   `(remaining_grace, 1000ms)` so its log pumps can flush a final line.\n\nPrevious releases fired-and-forgot `workerd.stop()` in parallel with the drain,\nwhich killed workerd while requests were still forwarding through it (502s on\nthe way out). The current ordering ensures in-flight requests keep their\noriginal socket until they complete naturally — same property HMR's dual-process\nswap relies on.\n\nIf grace is too tight (rare), bump `1TUBE_SHUTDOWN_GRACE_MS=30000`. The boot log\nrecords the actual drain + workerd-stop durations so you can size it from real\nnumbers.\n\n### Prebuilt artifacts (`1tube build` + `--prebuilt`)\n\nFor prod deploys you usually don't want esbuild on the box. `1tube build`\nproduces a sealed artifact directory that `1tube serve --prebuilt` can boot with\n**zero bundling on the critical path** — just load, sha-256-verify, hand to\nworkerd.\n\n```bash\n# Build in CI (or locally)\ndeno task build --functions ./supabase/functions --out ./dist\n# → dist/manifest.json + hello.js + echo.js + ... + .gitignore\n\n# Ship `dist/` and the workerd binary to prod, then:\ndeno run --allow-all src/cli.ts --backend workerd --prebuilt ./dist\n```\n\nBuild flags worth knowing:\n\n| Flag                       | Default                | Purpose                                                                                                                    |\n| -------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------- |\n| `--functions \u003cpath\u003e`       | `./supabase/functions` | Source dir to bundle.                                                                                                      |\n| `--out \u003cpath\u003e`             | `./dist`               | Output artifact directory.                                                                                                 |\n| `--only A,B,C`             | all                    | Build a subset (smoke tests, partial deploys).                                                                             |\n| `--sourcemap MODE`         | `linked`               | `none` / `linked` / `inline`.                                                                                              |\n| `--minify`                 | off                    | Minify output.                                                                                                             |\n| `--compat-date YYYY-MM-DD` | (workerd's default)    | Bake a workerd compatibility date into the manifest.                                                                       |\n| `--compat-flag FLAG`       | `nodejs_compat`        | Repeatable; appends a workerd compatibility flag.                                                                          |\n| `--workerd-env A,B,C`      | empty                  | Bake env-var allowlist into the manifest. Values are still read from process env at serve time — only names are persisted. |\n\n`dist/manifest.json` carries:\n\n- Schema version (`schema: 1`) — serve refuses newer schemas instead of\n  mis-parsing.\n- 1tube version + ISO build timestamp (diagnostic).\n- Per-function `bundleFile` + `bundleBytes` + `bundleSha256` (lower-case hex).\n  The serve loader recomputes the digest at boot and **fails fast** if a bundle\n  has been modified or corrupted.\n- Per-function `1tube.json` parsed in-line, so a prebuilt deploy doesn't need\n  the source tree at all.\n\nPrebuilt-mode behaviour at serve time:\n\n- `--functions` is ignored (warned about). `1TUBE_PREBUILT=path` works as an env\n  var equivalent.\n- `--prebuilt` implies `--backend workerd`; coerced with a warning if you\n  forget.\n- `--hmr` is rejected with a one-line warning — sealed artifacts can't reload.\n  Re-run `1tube build` and restart.\n- `backend.reload()` is rejected. Crash-recovery (re-spawning workerd against\n  the same on-disk bundles) still works — it doesn't need to re-bundle.\n- The artifact directory must be **writable at boot** because workerd's\n  generated `config.gen-*.capnp` is written next to the bundles. If your prod\n  filesystem is read-only, `cp -r dist /tmp/dist-rw \u0026\u0026 --prebuilt /tmp/dist-rw`.\n  (Capnp generation is microseconds; we don't bake it at build time so `--port`,\n  `--host`, and `--workerd-env` overrides keep working at serve.)\n\nWhat you gain by separating build and serve:\n\n- Reproducible deploys — the artifact is byte-identical for fixed inputs\n  (esbuild output is deterministic; manifest sorts by function name).\n- Tamper-evident — sha-256 verification at boot catches both corruption and\n  silent edits.\n- No esbuild / `@deno/esbuild-plugin` / npm fetch on the prod box.\n- Faster boot — esbuild (the slow part) runs once in CI, not once per restart.\n\n### Cache layout\n\nBundles, sourcemaps, and the generated `config.gen-{0,1}.capnp` files live in\n`node_modules/.cache/1tube-workerd/` (or `.1tube-cache/workerd/` if there's no\n`node_modules/`). The directory is auto-gitignored. Safe to delete; it gets\nrebuilt on next boot.\n\n### Benchmarking\n\nA self-contained bench script under `scripts/bench.ts` boots a gateway with each\nbackend, hammers `/functions/v1/hello` (GET) and `/functions/v1/echo` (POST with\na 256-byte body), and reports `RPS / p50 / p95 / p99` per (backend, route) pair.\nIt auto-disables rate limiting via `1TUBE_DISABLE_RATE_LIMIT=1` so per-function\nmanifest caps don't skew the numbers.\n\n```bash\n# Default sweep (5000 reqs at 64 concurrency, both backends, ~30s).\ndeno run --allow-all scripts/bench.ts\n\n# Single backend + heavier load.\ndeno run --allow-all scripts/bench.ts --backend workerd -n 20000 -c 128\n\n# Quick smoke (won't catch tail latency but boots fast).\ndeno run --allow-all scripts/bench.ts -n 500 -c 16 --warmup 50\n```\n\nOrder-of-magnitude numbers on a recent laptop, loopback, both backends warm:\n\n| Backend | Route     |  RPS |    p50 |    p95 |    p99 |\n| ------- | --------- | ---: | -----: | -----: | -----: |\n| Deno    | GET hello | 3180 |  8.8ms | 16.7ms | 26.0ms |\n| Deno    | POST echo | 1819 | 16.4ms | 27.2ms | 36.3ms |\n| Workerd | GET hello | 1377 | 22.6ms | 32.9ms | 38.0ms |\n| Workerd | POST echo | 1034 | 29.7ms | 47.2ms | 57.2ms |\n\nWorkerd costs roughly 2× the latency of in-process Deno calls because every\nrequest takes one extra HTTP hop (gateway → workerd loopback socket). For\nLLM/streaming workloads where the upstream call dwarfs proxy overhead, that\ndelta is invisible.\n\n## .NET integration\n\nAdd the `OneTube` NuGet package to your ASP.NET project:\n\n```csharp\nservices.AddOneTube(options =\u003e\n{\n    // Path to the edge functions directory. Absolute, or relative\n    // to AppContext.BaseDirectory (the host's bin/).\n    options.FunctionsPath = \"supabase/functions\";\n\n    // Binaries — absolute, relative to AppContext.BaseDirectory,\n    // or bare names (PATH lookup). On servers, drop deno/workerd\n    // next to the published host and use relative paths so the\n    // deploy is reproducible.\n    options.DenoBinary = \"onetube-bin/deno.exe\";\n    options.WorkerdBinary = \"onetube-bin/workerd.exe\";\n\n    options.Port = 3100;\n    options.EnvVars = new()\n    {\n        [\"SUPABASE_URL\"] = \"...\",\n        [\"JWT_SECRET\"] = \"...\",\n    };\n});\n\n// In the pipeline, after UseRouting:\napp.MapOneTube(port: 3100);\n```\n\nThe OneTube package ships the gateway TypeScript sources alongside the DLL\n(under `OneTubeGateway/` in the host's output directory), so you do **not** need\na 1tube checkout on the production host — only the `deno` and `workerd` binaries\nyou point at via `DenoBinary` / `WorkerdBinary`.\n\nThis spawns the Deno gateway as a managed child process with health monitoring\nand auto-restart, and forwards `/functions/v1/*` via YARP zero-copy proxying.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flofcz%2F1tube","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flofcz%2F1tube","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flofcz%2F1tube/lists"}