feat(vercel): Vercel provider — auth, deploy engine, Function (async + Effect), and the platform surface - #1217
Draft
sam-goodwin wants to merge 13 commits into
Draft
feat(vercel): Vercel provider — auth, deploy engine, Function (async + Effect), and the platform surface#1217sam-goodwin wants to merge 13 commits into
sam-goodwin wants to merge 13 commits into
Conversation
…+ Effect), and the platform surface Infrastructure-as-Effects support for Vercel at parity with the Cloudflare provider, built and live-tested against a real Pro team (60+ live tests). - Auth: VercelAuth (env/stored token, team picker), VercelEnvironment (teamId scope), providers() barrel, alchemy login integration - Deploy engine (internal): Build Output v3 synthesis, SHA1 upload-all, immutable deployments with meta stamps, crash recovery by content hash, protection-bypass minted before first deploy, managedEnv baseline for write-only sensitive env rows - Vercel.Function: async mode (web-standard handlers, InferEnv typed env) AND Effect mode (two-phase class, per-request Scope settled via waitUntil, cron router, SIGTERM instance-scope close); Function.URL; InvokeFunction with circular A<->B precreate support - Resources: Project, ProjectEnv, EdgeConfig (+EdgeConfigToken), BlobStore (+store<->project connections), Domain, DnsRecord, ProjectDomain, Cert, Webhook, Drain, FirewallConfig, CustomEnvironment, SharedEnv, AccessGroup(+Project), Alias (+promote/rollback actions), RollingRelease - Capabilities: ReadEdgeConfig, ReadBlob/WriteBlob/ReadWriteBlob, SendMessage/ReceiveMessages + subscribe (Vercel Queues — typed Topic values, real push delivery via a separate consumer .func), cron - Websites: StaticSite, Nuxt (built-in nitro preset), Astro, SvelteKit - distilled submodule: binary-body codegen + 31 OpenAPI patches (alchemy-run/distilled#457) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rrowing, misc
- InvokeFunction: by-logical-id forward references (Vercel.InvokeRef) so
circular A<->B pairs need no mutual class import (the type-inference
cycle collapsed both classes to any); layer-scoped forward-ref
constructor keeps Providers out of the runtime callable's R
- Blob Read/Write clients narrow impossible-by-construction tags to
defects, preserving least-privilege error unions
- EdgeConfigToken diff: drop the {}-default poisoning Input inference
- sendMessageFromEnv constrained to service-free schemas
- distilled bump: typed 404 on filterProjectEnvs
Full workspace tsc green; live suite 65 passed / 0 failed; census clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
|
Install the packages built from this commit: Alchemyalchemy bun add https://pkg.ing/alchemy/0d72e38@alchemy.run/better-auth bun add https://pkg.ing/@alchemy.run/better-auth/0d72e38@alchemy.run/cloudflare-runtime bun add https://pkg.ing/@alchemy.run/cloudflare-runtime/0d72e38@alchemy.run/frontend-frameworks bun add https://pkg.ing/@alchemy.run/frontend-frameworks/0d72e38@alchemy.run/node-utils bun add https://pkg.ing/@alchemy.run/node-utils/0d72e38@alchemy.run/pr-package bun add https://pkg.ing/@alchemy.run/pr-package/0d72e38@alchemy.run/floci bun add https://pkg.ing/@alchemy.run/floci/0d72e38Distilled@distilled.cloud/core bun add https://pkg.ing/@distilled.cloud/core/3401096@distilled.cloud/aws bun add https://pkg.ing/@distilled.cloud/aws/3401096@distilled.cloud/axiom bun add https://pkg.ing/@distilled.cloud/axiom/3401096@distilled.cloud/cloudflare bun add https://pkg.ing/@distilled.cloud/cloudflare/3401096@distilled.cloud/neon bun add https://pkg.ing/@distilled.cloud/neon/3401096@distilled.cloud/planetscale bun add https://pkg.ing/@distilled.cloud/planetscale/3401096 |
EffectHttp.toHandled closes its internal per-request scope inline after the response callback, so a handler's Effect.addFinalizer delayed the HTTP response by the finalizer's full duration (measured: +3348ms for a 3s finalizer) — contradicting the documented post-response-via-waitUntil contract. Port the Vercel bridge's scope ejection (scopeDisableClose + chaining onto the bridge's per-event scope closed under ctx.waitUntil) into toHandledWebResponse; streaming keeps scopeTransferToStream. Live-verified: 3367.9ms -> 19.3ms with the finalizer still running post-response; new FinalizerLatency test pins it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A timed-out test re-ran under the default retry, multiplying wall-clock by (1 + retries); the external timeout/CI wall then killed the process (exit 130) before TestEnd was written — the 'runner crash with an empty log'. Timeouts no longer retry (a timeout consumed the whole budget and may have left an abandoned fiber; matches the speed doctrine), retry attempts stream their errors to console+log immediately via a TestRetry event, and an interrupted run appends a RUN INTERRUPTED trailer naming in-flight tests. Three subprocess regression tests added. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…long tail - Local providers (alchemy dev): Function (full launcher-matrix shim, URL-stable hot restarts, cron timers, waitUntil drain), EdgeConfig (exact @vercel/edge-config read protocol), Blob (data-plane emulation with etag CAS via the VERCEL_BLOB_API_URL override), Queues (v3 data-plane broker + CloudEvents push dispatch); .local.test.ts suites incl. Alchemy.remote() opt-outs - Vercel.state(): Blob-backed state store function (shared StateApi contract, AES-CTR rows, bearer auth, /version) deployed by our own engine; alchemy vercel bootstrap/teardown; live e2e green - Preview tenancy: stable per-stage aliases, per-deployment tenant env with the mandatory project-env conflict check, cross-stage stackRef test - logs/tail wiring over the live NDJSON runtime-log stream - P2: ProjectMember, ProjectRoutes, BulkRedirects, FeatureFlag, MicrofrontendsGroup (billing-gated: $250/mo add-on), Check, Team + TeamMember (creation/invites gated), WebAnalytics, AIGateway, EdgeCache purge, SandboxDrive/Snapshot - Engine: precreate stub gates only on project-facing props (fixes the Function<->BlobStore cycle 500-loop); managedEnv baseline hardening - Generated API reference for all 22 Vercel services; distilled bump (retry give-up on out-of-patience Retry-After + 19 new patches) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Plan.make skipped the cold-start Unowned probe whenever news carried unresolved Outputs (the comment claimed such resources 'cannot be pre-existing') — but physical identity usually comes from a deterministic name, so a same-name resource could pre-exist while env/binding inputs were unresolved, and reconcile silently converged onto it without the --adopt consent gate. Effect-valued props count as unresolved too, so Cloudflare/AWS circular-binding env patterns were equally exposed. The probe now runs for every greenfield row, handing read the stripUnresolved(news) shape it is already contractually required to tolerate; probe failures degrade to the pre-fix behavior (best-effort, mirroring the #995 recovery-read). Three engine tests pin it — the first two failed pre-fix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… data planes Wave E — 8 interaction chains (test/Vercel/Chains/), each 3-8 live reconciliation cycles asserting changed-vs-stable, which surfaced and fixed: - DnsRecord: silent data loss on parent-domain same-name replacement - ProjectDomain: same-name re-points are delete-first (one project per domain name per team) - BlobStore: connected-store replacement is delete-first; forceDestroy purges non-empty stores through the data plane (409 not_empty had made them undeletable); connection races hardened - Function: URL read-back no longer trusts nondeterministic alias ordering; generated project names capped at 35 chars (auto-domain truncation collisions); Alias guards precreate stubs with a typed error - State store: versioned row pathnames (overwritten blobs are eventually consistent); tenant flows handle Vercel auto-promoting first deployments - Platform semantics pinned: rollback sweeps custom aliases; external OIDC tokens are always development-scoped HTTP alignment — every Vercel wire call now rides distilled's generated services (blob_data, queues_data, edge_config_data): BlobHttp/QueueData/ EdgeConfigRead internals swapped behind byte-compatible client surfaces, QueueApi.ts deleted, fromEnv async clients migrated, local-emulation env contracts preserved. Documented exemptions: the hanging runtime-log stream, calls to the user's own Functions, and local emulator serving. Permission audit: '## Runtime authorization' JSDoc on every capability (platform-injected store tokens, ambient per-deployment OIDC, minted scoped read tokens, protection-bypass secrets). Live-probed verdict: project-scoped auth tokens cannot reach team-owned Edge Configs, so WriteEdgeConfig ships as explicit user-supplied-token opt-in — never an auto-bound management token — with an ungated probe test that fails loudly if Vercel ever ships scoped write credentials. Full suite: 122 passed / 0 failed / 15 gated (62 files); census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Design ruling: a custom environment is ~one config row on a project with no independent life — it belongs as a future `environment?: string` prop on Function/Website (ensure-on-use, ownership in attrs; environment maps 1:1 to the alchemy stage by default in shared projects), not a standalone resource competing with alchemy's stage concept. Removed before the draft merges so no soon-deprecated API ships. SharedEnv (genuinely independent, team-level) stays. The isolation model and defaulting rules are recorded in processes/Vercel/DESIGN.md §12a. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
29 hand-written pages mirroring the Cloudflare/AWS docs structure: - 6-part tutorial building one evolving order-intake app (bare Function -> storage bindings -> queues + cron -> local dev -> testing -> stages/rollback/CI), every snippet mined from the green test suites - guides: compute (functions, fluid, deployments-and-rollback, cron), frontend (websites + nuxt/astro/sveltekit/static), data (blob, edge-config), messaging (queues incl. the isolation model, webhooks), apis (effect-http-api, invoke-function), networking, security (secrets-env, deployment-protection + the runtime-authorization table, firewall), observability (logs, drains), landing + setup - Vercel.state() documented alongside the Cloudflare store in state-store/; sidebar + provider tab wired in astro.config.mjs - ground-truth law held: unshipped surfaces (nextjs, environment:, domain:, keepDeployments) are deliberately absent; writers reported every composition not literally pinned by a test - site build green (4299 pages, link + diff-indent checks) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
Website Preview DeployedURL: https://alchemyeffectwebsite-website-pr-1217-3tlvgy45zhoz7h7w.testing-2b2.workers.dev Built from commit This comment updates automatically with each push. |
The header tab registry (src/docs-tabs.ts) is separate from the sidebar config and was missed — Vercel joins the primary slot next to Cloudflare/AWS, owning /vercel and /providers/vercel prefixes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Vercel gets its official simple-icons triangle in the tab bar and Reference sidebar (it was the only bare-text tab) - primary tabs gain a group field (core | clouds | databases) and the tab bar renders a subtle vertical divider at each group boundary and before More: Core CLI | Cloudflare AWS Vercel | PlanetScale Neon Prisma | More Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An alchemy app is any number of Functions and Websites, each owning its own project — the landing page's opening implied a one-Function limit. Also retitle the showcase section to 'one file' (matching the design doc's phrasing it was echoing). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Straightforward introduction mirroring the Cloudflare landing: intro paragraph, setup/tutorial pointer, category link lists with one-line descriptions, and the 'What are you building?' table. Drops the defensive 'No git, no build pipeline' section and the inline showcase (both live where they belong: deployments-and-rollback and compute/functions). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Infrastructure-as-Effects support for Vercel, at parity with the Cloudflare provider where the platform allows. Built wave-by-wave against a real Pro team — 60+ live tests, each service's API mismatches fed back as typed-error patches to distilled (companion: alchemy-run/distilled#457, submodule bumped here).
Plain async mode works too (
Vercel.Function("Hooks", { main, env })— web-standard handlers,InferEnv-typedprocess.env), plusWebsite.{StaticSite,Nuxt,Astro,SvelteKit}over the same deploy engine.What's inside
VercelAuth(env / stored token, team picker),VercelEnvironment(teamId scoping),alchemy loginintegrationmeta.alchemy*stamps; crash recovery by content hash; protection-bypass secrets minted before first deploy (they only open later deployments); state-persistedmanagedEnvbaseline for Vercel's write-only sensitive env rowsFunction(async + Effect bridge — re-entrant for Fluid concurrency, per-requestScopesettled viawaitUntil, SIGTERM instance-scope close),Function.URL,InvokeFunction(circular A↔B topologies via precreate),cronTopicconfig values (schema + region pinning),SendMessage, pollReceiveMessages, andsubscribe— real platform push delivery through a separate consumer.func(a trigger on the main function would kill all public HTTP routing)Project,ProjectEnv,EdgeConfig(+EdgeConfigToken),BlobStore(+store↔project connections),Domain,DnsRecord,ProjectDomain,Cert,Webhook,Drain,FirewallConfig,CustomEnvironment,SharedEnv,AccessGroup(+Project),Alias+promoteToProduction/rollbackProduction,RollingReleaseEntitlement-gated surfaces (Enterprise access groups, multi custom envs, certs) ship fully implemented with typed-tag probe tests and
VERCEL_TEST_*skipIf gates.🤖 Generated with Claude Code