{"id":13626684,"url":"https://github.com/jedisct1/zigly","last_synced_at":"2026-01-20T01:46:03.789Z","repository":{"id":50676666,"uuid":"313706820","full_name":"jedisct1/zigly","owner":"jedisct1","description":"The easiest way to write services for Fastly's Compute@Edge in Zig.","archived":false,"fork":false,"pushed_at":"2025-03-05T12:33:15.000Z","size":125,"stargazers_count":88,"open_issues_count":0,"forks_count":2,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-31T12:04:30.240Z","etag":null,"topics":["fastly","webassembly","zig","zig-library","zig-package"],"latest_commit_sha":null,"homepage":"","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jedisct1.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}},"created_at":"2020-11-17T18:24:22.000Z","updated_at":"2025-03-24T13:18:29.000Z","dependencies_parsed_at":"2024-10-31T03:02:55.282Z","dependency_job_id":"999d0c8d-4cc6-4a2e-b102-a4a3f5246108","html_url":"https://github.com/jedisct1/zigly","commit_stats":{"total_commits":144,"total_committers":3,"mean_commits":48.0,"dds":0.06944444444444442,"last_synced_commit":"c776a1eb82d3e698a0d54e1a17539fbeb1526b14"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jedisct1%2Fzigly","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jedisct1%2Fzigly/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jedisct1%2Fzigly/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jedisct1%2Fzigly/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jedisct1","download_url":"https://codeload.github.com/jedisct1/zigly/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247657276,"owners_count":20974344,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["fastly","webassembly","zig","zig-library","zig-package"],"created_at":"2024-08-01T21:02:26.504Z","updated_at":"2026-01-20T01:46:03.782Z","avatar_url":"https://github.com/jedisct1.png","language":"Zig","funding_links":[],"categories":["Zig","Libraries"],"sub_categories":[],"readme":"![Zigly](logo.png)\n========\n\nThe easiest way to write Fastly Compute services in Zig.\n\n- [What is Fastly Compute?](#what-is-fastly-compute)\n- [What is Zigly?](#what-is-zigly)\n- [Usage](#usage)\n  - [Example application](#example-application)\n  - [Adding Zigly as a dependency](#adding-zigly-as-a-dependency)\n  - [A minimal WebAssembly program](#a-minimal-webassembly-program)\n  - [Testing Fastly Compute modules](#testing-fastly-compute-modules)\n  - [Using Zigly](#using-zigly)\n    - [Hello world!](#hello-world)\n    - [Inspecting incoming requests](#inspecting-incoming-requests)\n    - [Making HTTP queries](#making-http-queries)\n    - [Cache override](#cache-override)\n    - [Pipes](#pipes)\n  - [Proxying](#proxying)\n  - [Redirects](#redirects)\n  - [Response decompression](#response-decompression)\n    - [Dictionaries](#dictionaries)\n    - [Logging](#logging)\n    - [KV Store](#kv-store)\n    - [Geolocation](#geolocation)\n    - [User Agent Parsing](#user-agent-parsing)\n    - [Dynamic Backends](#dynamic-backends)\n    - [ACL (Access Control Lists)](#acl-access-control-lists)\n    - [Device Detection](#device-detection)\n    - [Cache Purging](#cache-purging)\n    - [Runtime Metrics](#runtime-metrics)\n    - [Cache Transactions](#cache-transactions)\n    - [Rate Limiting](#rate-limiting)\n- [Deployment to Fastly's platform](#deployment-to-fastlys-platform)\n\n## What is Fastly Compute?\n\n[Fastly Compute](https://www.fastly.com/products/compute) is [Fastly](https://fastly.com)'s service to run custom code directly on CDN nodes.\n\nThe service runs anything that can be compiled to WebAssembly, and exports a convenient set of functions to interact with the platform.\n\n## What is Zigly?\n\nZigly is a library that makes it easy to write Fastly Compute modules in [Zig](https://ziglang.org).\n\nBeyond the functions exported by the Fastly platform, Zigly will eventually include additional utility functions (cookie manipulation, JWT tokens, tracing...) to make application development as simple as possible.\n\nZigly is written for Zig 0.16.x and later versions.\n\n## Usage\n\n### Example application\n\nCheck out the `example` directory.\n\nThis contains an example Fastly application that relays all incoming traffic to a backend server, with transparent caching.\n\nIf you just want to use Fastly as a CDN, this is all you need!\n\n### Adding Zigly as a dependency\n\nAdd the dependency to your project:\n\n```sh\nzig fetch --save=zigly https://github.com/jedisct1/zigly/archive/refs/tags/0.1.12.tar.gz\n```\n\nAnd the following to your `build.zig` file:\n\n```zig\n    const zigly = b.dependency(\"zigly\", .{\n        .target = target,\n        .optimize = optimize,\n    });\n\n    const exe_module = b.createModule(.{\n        .root_source_file = b.path(\"src/main.zig\"),\n        .target = target,\n        .optimize = optimize,\n    });\n    exe_module.addImport(\"zigly\", zigly.module(\"zigly\"));\n    exe_module.linkLibrary(zigly.artifact(\"zigly\"));\n\n    const exe = b.addExecutable(.{\n        .name = \"my_app\",\n        .root_module = exe_module,\n    });\n\n    b.installArtifact(exe);\n```\n\nThe `zigly` structure can be imported in your application with:\n\n```zig\nconst zigly = @import(\"zigly\");\n```\n\n### A minimal WebAssembly program\n\n```zig\nconst std = @import(\"std\");\n\npub fn main() !void {\n    std.debug.print(\"Hello from WebAssembly and Zig!\\n\", .{});\n}\n```\n\nThe program can be compiled with (replace `example.zig` with the source file name):\n\n```sh\nzig build-exe -target wasm32-wasi example.zig\n```\n\nHappy with the result? Add `-Doptimize=ReleaseSmall` or `-Doptimize=ReleaseFast` to get very small or very fast module:\n\n```sh\nzig build-exe -target wasm32-wasi -Doptimize=ReleaseSmall example.zig\n```\n\nThe example above should not compile to more than 411 bytes.\n\nIf you are using a build file instead, define the target as `wasm32-wasi` in the `build.zig` file:\n\n```zig\nconst target = b.standardTargetOptions(.{ .default_target = .{ .cpu_arch = .wasm32, .os_tag = .wasi } });\n```\n\n...and build with `zig build -Doptimize=ReleaseSmall` or `-Doptimize=ReleaseFast` to get optimized modules.\n\n### Testing Fastly Compute modules\n\nThe easiest way to test the resulting modules is to use [Viceroy](https://github.com/fastly/Viceroy), a reimplementation of the Fastly API that runs locally.\n\n### Using Zigly\n\n#### Hello world!\n\n```zig\nconst downstream = try zigly.downstream();\nvar response = downstream.response;\ntry response.body.writeAll(\"Hello world!\");\ntry response.finish();\n```\n\n`downstream()` returns a type representing the initial connection, from a client to the proxy.\n\nThat type includes `response`, that can be used to send a response, as well as `request`, that can be used to inspect the incoming request.\n\nEvery function call may fail with an error from the `FastlyError` set.\n\nSlightly more complicated example:\n\n```zig\nconst downstream = try zigly.downstream();\nvar response = downstream.response;\n\ntry response.setStatus(201);\ntry response.headers.set(\"X-Example\", \"Header\");\n\ntry response.body.writeAll(\"Partial\");\ntry response.flush();\ntry response.body.writeAll(\"Response\");\ntry response.finish();\n\nvar logger = try zigly.Logger.open(\"logging_endpoint\");\ntry logger.write(\"Operation successful!\");\n```\n\nNote that calling `finish()` is always required in order to actually send a response to the client.\n\nBut realistically, most responses will either be simple redirects:\n\n```zig\nvar downstream = try zigly.downstream();\ntry downstream.redirect(302, \"https://www.perdu.com\");\n```\n\nor responding directly from the cache, proxying to the origin if the cached entry is nonexistent or expired:\n\n```zig\nvar downstream = try zigly.downstream();\ntry downstream.proxy(\"google\", \"www.google.com\");\n```\n\n#### Inspecting incoming requests\n\nApplications can read the body of an incoming requests as well as other informations such as the headers:\n\n```zig\nvar request = downstream.request;\nconst user_agent = try request.headers.get(allocator, \"user-agent\");\nif (try request.isPost()) {\n    // method is POST, read the body until the end, up to 1000000 bytes\n    const body = try request.body.readAll(allocator, 1000000);\n}\n```\n\nAs usual in Zig, memory allocations are never hidden, and applications can choose the allocator they want to use for individual function calls.\n\n#### Making HTTP queries\n\nMaking HTTP queries is easy:\n\n```zig\nvar query = try zigly.http.Request.new(\"GET\", \"https://example.com\");\nvar response = try query.send(\"backend\");\nconst body = try response.body.readAll(allocator, 0);\n```\n\nArbitrary headers can be added the the outgoing `query`:\n\n```zig\ntry query.headers.set(\"X-Custom-Header\", \"Custom value\");\n```\n\nBody content can also be pushed, even as chunks:\n\n```zig\n_ = try query.body.write(\"X\");\n_ = try query.body.write(\"Y\");\ntry query.body.close();\n```\n\nAnd the resulting `response` contains `headers` and `body` properties, that can be inspected the same way as a downstream query.\n\n#### Cache override\n\nCaching can be disabled or configured on a per-query basis with `setCachingPolicy()`:\n\n```zig\ntry query.setCachingPolicy(.{ .serve_stale = 600, .pci = true });\n```\n\nAttributes include:\n\n- `no_cache`\n- `ttl`\n- `serve_stale`\n- `pci`\n- `surrogate_key`\n\n#### Pipes\n\nWith `pipe()`, the response sent to a client can be a direct copy of another response. The application will then act as a proxy, optionally also copying the original status and headers.\n\n```zig\nvar query = try zigly.http.Request.new(\"GET\", \"https://google.com\");\nvar upstream_response = try query.send(\"google\");\nvar downstream = try zigly.downstream();\ntry downstream.response.pipe(\u0026upstream_response, true, true);\n```\n\n### Proxying\n\nProxying is even easier to use than pipes when a query should be sent unmodified (with the exception of the `Host` header) to the origin:\n\n```zig\nvar downstream = try zigly.downstream();\ntry downstream.proxy(\"google\", \"www.google.com\");\n```\n\nThe second parameter is optional. If `null`, the original `Host` header will not be modified.\n\n### Redirects\n\nRedirecting the client to another address can be done with a single function call on the downstream object:\n\n```zig\nconst downstream = try zigly.downstream();\ntry downstream.redirect(302, \"https://www.perdu.com\");\n```\n\n### Response decompression\n\nBy default, responses are left as-is. Which means that if compression (`Content-Encoding`) was accepted by the client, the response can be compressed.\n\nCalling `setAutoDecompressResponse(true)` on a `Request` object configures the Fastly Compute runtime to decompress gzip-encoded responses before streaming them to the application.\n\n#### Dictionaries\n\n```zig\nconst dict = try zigly.Dictionary.open(\"name\");\nconst value = try dict.get(allocator, \"key\");\n```\n\n#### Logging\n\n```zig\nvar logger = try zigly.Logger.open(\"endpoint\");\ntry logger.write(\"Log entry\");\n```\n\n#### KV Store\n\nStore and retrieve key-value pairs using Fastly's object store:\n\n```zig\nvar store = try zigly.kv.Store.open(\"my_store\");\nconst value = try store.getAll(\"key\", allocator, 0);\n\n// Insert or replace a value\ntry store.replace(\"key\", \"new_value\");\n```\n\n#### Geolocation\n\nGet location information about IP addresses:\n\n```zig\nconst ip = zigly.geo.Ip{ .ip4 = .{ 8, 8, 8, 8 } };\nvar buf: [4096]u8 = undefined;\nconst location = try zigly.geo.lookup(allocator, ip, \u0026buf);\n// Access location.value fields: city, country_code, latitude, longitude, etc.\n```\n\n#### User Agent Parsing\n\nParse user agent strings:\n\n```zig\nvar family: [64]u8 = undefined;\nvar major: [16]u8 = undefined;\nvar minor: [16]u8 = undefined;\nvar patch: [16]u8 = undefined;\nconst ua = try zigly.UserAgent.parse(user_agent_string, \u0026family, \u0026major, \u0026minor, \u0026patch);\n// Access ua.family, ua.major, ua.minor, ua.patch\n```\n\n#### Dynamic Backends\n\nRegister backends dynamically at runtime:\n\n```zig\nconst backend_config = zigly.DynamicBackend{\n    .name = \"my_backend\",\n    .target = \"example.com:443\",\n    .use_ssl = true,\n    .host_override = \"example.com\",\n    .sni_hostname = \"example.com\",\n    .cert_hostname = \"example.com\",\n    .connect_timeout_ms = 5000,\n    .first_byte_timeout_ms = 15000,\n    .between_bytes_timeout_ms = 10000,\n};\nconst backend = try backend_config.register();\n\n// Check backend properties\nconst exists = try zigly.Backend.exists(\"my_backend\");\nconst is_ssl = try backend.isSsl();\nconst port = try backend.getPort();\n```\n\n#### ACL (Access Control Lists)\n\nCheck IP addresses against access control lists:\n\n```zig\nconst acl = try zigly.Acl.open(\"my_acl\");\n\nconst client_ip = zigly.geo.Ip{ .ip4 = .{ 192, 168, 1, 100 } };\nif (try acl.match(allocator, client_ip)) |result| {\n    defer result.deinit();\n    if (result.value.isBlock()) {\n        // IP is blocked\n    }\n    // result.value.action is \"BLOCK\" or \"ALLOW\"\n    // result.value.prefix is the matching rule (e.g., \"192.168.0.0/16\")\n} else {\n    // No matching rule found\n}\n```\n\n#### Device Detection\n\nDetect device type from User-Agent strings:\n\n```zig\nconst user_agent = \"Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)\";\nvar buf: [4096]u8 = undefined;\n\nconst result = try zigly.device.lookup(allocator, user_agent, \u0026buf);\ndefer result.deinit();\n\n// Access device properties\nconst device = result.value.device;\n// device.name, device.brand, device.model, device.hwtype\n// device.is_mobile, device.is_tablet, device.is_desktop, etc.\n\n// Or use convenience functions\nconst is_mobile = try zigly.device.isMobile(allocator, user_agent);\nconst is_desktop = try zigly.device.isDesktop(allocator, user_agent);\n```\n\n#### Cache Purging\n\nPurge cached content by surrogate key:\n\n```zig\n// Hard purge (immediate removal)\ntry zigly.purge.purge(\"my-surrogate-key\");\n\n// Soft purge (mark as stale)\ntry zigly.purge.softPurge(\"my-surrogate-key\");\n```\n\n#### Runtime Metrics\n\nMonitor compute resource usage:\n\n```zig\nconst vcpu_ms = try zigly.runtime.getVcpuMs();\n// Returns the amount of vCPU time used in milliseconds\n```\n\n#### Cache Transactions\n\nThe cache API provides both simple caching and request-collapsing transactions:\n\n```zig\nconst cache = zigly.cache;\n\n// Simple cache insert\nvar body = try cache.insert(\"my-cache-key\", .{\n    .max_age_ns = cache.secondsToNs(3600),  // 1 hour TTL\n});\n_ = try body.write(\"Cached content\");\ntry body.close();\n\n// Simple cache lookup\nvar entry = try cache.lookup(\"my-cache-key\", .{});\nconst state = try entry.getState();\nif (state.isFound() and state.isUsable()) {\n    var cached_body = try entry.getBody(null);\n    const content = try cached_body.readAll(allocator, 0);\n    // Use cached content\n}\ntry entry.close();\n```\n\nFor request-collapsing (preventing thundering herd), use transactional lookups:\n\n```zig\n// Transactional lookup - only one request will fetch/generate content\nvar tx = try cache.transactionLookup(\"my-key\", .{});\nconst tx_state = try tx.getState();\n\nif (tx_state.mustInsertOrUpdate()) {\n    // We won the race - insert new content\n    var result = try tx.insert(.{\n        .max_age_ns = cache.secondsToNs(60),\n    });\n    _ = try result.body.write(\"Fresh content\");\n    try result.body.close();\n} else if (tx_state.isUsable()) {\n    // Content is available\n    var cached_body = try tx.getBody(null);\n    const content = try cached_body.readAll(allocator, 0);\n}\n\ntry tx.close();\n```\n\n#### Rate Limiting\n\nEdge Rate Limiting provides rate counters and penalty boxes for traffic control:\n\n```zig\nconst erl = zigly.erl;\n\n// Rate counter - track request rates\nconst rc = erl.RateCounter.open(\"my_rate_counter\");\ntry rc.increment(\"client-ip-192.168.1.1\", 1);\n\nconst rate = try rc.lookupRate(\"client-ip-192.168.1.1\", 10);  // 10-second window\nconst count = try rc.lookupCount(\"client-ip-192.168.1.1\", 60); // 60-second window\n\n// Penalty box - block bad actors\nconst pb = erl.PenaltyBox.open(\"my_penalty_box\");\nif (try pb.has(\"bad-actor\")) {\n    // Client is in penalty box, reject request\n}\ntry pb.add(\"bad-actor\", 300);  // Block for 5 minutes\n\n// Combined rate limiter\nconst limiter = erl.RateLimiter.init(.{\n    .rate_counter = \"my_rate_counter\",\n    .penalty_box = \"my_penalty_box\",\n    .window_seconds = 10,\n    .limit = 100,           // 100 requests per 10 seconds\n    .ttl_seconds = 300,     // 5 minute penalty\n});\n\nif (!try limiter.isAllowed(\"client-id\", 1)) {\n    // Rate limited - reject request\n}\n```\n\n## Deployment to Fastly's platform\n\nThe `fastly` command-line tool only supports compilation of Rust and AssemblyScript at the moment.\nHowever, it can still be used to upload pre-compiled code written in other languages, including Zig.\n\n1. Create a new project:\n\n```sh\nfastly compute init\n```\n\nFor the language, select `Other (pre-compiled WASM binary)`.\n\n2. Add a build script:\n\nAdd the following lines to the fastly.toml file:\n\n```toml\n[scripts]\nbuild = \"zig build -Doptimize=ReleaseSmall -Dtarget=wasm32-wasi \u0026\u0026 mkdir -p bin \u0026\u0026 cp zig-out/bin/*.wasm bin/main.wasm\"\n```\n\n3. Compile and package the Fastly Compute module:\n\n```sh\nfastly compute build\n```\n\n4. Test locally\n\n```sh\nfastly compute serve\n```\n\n5. Deploy!\n\n```sh\nfastly compute deploy\n```\n\nIn order to deploy new versions, repeat steps 3 and 5.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjedisct1%2Fzigly","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjedisct1%2Fzigly","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjedisct1%2Fzigly/lists"}