@volter/twin-cloudflare
v0.1.35
Published
Local Cloudflare API twin for accounts, zones, Cloudflare Tunnels, tunnel configurations and zone DNS records, built on @volter/world-core.
Readme
@volter/twin-cloudflare
Local, deterministic Cloudflare API v4 control-plane twin for generic account/zone discovery,
remotely managed Cloudflare Tunnel provisioning, tunnel configuration/token retrieval, a bounded
zone DNS-record control-plane slice, and the Workers provisioning plane a wrangler-shaped or
raw-API deploy script drives: Worker script upload (metadata + bindings recorded, content
fingerprinted, never executed), static-assets upload sessions (hashes and sizes recorded, bytes
never kept), versions and deployments, script settings, cron triggers, workers.dev subdomains,
zone routes, custom domains, Durable Object namespace records, R2 bucket control plane,
Hyperdrive configuration records, and Queues with consumers. An unmodified wrangler deploy —
first deploy and redeploy — completes against it. State is persisted through the shared @volter/world-core kernel.
The pack never opens a real tunnel, publishes DNS, executes a Worker, stores an object, dials an
origin, contacts Cloudflare, or embeds an application-specific workflow.
bun packages/twin/cloudflare/src/cli.ts servePoint the official cloudflare TypeScript SDK at the printed loopback URL with any non-empty local
token and maxRetries: 0. The integration test uses the SDK unchanged; only its public baseURL
option is set.
Modeled surface
Cloudflare accounts and zones normally pre-exist the workflow under certification. Seed them using the disclosed twin-only endpoint:
POST /twin/bootstrap
Authorization: Bearer local
Content-Type: application/json
{
"accounts": [{ "id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "name": "Acme" }],
"zones": [{ "id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "account_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "name": "example.test" }]
}The vendor-shaped endpoints are:
- accounts: list/get;
- zones: list/get, including
nameandaccount.idfilters; /accounts/{account_id}/cfd_tunnel: list/create/get/edit/delete;- tunnel configuration: get plus a locally useful put slice and tunnel token retrieval. The
official PUT includes broad nested
originRequestschemas that are not fully validated here, so that whole OpenAPI operation remains a manifest todo; /zones/{zone_id}/dns_records: list/get/delete plus locally useful create/put/patch support for CNAME, address, text, and structured CAA-shaped certification records. Because Cloudflare's three write operations are discriminated unions spanning many more record schemas, those whole OpenAPI operations remain manifest todos; the pack does not claim complete union fidelity;- Workers scripts:
/accounts/{account_id}/workers/scriptslist, script download, delete (honoring the pinned OpenAPIforcecontract: withoutforce=truea delete is stopped by associated Durable Object namespaces or service bindings), plus thePUTmodule upload itself. The upload accepts all three wire forms an unmodified client actually produces: raw multipart/form-data with a JSONmetadatapart (wrangler / raw API), the official SDK's FormData serialization (bracket-notationmetadata[...]parts under the operation'sapplication/javascriptcontent type — measured against[email protected]), and a raw service-worker-syntax script body. Module content is recorded and SHA-256-fingerprinted into theetag, never executed, parsed, or run. Because the full multipart metadata union (38 binding kinds, assets, placement, limits, observability) is far broader than the implemented deploy slice, the wholePUToperation honestly remains a manifest todo (the same rule as the DNS write unions) even though every worker-seeding verify drives it. The implemented binding slice validates the closed binding-kind union and the deploy-critical kinds (plain_text/secret_text/json/kv_namespace/service/r2_bucket/queue/hyperdrive/durable_object_namespace), including referential checks against this twin's own R2 buckets, queues, and Hyperdrive configs; other known kinds are recorded as configuration without deep validation. Durable Objectmigrations(new/new_sqlite/deleted/renamed classes, old/new tag matching, multi-stepsteps) materialize namespace records;transferred_classesfails loudly as unmodeled; - what
wrangler deploydrives around the upload:GET /accounts/{account_id}/workers/services/{service_name}— the Worker-as-service view wrangler reads first (default_environment.script.{tag,tags,migration_tag,last_deployed_from}), answering10007for a Worker that does not exist yet. That path is not in the pinned OpenAPI, so it is served but carries no manifest capability; its shape is grounded in what Cloudflare's own client (wrangler's deploy helpers) reads from it.last_deployed_fromiswranglerwhen the upload's User-Agent is wrangler's,apiotherwise. A missing account workers.dev subdomain answers10007, the code wrangler's register-or-fail branch keys on — so a deploy that wantsworkers.devneeds the account subdomain registered first (PUT /accounts/{account_id}/workers/subdomain), exactly as on a real account; - static assets:
POST …/scripts/{script_name}/assets-upload-sessionanswers{jwt, buckets}naming only the hashes the account does not already hold (at most 10 per bucket — a twin convention; the spec does not pin the partition);POST …/workers/assets/upload?base64=true, authenticated by that session token rather than the API token, checks each part's decoded size against the manifest, records the asset's hash, size and content type — never its bytes, since no vendor read endpoint returns them — and returns the completion token once every hash is held. The manifest hash is wrangler's blake3 over base64 content plus extension; the twin does not recompute blake3 and trusts the declared hash. The script upload redeemsmetadata.assets.jwt(issued for this account and Worker, every hash uploaded), records the manifest and the assets config (run_worker_first,html_handling, …) as configuration, honourskeep_assets, and refuses anassetsbinding with no assets. The single-file Edge-KV upload path (…/assets/upload/{hash}) is not served: the twin's tokens never enable it; - versions and deployments: every script
PUTis a new version deployed at 100% and answersdeployment_id(the version id wrangler prints as "Current Version ID"). A redeploy with no pending migration takes wrangler's versions path:POST …/versionsuploads a version without deploying it (Durable Object migrations refused — Cloudflare migrates only on deploy;inheritbindings resolved from the deployed version, refused underbindings_inherit=strictwhen there is nothing to inherit),POST …/deploymentsrecords a one- or two-version percentage split and makes the larger share the live content (the split is recorded, never routed), andPATCH …/script-settingsrecords logpush, tags, tail consumers and observability.GET …/deployments,GET …/versionsand…/versions/{version_id},GET …/settings(the deployed bindings and runtime),GET …/script-settings, andGET …/secrets/…/secrets/{secret_name}read it back; secret values are write-only and never echoed.POST …/versionscarries the same multipart metadata union as the scriptPUTand stays a manifest todo for the same reason;worker_loaderbindings are recorded as configuration like the other non-deploy-critical kinds; - cron triggers / subdomains / routes / domains: script
schedulesget/put (5-field cron validation), per-script workers.dev subdomain get/post/delete, the account-level Workers subdomain get/put/delete (204), zoneworkers/routesCRUD, andworkers/domainsattach/list/get/detach — domain attach resolves the hostname against this twin's zones and requires the referenced Worker to exist, composing with the existing zone and DNS slices; - Durable Object namespaces:
/accounts/{account_id}/workers/durable_objects/namespaceslist, populated exclusively by script-upload migrations (Cloudflare has no direct namespace-create endpoint); - R2 buckets (control plane ONLY): create/list/get/delete with the vendor's documented name
pattern,
locationHint/storageClassenums, and thecf-r2-jurisdictionheader recorded. The object data plane is explicitly out of scope: no object upload/get/list, no presigned URLs, no CORS/lifecycle/sippy — a bucket here is a provisioning record, not storage; - Hyperdrive configs: create/list/get/put/patch/delete over the three official origin shapes
(public database, Access-protected, Workers VPC). The origin connection is recorded as
configuration and never dialed;
password/access_client_secretare writeOnly per the vendor schema and are never echoed back; - Queues: queue create/list/get/delete and consumer create/list/get/put/delete over the
official worker/http_pull consumer union with per-type settings validation. A queue's
producerslist is computed from Worker uploads whose bindings declaretype:"queue"— producer bindings are configuration, not message flow. Queue messages, purge, metrics, and subscriptions stay manifest todos (data plane).
Responses use Cloudflare's API v4 {success, errors, messages, result, result_info?} envelope.
Writes honor the kernel's read-only mode. Deletes are projected as tombstones, and deterministic ID
allocation scans tombstones so delete/recreate cannot resurrect stale state.
Control plane, not infrastructure
The tunnel object, ingress configuration, token, DNS record, Worker script record, bucket,
Hyperdrive config, and queue are local control-plane data. The twin does not run
cloudflared, connect to Cloudflare's edge, resolve DNS, propagate records, issue certificates,
provide a publicly reachable hostname, execute a Worker, evaluate a cron trigger, serve a
route, store or serve an R2 object, dial a Hyperdrive origin, or move a queue message. Those
are intentionally real-world certification steps and never manifest capabilities — a twin is a
deterministic, offline model of the API contract, and the tunnel/DNS infrastructure legs are what
the real root does. The provisioning slice's data planes (R2 objects, queue messages) are todos.
Cloudflare is API-first for this use case. Its dashboard is incidental administration UI, so this pack has no mirror and no UI capability claims.
Connector and budget
All optional real I/O crosses the injected CloudflareExecute function. Pull walks every
result_info.total_pages page for accounts and zones, then every tunnel page per observed account,
one configuration read per remotely managed tunnel, and every DNS-record page per observed zone.
It validates required vendor fields before folding all five resource types through the kernel observation path,
tombstones previously vendor-observed resources absent from a later inventory only when pagination
metadata proves that inventory complete (metadata-free observations never delete state), rejects anything
other than a success:true envelope, and is idempotent for identical observations. Push translates
only the modeled local actions, preserves PUT-versus-PATCH replacement semantics and
Cloudflare-returned tunnel/DNS IDs across dependent
actions and retries, and confirms each only after a successful response with the required result
identity; unknown actions fail loudly.
The only live executor, liveCloudflareExecute, checks a persistent CloudflareBudget before every
fetch. Cloudflare documents a global 1,200 requests per five minutes per user/account token. This
pack deliberately uses a tighter fallback-sized ceiling of 60 weighted units per minute: reads cost
2 (30/minute), mutations cost 3 (20/minute), and 429 Retry-After establishes a persistent
cooldown. No live call is made by the tests or capability probes.
Error-code provenance for the provisioning slice
The pinned OpenAPI declares only a generic 4XX error envelope for the Workers/R2/Hyperdrive/
Queues operations, so specific numeric error codes are not pinned by it. This pack uses
Cloudflare's widely observed codes where they are well known — 10007 (script not found, and a
missing account workers.dev subdomain — the code wrangler branches on for both),
10006 (R2 bucket does not exist), 10004 (R2 bucket already exists) — and the pack's generic
conventions (1004 validation, 1001 not found, 1003 conflict) everywhere else. Statuses and
envelopes are spec-grounded; the numeric codes outside the three above are conventions, stated
here rather than dressed up as vendor facts.
Coverage denominator
The vendor denominator is the exact 3,334 HTTP operations across 2,077 paths in the pinned
first-party OpenAPI document: one manifest entry per method + path, with no namespace sentinels or
mixed granularity. The committed census is mechanically generated by
scripts/generate-operation-census.ts; the manifest separately records fourteen connector
capabilities. The Workers provisioning slice proves 51
additional operations done (Workers scripts/schedules/subdomains/routes/domains, static-assets
upload session and upload, versions and deployments, script settings and secrets listing,
Durable Object namespace listing, R2 bucket control plane, Hyperdrive configs, Queues +
consumers), each with a
failable fresh-root verify asserting response values from a fresh root, and driving the negative
4xx or not-found path its title implies wherever the operation has a reachable one. The official SDK's 121 top-level resource
namespaces remain a separately asserted cross-check, never a substitute for the operation
denominator. This intentionally produces about 1% initial coverage rather than letting the
implementation define its own denominator.
See src/cloudflare-capabilities.ts for the complete manifest and generated repository docs for
the measured counts.
Official grounding
Grounding was inspected on 2026-08-21 (provisioning slice re-read 2026-08-23 from the same pinned commits) from only Cloudflare-maintained sources:
cloudflare/api-schemas, OpenAPI 4.0.0 at commit2ac8369e9b63dccacee1a2284e95bb819f05b307;cloudflare/cloudflare-typescriptat commitfaaaf89ed8064a9fb54de538ec3e89487f1302b0and npm[email protected];- Cloudflare's official API rate-limit reference: https://developers.cloudflare.com/fundamentals/api/reference/limits/.
The OpenAPI supplied the complete 3,334-operation census plus the account, zone, cfd_tunnel,
tunnel configuration/token, DNS-record, Workers, R2, Hyperdrive, Queues, and Durable Object
paths and request schemas (including the script-name and bucket-name patterns and the script
delete force contract). The official SDK supplied the namespace census, structured DNS unions
(including data-shaped CAA records), PUT-versus-PATCH semantics, tunnel filter/secret
contracts, the Workers binding-kind and queue-consumer unions, the response shapes for the
provisioning slice, the SDK's actual multipart serialization for script uploads (measured on the
wire during this build), and the unchanged loopback client contract. No third-party Cloudflare
wrappers or application code were used as behavioral truth.
No live probe was possible for any slice of this pack — there is no Cloudflare account to probe. The pinned first-party OpenAPI document plus the pinned official SDK are the honest grounding ceiling: paths, envelopes, request schemas, pagination, and closed enums/unions are spec-grounded; behavior the spec leaves open (specific 4xx codes, uniqueness-conflict statuses, cross-resource referential checks on bindings/domains/consumers) is a documented convention of this twin, not a verified vendor observation.
