{"id":49569187,"url":"https://github.com/nilslice/workers-zig","last_synced_at":"2026-05-23T20:31:06.272Z","repository":{"id":351747547,"uuid":"1211828400","full_name":"nilslice/workers-zig","owner":"nilslice","description":"Write Cloudflare Workers in 100% Zig via WebAssembly","archived":false,"fork":false,"pushed_at":"2026-04-24T17:39:27.000Z","size":242,"stargazers_count":59,"open_issues_count":1,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-20T04:02:46.073Z","etag":null,"topics":["cloudflare","webassembly","workers","zig"],"latest_commit_sha":null,"homepage":"","language":"Zig","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/nilslice.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-04-15T19:40:03.000Z","updated_at":"2026-05-12T20:56:26.000Z","dependencies_parsed_at":"2026-05-20T03:01:36.080Z","dependency_job_id":null,"html_url":"https://github.com/nilslice/workers-zig","commit_stats":null,"previous_names":["nilslice/workers-zig"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/nilslice/workers-zig","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nilslice%2Fworkers-zig","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nilslice%2Fworkers-zig/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nilslice%2Fworkers-zig/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nilslice%2Fworkers-zig/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nilslice","download_url":"https://codeload.github.com/nilslice/workers-zig/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nilslice%2Fworkers-zig/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33412082,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-23T18:09:33.147Z","status":"ssl_error","status_checked_at":"2026-05-23T18:09:31.380Z","response_time":53,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["cloudflare","webassembly","workers","zig"],"created_at":"2026-05-03T13:00:33.041Z","updated_at":"2026-05-23T20:31:06.250Z","avatar_url":"https://github.com/nilslice.png","language":"Zig","funding_links":[],"categories":["Zig"],"sub_categories":[],"readme":"# workers-zig\n\nA comprehensive Zig SDK for [Cloudflare Workers](https://workers.cloudflare.com). Write Workers, Durable Objects, Workflows, and more — entirely in Zig.\n\nBuilt on [JSPI](https://v8.dev/blog/jspi) (JavaScript Promise Integration), `workers-zig` lets you call async Workers APIs with normal, synchronous Zig code. No callbacks, no event loops, no allocator gymnastics — just straightforward Zig.\n\n## Features\n\n- **Full Workers platform coverage** — KV, R2, D1, Durable Objects, Queues, AI, Workflows, Vectorize, Hyperdrive, Analytics Engine, Rate Limiting, Service Bindings, Artifacts, and more\n- **HTTP Router** — path params, wildcards, method filtering, comptime route tables\n- **Durable Objects** — define classes as Zig structs, auto-detected at build time\n- **Workflows** — define steps with `step.do()`, `step.sleep()`, `step.waitForEvent()`\n- **WebSockets** — server-side accept, send, receive loop\n- **TCP Sockets** — outbound TCP connections with TLS, StartTLS, mTLS, and custom CA / SNI\n- **Zig stdlib on WASI** — `std.fs` read/write to `/tmp`, `std.process.Environ` reads worker vars, `std.time`, `std.crypto.random`\n- **Streaming responses** — chunked transfer via `StreamingResponse`\n- **Workers AI** — text generation, embeddings, image models, speech, streaming\n- **Email** — incoming email routing and outbound email sending\n- **Tail Workers** — structured trace/log consumption\n- **HTML Rewriter** — streaming HTML transformation\n- **Crypto** — Web Crypto API bindings\n- **Zero-overhead build integration** — `zig build` produces `worker.wasm` + `entry.js` + `shim.js`, ready for `wrangler deploy`\n\n## Quick Start\n\n### Prerequisites\n\n- [Zig 0.16+](https://ziglang.org/download/)\n- [Wrangler](https://developers.cloudflare.com/workers/wrangler/) (for local dev and deployment)\n\n### 1. Create your project\n\n```\nmkdir my-worker \u0026\u0026 cd my-worker\nzig init\n```\n\n### 2. Add the dependency\n\n```sh\nzig fetch --save git+https://github.com/nilslice/workers-zig\n```\n\nOr manually add it to your `build.zig.zon`:\n\n```zig\n.{\n    .name = .my_worker,\n    .version = \"0.1.0\",\n    .fingerprint = 0xYOUR_FINGERPRINT,\n    .minimum_zig_version = \"0.16.0\",\n    .dependencies = .{\n        .@\"workers-zig\" = .{\n            .url = \"https://github.com/nilslice/workers-zig/archive/refs/heads/main.tar.gz\",\n            .hash = \"...\",  // zig build will tell you the correct hash\n        },\n    },\n    .paths = .{ \"build.zig\", \"build.zig.zon\", \"src\" },\n}\n```\n\n### 3. Set up build.zig\n\n```zig\nconst std = @import(\"std\");\nconst workers_zig = @import(\"workers-zig\");\n\npub fn build(b: *std.Build) void {\n    const optimize = b.standardOptimizeOption(.{});\n    const dep = b.dependency(\"workers-zig\", .{});\n\n    const exe = workers_zig.addWorker(b, dep, b.path(\"src/main.zig\"), .{\n        .name = \"worker\",\n        .optimize = optimize,\n    });\n\n    b.installArtifact(exe);\n}\n```\n\n### 4. Write your worker\n\n```zig\n// src/main.zig\nconst workers = @import(\"workers-zig\");\nconst Request = workers.Request;\nconst Response = workers.Response;\nconst Env = workers.Env;\nconst Context = workers.Context;\n\npub fn fetch(request: *Request, env: *Env, _: *Context) !Response {\n    _ = request;\n    _ = env;\n    return Response.ok(\"Hello from Zig!\");\n}\n```\n\n### 5. Configure Wrangler\n\nCreate a `wrangler.toml`:\n\n```toml\nname = \"my-worker\"\nmain = \"zig-out/bin/entry.js\"\n\n[build]\ncommand = \"zig build -Doptimize=ReleaseSmall\"\n```\n\n### 6. Run locally\n\n```sh\nzig build \u0026\u0026 npx wrangler dev\n```\n\n## How It Works\n\n`workers-zig` uses a 3-layer architecture:\n\n```\nYour Zig code\n    ↓ imports \"workers-zig\"\nsrc/entry.zig          — wasm export dispatcher (fetch, scheduled, queue, etc.)\n    ↓ calls extern \"env\" functions\nsrc/js.zig             — FFI declarations (extern fn → JS imports)\n    ↓ imported by\njs/shim.js             — JS glue: maps FFI calls to Workers runtime APIs\n    ↓ wrapped with\nJSPI (Suspending/Promising)  — async JS calls appear synchronous to Zig\n```\n\n**JSPI** is the key innovation: when your Zig code calls an async API (e.g., `kv.getText()`), the WebAssembly stack suspends, the JS promise resolves, and execution resumes in Zig — all transparently. Your Zig code reads like synchronous, blocking I/O.\n\n**Build-time code generation**: `zig build` compiles your code to wasm, then `gen_entry` parses the wasm export section to discover Durable Object classes (via `do_\u003cName\u003e_fetch` exports) and Workflow classes (via `wf_\u003cName\u003e_run` exports), generating the correct `entry.js` with all necessary factory functions.\n\n## Handler Entrypoints\n\nExport these public functions from your `src/main.zig` to handle different event types:\n\n```zig\n// HTTP requests (required)\npub fn fetch(request: *workers.Request, env: *workers.Env, ctx: *workers.Context) !workers.Response\n\n// Cron triggers\npub fn scheduled(event: *workers.ScheduledEvent, env: *workers.Env, ctx: *workers.Context) !void\n\n// Queue consumer\npub fn queue(batch: *workers.Queue.Batch, env: *workers.Env, ctx: *workers.Context) !void\n\n// Tail worker (log consumer)\npub fn tail(events: []const workers.Tail.TraceItem, env: *workers.Env, ctx: *workers.Context) !void\n\n// Email routing\npub fn email(message: *workers.EmailMessage, env: *workers.Env, ctx: *workers.Context) !void\n```\n\nAll handlers are auto-detected at compile time — just export the function and the framework handles the rest.\n\n## Router\n\nThe built-in router provides path-parameter extraction and method-based routing with zero allocations:\n\n```zig\nconst workers = @import(\"workers-zig\");\nconst Request = workers.Request;\nconst Response = workers.Response;\nconst Env = workers.Env;\nconst Context = workers.Context;\nconst Router = workers.Router;\n\npub fn fetch(request: *Request, env: *Env, _: *Context) !Response {\n    return Router.serve(request, env, \u0026.{\n        Router.get(\"/\", handleIndex),\n        Router.get(\"/users/:id\", getUser),\n        Router.post(\"/users\", createUser),\n        Router.all(\"/health\", healthCheck),\n    }) orelse Response.err(.not_found, \"Not Found\");\n}\n\nfn getUser(_: *Request, _: *Env, params: *Router.Params) !Response {\n    const id = params.get(\"id\") orelse \"unknown\";\n    return Response.ok(id);\n}\n```\n\n**Supported patterns:**\n- Exact: `/api/users`\n- Parameters: `/users/:id/posts/:post_id` (up to 8 params)\n- Wildcards: `/static/*`\n- Methods: `get`, `post`, `put`, `delete`, `patch`, `head`, `all`\n\n## Bindings\n\n### KV\n\n```zig\nconst kv = try env.kv(\"MY_KV\");\n\n// Read\nconst value = try kv.getText(\"key\");\n\n// Write\nkv.put(\"key\", \"value\");\n\n// List\nconst result = try kv.list(.{ .prefix = \"user:\" });\n\n// Delete\nkv.delete(\"key\");\n```\n\n### R2\n\n```zig\nconst bucket = try env.r2(\"MY_BUCKET\");\n\n// Upload\n_ = try bucket.put(\"file.txt\", body, .{ .content_type = \"text/plain\" });\n\n// Download\nif (try bucket.get(\"file.txt\")) |obj| {\n    const data = obj.body;\n}\n\n// List\nconst objects = try bucket.listObjects(.{ .prefix = \"uploads/\" });\n```\n\n### D1\n\n```zig\nconst db = try env.d1(\"MY_DB\");\nconst results = try db.query(\"SELECT * FROM users WHERE id = ?\", .{42});\n```\n\n### Durable Objects\n\nDefine a Durable Object as a Zig struct with `fetch` (and optionally `alarm`):\n\n```zig\npub const Counter = struct {\n    state: workers.DurableObject.State,\n    env: workers.Env,\n\n    pub fn fetch(self: *Counter, request: *workers.Request) !workers.Response {\n        var storage = self.state.storage();\n        const count = try storage.get(\"count\");\n        // ...\n        return workers.Response.ok(count orelse \"0\");\n    }\n\n    pub fn alarm(self: *Counter) !void {\n        // periodic alarm logic\n    }\n};\n```\n\nUse from your fetch handler:\n\n```zig\nconst ns = try env.durableObject(\"COUNTER\");\nconst id = ns.idFromName(\"my-instance\");\nconst stub = ns.get(id);\nvar resp = try stub.fetch(\"http://do/increment\", .{});\n```\n\nThe build system auto-detects DO classes and generates the necessary JS glue. Configure in `wrangler.toml`:\n\n```toml\n[durable_objects]\nbindings = [{ name = \"COUNTER\", class_name = \"Counter\" }]\n\n[[migrations]]\ntag = \"v1\"\nnew_classes = [\"Counter\"]\n```\n\n### Workers AI\n\n```zig\nconst ai = try env.ai(\"AI\");\n\n// Text generation\nconst result = try ai.textGeneration(\"@cf/meta/llama-3.1-8b-instruct\", .{\n    .prompt = \"Explain WebAssembly in one sentence\",\n    .max_tokens = 100,\n});\nconst text = result.response orelse \"no response\";\n\n// Streaming\nvar reader = try ai.textGenerationStream(\"@cf/meta/llama-3.1-8b-instruct\", .{\n    .prompt = \"Write a haiku\",\n    .max_tokens = 60,\n});\nwhile (try reader.next()) |chunk| {\n    stream.write(chunk);\n}\n\n// Embeddings\nconst embeddings = try ai.textEmbeddings(\"@cf/baai/bge-base-en-v1.5\", .{\n    .text = \u0026.{\"hello world\"},\n});\n```\n\n### Workflows\n\nDefine a workflow class:\n\n```zig\npub const MyWorkflow = struct {\n    pub fn run(event: workers.Workflow.Event, step: workers.Workflow.Step) ![]const u8 {\n        const result = try step.do(\"process\", .{}, struct {\n            fn callback() []const u8 {\n                return \"computed value\";\n            }\n        }.callback);\n\n        try step.sleep(\"pause\", std.time.ms_per_s * 5);\n\n        return result;\n    }\n};\n```\n\nCreate and manage instances from your fetch handler:\n\n```zig\nconst wf = try env.workflow(\"MY_WORKFLOW\");\nconst instance = try wf.create(.{ .input = \"data\" }, .{});\nconst status = try instance.status();\n```\n\nConfigure in `wrangler.toml`:\n\n```toml\n[[workflows]]\nname = \"my-workflow\"\nbinding = \"MY_WORKFLOW\"\nclass_name = \"MyWorkflow\"\n```\n\n### Artifacts\n\n```zig\nconst arts = try env.artifacts(\"ARTIFACTS\");\n\n// Create a repo\nconst result = try arts.create(\"my-repo\", .{});\nconst remote = result.remote;\nconst token = result.token;\n\n// Get a repo handle\nif (try arts.get(\"my-repo\")) |repo| {\n    // Get repo info (JSON)\n    const info_json = try repo.info();\n\n    // Mint a read token valid for 1 hour\n    const tok_json = try repo.createToken(.read, 3600);\n\n    // Fork into a new repo\n    const fork_json = try repo.fork(\"my-repo-fork\", .{\n        .description = \"Fork for testing\",\n        .default_branch_only = true,\n    });\n}\n\n// List repos\nconst list_json = try arts.list(.{ .limit = 20 });\n\n// Import a public GitHub repo\nconst result = try arts.import(.{\n    .source = .{\n        .url = \"https://github.com/nilslice/workers-zig\",\n        .branch = \"main\",\n        .depth = 1,\n    },\n    .target = .{\n        .name = \"my-mirror\",\n    },\n});\n\n// Access imported repo via result.repo\nconst info = try result.repo.info();\n\n// Delete a repo\n_ = arts.delete(\"my-repo\");\n```\n\nConfigure the binding in `wrangler.toml`:\n\n```toml\n[[artifacts]]\nbinding = \"ARTIFACTS\"\nnamespace = \"default\"\n```\n\n### WebSockets\n\n```zig\nvar ws = workers.WebSocket.init(allocator);\nws.accept();\nws.sendText(\"connected\");\n\nwhile (ws.receive()) |event| {\n    switch (event.type()) {\n        .text =\u003e {\n            const msg = try event.text();\n            ws.sendText(msg); // echo\n        },\n        .close =\u003e {\n            ws.close(1000, \"bye\");\n            break;\n        },\n        else =\u003e {},\n    }\n}\n\nreturn ws.response();\n```\n\n### TCP Sockets\n\n```zig\n// Plaintext or simple TLS via cloudflare:sockets\nvar socket = try workers.Socket.connect(allocator, \"example.com\", 80, .{});\nsocket.write(\"GET / HTTP/1.0\\r\\n\\r\\n\");\nconst data = try socket.read();\nsocket.close();\n\n// Extended TLS via node:tls — custom CA, SNI override, or mTLS\nvar tls = try workers.Socket.connectTls(allocator, \"api.example.com\", 443, .{\n    .servername = \"api.example.com\",\n    .cert = try env.get(\"CLIENT_CERT\"),\n    .key  = try env.get(\"CLIENT_KEY\"),\n});\ndefer tls.close();\n```\n\n`connectTls` requires `compatibility_flags = [\"nodejs_compat\"]` in your `wrangler.toml`.\n\n### Queues\n\n```zig\n// Producer\nconst queue = try env.queue(\"MY_QUEUE\");\nqueue.send(\"message body\");\n\n// Consumer (export the handler)\npub fn queue(batch: *workers.Queue.Batch, env: *workers.Env, ctx: *workers.Context) !void {\n    for (batch.messages()) |msg| {\n        const body = msg.body();\n        msg.ack();\n    }\n}\n```\n\n### Streaming Responses\n\n```zig\nvar stream = workers.StreamingResponse.start(.{});\nstream.setHeader(\"content-type\", \"text/event-stream\");\nstream.write(\"data: hello\\n\\n\");\nstream.write(\"data: world\\n\\n\");\nstream.close();\nreturn stream.response();\n```\n\n### Email\n\nIncoming email routing:\n\n```zig\npub fn email(message: *workers.EmailMessage, _: *workers.Env, _: *workers.Context) !void {\n    const from = message.from();\n    const to = message.to();\n    const size = message.rawSize();\n\n    // Forward, reply, or reject\n    try message.forward(\"admin@example.com\");\n}\n```\n\nOutbound email:\n\n```zig\nconst mailer = try env.sendEmail(\"EMAIL\");\ntry mailer.send(.{\n    .from = \"noreply@example.com\",\n    .to = \"user@example.com\",\n    .subject = \"Hello from Zig\",\n    .body_text = \"Plain text body\",\n    .body_html = \"\u003ch1\u003eHello!\u003c/h1\u003e\",\n});\n```\n\n### Tail Workers\n\n```zig\npub fn tail(events: []const workers.Tail.TraceItem, env: *workers.Env, _: *workers.Context) !void {\n    for (events) |item| {\n        for (item.logs) |entry| {\n            workers.log(\"trace: {s}\", .{entry.message});\n        }\n    }\n}\n```\n\n### Additional Bindings\n\n| Binding | Access | Description |\n|---------|--------|-------------|\n| **Cache** | `workers.Cache` | Cache API (put, match, delete) |\n| **Vectorize** | `env.vectorize(\"INDEX\")` | Vector database for embeddings |\n| **Hyperdrive** | `env.hyperdrive(\"DB\")` | Connection pooling for databases |\n| **Analytics Engine** | `env.analyticsEngine(\"AE\")` | Write analytics data points |\n| **Rate Limiting** | `env.rateLimit(\"RL\")` | Rate limiter binding |\n| **Service Bindings** | `env.serviceBinding(\"SVC\")` | Call other Workers |\n| **Dispatch Namespace** | `env.dispatchNamespace(\"NS\")` | Workers for Platforms |\n| **Crypto** | `workers.Crypto` | Web Crypto (digest, random, sign, verify) |\n| **HTMLRewriter** | `workers.HTMLRewriter` | Streaming HTML transformation |\n| **FormData** | `workers.FormData` | Multipart form data parsing |\n| **EventSource** | `workers.EventSource` | Server-Sent Events (SSE) |\n| **Artifacts** | `env.artifacts(\"ARTIFACTS\")` | Durable Git repos (create, fork, tokens) |\n| **Container** | `workers.Container` | Container Workers |\n\n### Convenience Functions\n\n```zig\n// Allocators: `env.allocator` is a per-request arena freed when the\n// request ends — prefer it inside `fetch` handlers. `workers.allocator`\n// is the process-lifetime wasm heap, for DOs / long-lived state.\nconst alloc = env.allocator;\n\n// Outbound HTTP fetch\nvar resp = try workers.fetch(alloc, \"https://api.example.com/data\", .{\n    .method = .POST,\n    .body = \"{\\\"key\\\": \\\"value\\\"}\",\n});\ndefer resp.deinit();\nconst body = try resp.text();\n\n// Current time (milliseconds since epoch)\nconst timestamp = workers.now();\n\n// Sleep (JSPI-suspending)\nworkers.sleep(1000);\n\n// Console logging\nworkers.log(\"request from {s}\", .{request.cf().country orelse \"unknown\"});\n```\n\n## Examples\n\nSee the [`examples/`](examples/) directory:\n\n| Example | Description |\n|---------|-------------|\n| [01-hello](examples/01-hello/) | Router-based hello world with path params |\n| [02-kv-r2](examples/02-kv-r2/) | KV and R2 storage operations |\n| [03-durable-object](examples/03-durable-object/) | Durable Object counter with increment/get/reset |\n| [04-websocket-ai](examples/04-websocket-ai/) | WebSocket echo server + Workers AI text generation |\n| [05-tcp-echo](examples/05-tcp-echo/) | Outbound TCP socket + HTTP fetch |\n\nEach example is a standalone project. To run one:\n\n```sh\ncd examples/01-hello\nzig build \u0026\u0026 npx wrangler dev\n```\n\n## Project Structure\n\n```\nworkers-zig/\n├── build.zig          # Build system with addWorker() helper\n├── build.zig.zon      # Package manifest\n├── src/\n│   ├── root.zig       # Public API surface\n│   ├── entry.zig      # Wasm export dispatcher\n│   ├── js.zig         # FFI extern declarations\n│   ├── gen_entry.zig  # Build tool: wasm → entry.js\n│   ├── Router.zig     # HTTP router\n│   ├── Workflow.zig   # Workflows API\n│   ├── ...            # One file per binding\n│   └── Tail.zig       # Tail workers\n├── js/\n│   └── shim.js        # JS glue layer (JSPI, handle table, API mapping)\n└── examples/          # Standalone example workers\n```\n\n## Requirements\n\n- **Zig 0.16.0** or later\n- **Wrangler** for local development and deployment\n- Workers runtime with **JSPI support** (standard on Cloudflare Workers)\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnilslice%2Fworkers-zig","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnilslice%2Fworkers-zig","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnilslice%2Fworkers-zig/lists"}