diff --git a/docs/docs/get-started/quickstart.md b/docs/docs/get-started/quickstart.md
index 82bda31a..6aba1ef2 100644
--- a/docs/docs/get-started/quickstart.md
+++ b/docs/docs/get-started/quickstart.md
@@ -71,5 +71,6 @@ Now you got the taste of Wasm Workers, it's time to create your first worker:
* [Create your first Python worker](../languages/python.md)
* [Create your first Ruby worker](../languages/ruby.md)
* [Create your first Go worker](../languages/go.md)
+* [Create your first Zig worker](../languages/zig.md)
And if you are curious, here you have a guide about [how it works](./how-it-works.md).
diff --git a/docs/docs/languages/zig.md b/docs/docs/languages/zig.md
index fc81389a..4a4fa4e3 100644
--- a/docs/docs/languages/zig.md
+++ b/docs/docs/languages/zig.md
@@ -4,369 +4,465 @@ sidebar_position: 6
# Zig
-Zig workers are tested with Zig version `0.11.0`. Then, they are loaded by Wasm Workers Server and start processing requests.
+Zig workers are tested with Zig version `0.12.0`. Then, they are loaded by Wasm Workers Server and start processing requests.
## Your first Zig worker
-The recommended way to implement workers is by using the `worker.ServeFunc` function.
+The recommended way to implement workers is by using the `wws.zig` SDK.
In this example, the worker will get a request and print all the related information.
1. Create a new Zig project:
```shell-session
- zig init-exe
+ zig init
```
2. Add Wasm Workers Server Zig dependency
- At this point in time Zigs Package manager is not yet available. We will therefore clone the repository to make the library locally available.
+ At this point in time, the Zig SDK is located at a subpath of the wasm-workers-server repo. Zig's package manager works with archives and does not support referencing a package as a subpath of an archive. We will therefore add wasm-workers-server as a submodule of our project.
```shell-session
mkdir lib
- wget -O ./lib/worker.zig https://raw.githubusercontent.com/vmware-labs/wasm-workers-server/main/kits/zig/worker/src/worker.zig
+ git submodule add https://github.com/vmware-labs/wasm-workers-server.git lib/wws
```
-3. Edit the `src/main.zig` to match the following contents:
+3. Add the wws dependency to `build.zig.zon`
- ```c title="worker.zig"
- const std = @import("std");
- const worker = @import("worker");
-
- fn requestFn(resp: *worker.Response, r: *worker.Request) void {
- _ = r;
+ We can now modify the `build.zig.zon` file to include the Zig SDK as a dependency:
- _ = &resp.headers.append("x-generated-by", "wasm-workers-server");
- _ = &resp.writeAll("hello from zig");
+ ```zig title="build.zig.zon"
+ .{
+ // ...
+ .dependencies = .{
+ // Add this to dependencies
+ .wws = .{
+ .path = "lib/wws/kits/zig/wws",
+ },
+ },
+ // ...
}
+ ```
- pub fn main() !void {
- worker.ServeFunc(requestFn);
+4. Edit the `build.zig`
+
+ The WWS Zig SDK exports a few build-time helpers to compile your executable to WASM and generate your worker config. Let's update the `build.zig` to include the `wws` dependency and build our worker and its associated config.
+
+ ```zig title="build.zig"
+ const std = @import("std");
+ const wws = @import("wws");
+
+ pub fn build(b: *std.Build) !void {
+ // getTarget is a helper to get the proper wasm target
+ const target = wws.getTarget(b);
+
+ const optimize = b.standardOptimizeOption(.{});
+
+ // This references the wws dependency we added to build.zig.zon
+ const wws_dep = b.dependency("wws", .{});
+
+ // addWorker lets us specify similar options to std.Build.addExecutable
+ const worker = try wws.addWorker(b, .{
+ .name = "example",
+ // path lets us specify the file path for our worker and its config.
+ // This will affect routing.
+ .path = "hello/[name]",
+ .root_source_file = .{ .path = "src/main.zig" },
+ .target = target,
+ .optimize = optimize,
+ // We need to pass the wws dependency to addWorker so it can make sure
+ // that the wws module is provided to your application.
+ .wws = wws_dep,
+ // features lets you specify which WWS features your worker will have
+ // access to.
+ .features = .{ .kv = .{ .namespace = "example" } },
+ });
+
+ // We use addWriteFiles to provide an output destination for our worker
+ // and its config.
+ const wf = b.addWriteFiles();
+
+ // addToWriteFiles will add the compiled wasm and worker config to a subpath
+ // determined by the path provided to addWorker above.
+ try worker.addToWriteFiles(b, wf);
+
+ {
+ // Copy the wf directory contents into the zig-out directory.
+ const install = b.addInstallDirectory(.{
+ .source_dir = wf.getDirectory(),
+ .install_dir = .prefix,
+ // The contents of wf will be accessible in zig-out/root.
+ .install_subdir = "root",
+ });
+
+ b.getInstallStep().dependOn(&install.step);
+ }
}
```
-4. Additionally, you can now go further add all the information from the received `worker.Request`:
+5. Edit `src/main.zig` to match the following contents:
- ```c title="worker.zig"
+ ```zig title="src/main.zig"
const std = @import("std");
- const worker = @import("worker");
-
- var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
- const allocator = arena.allocator();
+ const wws = @import("wws");
+
+ const tmpl =
+ \\
+ \\
+ \\Wasm Workers Server
+ \\
+ \\
+ \\
+ \\
+ \\
+ \\
+ \\
+ \\Hello {[name]s} from Wasm Workers Server 👋
+ \\Replying to {[url]s}
+ \\Method: {[method]s}
+ \\User Agent: {[user_agent]s}
+ \\Payload: {[payload]s}
+ \\
+ \\This page was generated by a Zig⚡️ file running in WebAssembly.
+ \\
+ \\
+ \\
+ \\
+ ;
- fn requestFn(resp: *worker.Response, r: *worker.Request) void {
- std.debug.print("Hello from function\n", .{ });
+ fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response {
+ const payload = if (request.body.len == 0) "-" else request.body;
+ const user_agent = request.headers.map.get("user-agent") orelse "-";
+ const name = request.params.map.get("name") orelse "world";
- // // TODO: prepare to read request body and send it back
- std.debug.print("+++ doing payload \n", .{ });
+ const body = try std.fmt.allocPrint(arena, tmpl, .{
+ .url = request.url,
+ .method = request.method,
+ .user_agent = user_agent,
+ .payload = payload,
+ .name = name,
+ });
- var payload: []const u8 = "";
- var reqBody = r.data;
+ var response = wws.Response{
+ .data = body,
+ };
- if (reqBody.len == 0) {
- payload = "-";
- } else {
- payload = reqBody;
- }
+ try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server");
- const s =
- \\
- \\
- \\Wasm Workers Server
- \\
- \\
- \\
- \\
- \\
- \\
- \\
- \\Hello from Wasm Workers Server 👋
- \\Replying to {s}
- \\Method: {s}
- \\User Agent: {s}
- \\Payload: {s}
- \\
- \\This page was generated by a Zig⚡️ file running in WebAssembly.
- \\
- \\
- \\
- ;
-
- var body = std.fmt.allocPrint(allocator, s, .{ r.url.path, r.method, "-", payload }) catch undefined;
-
- _ = &resp.headers.append("x-generated-by", "wasm-workers-server");
- _ = &resp.writeAll(body);
+ return response;
}
pub fn main() !void {
- worker.ServeFunc(requestFn);
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+
+ const parse_result = try wws.parseStream(gpa.allocator(), .{});
+ defer parse_result.deinit();
+
+ var arena = std.heap.ArenaAllocator.init(gpa.allocator());
+ defer arena.deinit();
+
+ const request = parse_result.value;
+ const response = try handle(arena.allocator(), request);
+
+ const stdout = std.io.getStdOut();
+ try wws.writeResponse(response, stdout.writer());
}
```
-5. Compile the project
+6. Compile the project
```shell-session
- zig build-exe src/main.zig \
- --name worker \
- -mexec-model=reactor \
- -target wasm32-wasi \
- --mod worker::lib/worker.zig \
- --deps worker
+ $ zig build
+ $ tree zig-out
+ zig-out
+ └── root
+ └── hello
+ ├── [name].toml
+ └── [name].wasm
```
- You can also use a build script to build the project with a simple `zig build`, please find some inspiration in our [zig examples](https://github.com/vmware-labs/wasm-workers-server/tree/main/examples/).
-
-6. Run your worker with `wws`. If you didn't download the `wws` server yet, check our [Getting Started](../get-started/quickstart.md) guide.
+7. Run your worker with `wws`. If you haven't downloaded the `wws` server yet, check our [Getting Started](../get-started/quickstart.md) guide.
```shell-session
- wws .
-
- ⚙️ Loading routes from: .
- 🗺 Detected routes:
- - http://127.0.0.1:8080/worker
- => worker.wasm (name: default)
+ $ wws zig-out/root/
+ ⚙️ Preparing the project from: zig-out/root/
+ ⚙️ Loading routes from: zig-out/root/
+ ⏳ Loading workers from 1 routes...
+ ✅ Workers loaded in 35.659083ms.
+ - http://127.0.0.1:8080/hello/[name]
+ => zig-out/root/hello/[name].wasm
🚀 Start serving requests at http://127.0.0.1:8080
```
-7. Finally, open in your browser.
+8. Finally, open in your browser.
## Add a Key / Value store
Wasm Workers allows you to add a Key / Value store to your workers. Read more information about this feature in the [Key / Value store](../features/key-value.md) section.
-To add a KV store to your worker, follow these steps:
-
-1. Create a new Zig project:
-
- ```shell-session
- zig init-exe
- ```
-
-2. Add Wasm Workers Server Zig dependency
-
- At this point in time Zigs Package manager is not yet available. We will therefore clone the repository to make the library locally available.
-
- ```shell-session
- mkdir lib
- wget -O ./lib/worker.zig https://raw.githubusercontent.com/vmware-labs/wasm-workers-server/main/kits/zig/worker/worker.zig ./lib
- ```
-
-1. Edit `src/main.zig` file with the following contents:
+We can add a KV store to our Zig worker by specifying it inside `features` when we call `wws.addWorker`:
+
+```zig title="build.zig"
+pub fn build(b: *std.Build) !void {
+ // ...
+
+ const worker = try wws.addWorker(b, .{
+ .name = "workerkv",
+ .root_source_file = .{ .path = "src/main.zig" },
+ .target = target,
+ .optimize = optimize,
+ .wws = wws_dep,
+ .features = .{
+ // Define your KV namespace here
+ .kv = .{ .namespace = "workerkv" },
+ },
+ });
+
+ // ...
+}
+```
- ```c title="main.zig"
- const std = @import("std");
- const worker = @import("worker");
+We can see the resulting TOML configuration file when we call `build.zig`:
- var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
- const allocator = arena.allocator();
+```shell-session
+$ zig build
+$ tree zig-out/root
+zig-out/root/
+├── workerkv.toml
+├── workerkv.wasm
+$ cat zig-out/root/workerkv.toml
+name = "workerkv"
+version = "1"
+[data]
+[data.kv]
+namespace = "workerkv"
+```
- fn requestFn(resp: *worker.Response, r: *worker.Request) void {
- var cache = r.context.cache;
- var counter: i32 = 0;
+Then, we can read from/write to the KV store with the following example:
- var v = cache.getOrPut("counter") catch undefined;
+```zig title="src/main.zig"
+const std = @import("std");
+const wws = @import("wws");
- if (!v.found_existing) {
- v.value_ptr.* = "0";
- } else {
- var counterValue = v.value_ptr.*;
- var num = std.fmt.parseInt(i32, counterValue, 10) catch undefined;
- counter = num + 1;
- var num_s = std.fmt.allocPrint(allocator, "{d}", .{ counter }) catch undefined;
- _ = cache.put("counter", num_s) catch undefined;
- }
+const tmpl =
+ \\
+ \\
+ \\
+ \\Wasm Workers Server - KV example
+ \\
+ \\
+ \\
+ \\
+ \\Key / Value store in Zig
+ \\Counter: {d}
+ \\This page was generated by a Zig⚡️ file running in WebAssembly.
+ \\
+;
- const s =
- \\
- \\
- \\
- \\Wasm Workers Server - KV example
- \\
- \\
- \\
- \\
- \\Key / Value store in Zig
- \\Counter: {d}
- \\This page was generated by a Zig⚡️ file running in WebAssembly.
- \\
- ;
-
- var body = std.fmt.allocPrint(allocator, s, .{ counter }) catch undefined; // add useragent
-
- _ = &resp.headers.append("x-generated-by", "wasm-workers-server");
- _ = &resp.writeAll(body);
- }
+fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response {
+ const value = request.kv.map.get("counter") orelse "0";
+ var counter = std.fmt.parseInt(i32, value, 10) catch 0;
- pub fn main() !void {
- worker.ServeFunc(requestFn);
- }
- ```
+ counter += 1;
-5. Compile the project
+ const body = try std.fmt.allocPrint(arena, tmpl, .{counter});
- ```shell-session
- zig build-exe src/main.zig \
- --name worker-kv \
- -mexec-model=reactor \
- -target wasm32-wasi \
- --mod worker::lib/worker.zig \
- --deps worker
- ```
+ var response = wws.Response{
+ .data = body,
+ };
- You can also use a build script to build the project with a simple `zig build`, please find some inspiration in our [zig examples](https://github.com/vmware-labs/wasm-workers-server/tree/main/examples/).
+ const num_s = try std.fmt.allocPrint(arena, "{d}", .{counter});
+ try response.kv.map.put(arena, "counter", num_s);
-1. Create a `worker-kv.toml` file with the following content. Note the name of the TOML file must match the name of the worker. In this case we have `worker-kv.wasm` and `worker-kv.toml` in the same folder:
+ try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server");
- ```toml title="worker-kv.toml"
- name = "workerkv"
- version = "1"
+ return response;
+}
- [data]
- [data.kv]
- namespace = "workerkv"
- ```
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
-1. Run your worker with `wws`. If you didn't download the `wws` server yet, check our [Getting Started](../get-started/quickstart.md) guide.
+ const parse_result = try wws.parseStream(gpa.allocator(), .{});
+ defer parse_result.deinit();
- ```shell-session
- wws .
+ var arena = std.heap.ArenaAllocator.init(gpa.allocator());
+ defer arena.deinit();
- ⚙️ Loading routes from: .
- 🗺 Detected routes:
- - http://127.0.0.1:8080/worker-kv
- => worker-kv.wasm (name: default)
- 🚀 Start serving requests at http://127.0.0.1:8080
- ```
-
-1. Finally, open in your browser.
+ const request = parse_result.value;
+ const response = try handle(arena.allocator(), request);
+ const stdout = std.io.getStdOut();
+ try wws.writeResponse(response, stdout.writer());
+}
+```
## Dynamic routes
-You can define [dynamic routes by adding route parameters to your worker files](../features/dynamic-routes.md) (like `[id].wasm`). To read them in Zig, follow these steps:
+You can define [dynamic routes by adding route parameters to your worker files](../features/dynamic-routes.md) (like `[id].wasm`). Using the Zig SDK, you can configure this in your `build.zig` when you declare your worker:
-1. Use the `worker.ParamsKey` context value to read in the passed in parameters:
-
- ```c title="main.zig"
- const std = @import("std");
- const worker = @import("worker");
+```zig title="build.zig"
+pub fn build(b: *std.Build) !void {
+ // ...
- fn requestFn(resp: *worker.Response, r: *worker.Request) void {
- var params = r.context.params;
+ const worker = try wws.addWorker(b, .{
+ .name = "params",
+ // We specify the file path (and therefore the HTTP route) of our worker
+ .path = "params/[id]",
+ .root_source_file = .{ .path = "src/main.zig" },
+ .target = target,
+ .optimize = optimize,
+ .wws = wws_dep,
+ });
- ...
- }
- ```
-
-2. Then, you can read the values as follows:
-
- ```c title="main.zig"
- const std = @import("std");
- const worker = @import("worker");
-
- var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
- const allocator = arena.allocator();
+ // ...
+}
+```
- fn requestFn(resp: *worker.Response, r: *worker.Request) void {
- var params = r.context.params;
+If we run `zig build`, we can see the resulting file tree:
- var id: []const u8 = "the value is not available";
+```shell-session
+$ zig build
+$ tree zig-out/root
+zig-out/root/
+├── params/[id].toml
+├── params/[id].wasm
+```
- var v = params.get("id");
+Then, you can read the params from the request with the following example:
- if (v) |val| {
- id = val;
- }
+```zig title="src/main.zig"
+const std = @import("std");
+const wws = @import("wws");
- const s =
- \\Hey! The parameter is: {s}
- ;
+const tmpl =
+ \\Hey! The parameter is: {s}
+;
- var body = std.fmt.allocPrint(allocator, s, .{ id }) catch undefined; // add useragent
+fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response {
+ const id = request.params.map.get("id") orelse "the value is not available";
- _ = &resp.headers.append("x-generated-by", "wasm-workers-server");
- _ = &resp.writeAll(body);
- }
+ const body = try std.fmt.allocPrint(arena, tmpl, .{id});
- pub fn main() !void {
- worker.ServeFunc(requestFn);
- }
- ```
+ var response = wws.Response{
+ .data = body,
+ };
-3. Compile the project
+ try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server");
- ```shell-session
- zig build-exe src/main.zig \
- --name "[id]" \
- -mexec-model=reactor \
- -target wasm32-wasi \
- --mod worker::lib/worker.zig \
- --deps worker
- ```
+ return response;
+}
- You can also use a build script to build the project with a simple `zig build`, please find some inspiration in our [zig examples](https://github.com/vmware-labs/wasm-workers-server/tree/main/examples/).
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+ const allocator = gpa.allocator();
-4. Run your worker with `wws`. If you didn't download the `wws` server yet, check our [Getting Started](../get-started/quickstart.md) guide.
+ const parse_result = try wws.parseStream(allocator, .{});
+ defer parse_result.deinit();
+ const request = parse_result.value;
- ```shell-session
- wws .
+ var arena_impl = std.heap.ArenaAllocator.init(allocator);
+ defer arena_impl.deinit();
+ const arena = arena_impl.allocator();
- ⚙️ Loading routes from: .
- 🗺 Detected routes:
- - http://127.0.0.1:8080/[id]
- => worker-kv.wasm (name: default)
- 🚀 Start serving requests at http://127.0.0.1:8080
- ```
+ const response = try handle(arena, request);
-5. Finally, open in your browser.
+ const stdout = std.io.getStdOut();
+ try wws.writeResponse(response, stdout.writer());
+}
+```
## Read environment variables
-Environment variables are configured [via the related TOML configuration file](../features/environment-variables.md). These variables are accessible via `std.process.getEnvMap` or `std.process.getEnvVarOwned` in your worker. To read them, just use the same name you configured in your TOML file:
+Environment variables are configured [via the related TOML configuration file](../features/environment-variables.md). While using the Zig SDK, this configuration file is generated for us when we call `wws.addWorker` in our `build.zig`:
+
+```zig title="build.zig"
+pub fn build(b: *std.Build) !void {
+ // ...
+
+ const worker = try wws.addWorker(b, .{
+ .name = "envs",
+ .root_source_file = .{ .path = "src/main.zig" },
+ .target = target,
+ .optimize = optimize,
+ .wws = wws_dep,
+ .features = .{
+ // Define your environment variables here
+ .vars = &.{
+ .{
+ .name = "MESSAGE",
+ .value = "Hello 👋! This message comes from an environment variable",
+ },
+ },
+ },
+ });
+
+ // ...
+}
+```
-```toml title="envs.toml"
+```shell-session
+$ zig build
+$ tree zig-out/root
+zig-out/root/
+├── envs.toml
+├── envs.wasm
+$ cat zig-out/root/envs.toml
name = "envs"
version = "1"
-
[vars]
MESSAGE = "Hello 👋! This message comes from an environment variable"
```
-Now, you can read the `MESSAGE` variable using either the `std.process.getEnvMap` or the `std.process.getEnvVarOwned` functions:
+These variables are accessible via `std.process.getEnvMap` or `std.process.getEnvVarOwned` in your worker. To read them, just use the same name you configured in `build.zig`:
-```c title="envs.zig"
+```zig title="envs.zig"
const std = @import("std");
-const worker = @import("worker");
+const wws = @import("wws");
-var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
-const allocator = arena.allocator();
+const tmpl =
+ \\The environment variable value is: {s}
+;
-fn requestFn(resp: *worker.Response, r: *worker.Request) void {
- _ = r;
+fn handle(arena: std.mem.Allocator) !wws.Response {
+ const envvar = std.process.getEnvVarOwned(arena, "MESSAGE") catch "";
- const envvar = std.process.getEnvVarOwned(allocator, "MESSAGE") catch "";
- defer allocator.free(envvar);
+ const body = try std.fmt.allocPrint(arena, tmpl, .{envvar});
- const s =
- \\The environment variable value is: {s}
- ;
+ var response = wws.Response{
+ .data = body,
+ };
- var body = std.fmt.allocPrint(allocator, s, .{ envvar }) catch undefined; // add useragent
+ try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server");
- _ = &resp.headers.append("x-generated-by", "wasm-workers-server");
- _ = &resp.writeAll(body);
+ return response;
}
pub fn main() !void {
- worker.ServeFunc(requestFn);
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+
+ var arena = std.heap.ArenaAllocator.init(gpa.allocator());
+ defer arena.deinit();
+
+ const response = try handle(arena.allocator());
+
+ const stdout = std.io.getStdOut();
+ try wws.writeResponse(response, stdout.writer());
}
```
@@ -378,7 +474,7 @@ Find other examples in the [`/examples` directory](https://github.com/vmware-lab
## Contributors
-The Zig kit was originally authored by Christoph Voigt ([@voigt](https://github.com/voigt)).
+The Zig kit was originally authored for Zig 0.11 by Christoph Voigt ([@voigt](https://github.com/voigt)). It was then updated for Zig 0.12 by Christopher Grass([@sea-grass](https://github.com/sea-grass)).
## Feature compatibility
diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js
index d15be73e..2b3d1670 100644
--- a/docs/docusaurus.config.js
+++ b/docs/docusaurus.config.js
@@ -114,7 +114,7 @@ const config = {
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
- additionalLanguages: ['rust', 'toml', 'ruby', 'python'],
+ additionalLanguages: ['rust', 'toml', 'ruby', 'python', 'zig'],
},
}),
};
diff --git a/examples/zig-basic/README.md b/examples/zig-basic/README.md
deleted file mode 100644
index c6f1305e..00000000
--- a/examples/zig-basic/README.md
+++ /dev/null
@@ -1,32 +0,0 @@
-# Basic example
-
-Compile a Zig worker to WebAssembly and run it in Wasm Workers Server.
-
-## Prerequisites
-
-* Wasm Workers Server (wws):
-
- ```shell-session
- curl -fsSL https://workers.wasmlabs.dev/install | bash
- ```
-
-* [Zig](https://ziglang.org/download/) `0.11.0`
-
-## Build
-
-All specific build confiugrations are in `build.zig` file.
-
-```shell-session
-zig build
-```
-
-## Run
-
-```shell-session
-wws ./zig-out/bin/
-```
-
-## Resources
-
-* [Zig documentation](https://workers.wasmlabs.dev/docs/languages/zig)
-* [Announcing Zig support for Wasm Workers Server](https://wasmlabs.dev/articles/Zig-support-on-wasm-workers-server/)
diff --git a/examples/zig-basic/build.zig b/examples/zig-basic/build.zig
deleted file mode 100644
index a59f70d6..00000000
--- a/examples/zig-basic/build.zig
+++ /dev/null
@@ -1,26 +0,0 @@
-const std = @import("std");
-
-const examples = [1][]const u8{ "basic" };
-
-pub fn build(b: *std.Build) !void {
- const target = try std.zig.CrossTarget.parse(.{ .arch_os_abi = "wasm32-wasi" });
- const optimize = b.standardOptimizeOption(.{});
-
- const worker_module = b.createModule(.{
- .source_file = .{ .path = "../../kits/zig/worker/src/worker.zig" },
- });
-
- inline for (examples) |example| {
- const exe = b.addExecutable(.{
- .name = example,
- .root_source_file = .{ .path = "src/" ++ example ++ ".zig" },
- .target = target,
- .optimize = optimize,
- });
-
- exe.wasi_exec_model = .reactor;
- exe.addModule("worker", worker_module);
-
- b.installArtifact(exe);
- }
-}
diff --git a/examples/zig-basic/src/basic.zig b/examples/zig-basic/src/basic.zig
deleted file mode 100644
index 77268184..00000000
--- a/examples/zig-basic/src/basic.zig
+++ /dev/null
@@ -1,57 +0,0 @@
-const std = @import("std");
-const worker = @import("worker");
-
-var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
-const allocator = arena.allocator();
-
-fn requestFn(resp: *worker.Response, r: *worker.Request) void {
-
- var payload: []const u8 = "";
- var reqBody = r.body;
-
- if (reqBody.len == 0) {
- payload = "-";
- } else {
- payload = reqBody;
- }
-
- const s =
- \\
- \\
- \\Wasm Workers Server
- \\
- \\
- \\
- \\
- \\
- \\
- \\
- \\Hello from Wasm Workers Server 👋
- \\Replying to {s}
- \\Method: {s}
- \\User Agent: {s}
- \\Payload: {s}
- \\
- \\This page was generated by a Zig⚡️ file running in WebAssembly.
- \\
- \\
- \\
- ;
-
- var body = std.fmt.allocPrint(allocator, s, .{ r.url.path, r.method, "-", payload }) catch undefined; // add useragent
-
- _ = &resp.headers.append("x-generated-by", "wasm-workers-server");
- _ = &resp.writeAll(body);
-}
-
-pub fn main() !void {
- worker.ServeFunc(requestFn);
-}
diff --git a/examples/zig-envs/README.md b/examples/zig-envs/README.md
deleted file mode 100644
index cdbca6a9..00000000
--- a/examples/zig-envs/README.md
+++ /dev/null
@@ -1,33 +0,0 @@
-# Zig environment variables example
-
-Compile a Zig worker to WebAssembly and run it in Wasm Workers Server.
-
-## Prerequisites
-
-* Wasm Workers Server (wws):
-
- ```shell-session
- curl -fsSL https://workers.wasmlabs.dev/install | bash
- ```
-
-* [Zig](https://ziglang.org/download/) `0.11.0`
-
-## Build
-
-All specific build configurations are in `build.zig` file.
-
-```shell-session
-zig build
-```
-
-## Run
-
-```shell-session
-wws ./zig-out/bin/
-```
-
-## Resources
-
-* [Environment variables](https://workers.wasmlabs.dev/docs/features/environment-variables)
-* [Zig documentation](https://workers.wasmlabs.dev/docs/languages/zig)
-* [Announcing Zig support for Wasm Workers Server](https://wasmlabs.dev/articles/Zig-support-on-wasm-workers-server/)
diff --git a/examples/zig-envs/build.zig b/examples/zig-envs/build.zig
deleted file mode 100644
index 67d8478a..00000000
--- a/examples/zig-envs/build.zig
+++ /dev/null
@@ -1,26 +0,0 @@
-const std = @import("std");
-
-const examples = [1][]const u8{ "envs" };
-
-pub fn build(b: *std.Build) !void {
- const target = try std.zig.CrossTarget.parse(.{ .arch_os_abi = "wasm32-wasi" });
- const optimize = b.standardOptimizeOption(.{});
-
- const worker_module = b.createModule(.{
- .source_file = .{ .path = "../../kits/zig/worker/src/worker.zig" },
- });
-
- inline for (examples) |example| {
- const exe = b.addExecutable(.{
- .name = example,
- .root_source_file = .{ .path = "src/" ++ example ++ ".zig" },
- .target = target,
- .optimize = optimize,
- });
-
- exe.wasi_exec_model = .reactor;
- exe.addModule("worker", worker_module);
-
- b.installArtifact(exe);
- }
-}
diff --git a/examples/zig-envs/src/envs.zig b/examples/zig-envs/src/envs.zig
deleted file mode 100644
index 6be4131e..00000000
--- a/examples/zig-envs/src/envs.zig
+++ /dev/null
@@ -1,25 +0,0 @@
-const std = @import("std");
-const worker = @import("worker");
-
-var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
-const allocator = arena.allocator();
-
-fn requestFn(resp: *worker.Response, r: *worker.Request) void {
- _ = r;
-
- const envvar = std.process.getEnvVarOwned(allocator, "MESSAGE") catch "";
- defer allocator.free(envvar);
-
- const s =
- \\The environment variable value is: {s}
- ;
-
- var body = std.fmt.allocPrint(allocator, s, .{ envvar }) catch undefined; // add useragent
-
- _ = &resp.headers.append("x-generated-by", "wasm-workers-server");
- _ = &resp.writeAll(body);
-}
-
-pub fn main() !void {
- worker.ServeFunc(requestFn);
-}
diff --git a/examples/zig-envs/zig-out/bin/envs.toml b/examples/zig-envs/zig-out/bin/envs.toml
deleted file mode 100644
index 4c90dfc5..00000000
--- a/examples/zig-envs/zig-out/bin/envs.toml
+++ /dev/null
@@ -1,5 +0,0 @@
-name = "envs"
-version = "1"
-
-[vars]
-MESSAGE = "Hello! This message comes from an environment variable"
\ No newline at end of file
diff --git a/examples/zig-params/README.md b/examples/zig-examples/README.md
similarity index 52%
rename from examples/zig-params/README.md
rename to examples/zig-examples/README.md
index 220ee921..05ed6b5e 100644
--- a/examples/zig-params/README.md
+++ b/examples/zig-examples/README.md
@@ -1,6 +1,6 @@
-# Params example
+# zig-examples
-Compile a Zig worker to WebAssembly and run it in Wasm Workers Server.
+This example Zig project demonstrates how to use the WWS Zig SDK to compile WWS workers from Zig source code.
## Prerequisites
@@ -10,24 +10,28 @@ Compile a Zig worker to WebAssembly and run it in Wasm Workers Server.
curl -fsSL https://workers.wasmlabs.dev/install | bash
```
-* [Zig](https://ziglang.org/download/) `0.11.0`
+* [Zig](https://ziglang.org/download/) `0.12.0`
-## Build
+# Build
All specific build confiugrations are in `build.zig` file.
-```shell-session
+```sh
zig build
```
-## Run
+# Run
-```shell-session
-wws ./zig-out/bin/
+This step assumes that you have `wws` installed on your system.
+
+```sh
+wws ./zig-out/root
```
## Resources
-* [Dynamic routes](https://workers.wasmlabs.dev/docs/features/dynamic-routes)
* [Zig documentation](https://workers.wasmlabs.dev/docs/languages/zig)
* [Announcing Zig support for Wasm Workers Server](https://wasmlabs.dev/articles/Zig-support-on-wasm-workers-server/)
+* [Environment variables](https://workers.wasmlabs.dev/docs/features/environment-variables)
+* [Key / Value store](https://workers.wasmlabs.dev/docs/features/key-value)
+* [Dynamic routes](https://workers.wasmlabs.dev/docs/features/dynamic-routes)
diff --git a/examples/zig-examples/build.zig b/examples/zig-examples/build.zig
new file mode 100644
index 00000000..fa0b53ce
--- /dev/null
+++ b/examples/zig-examples/build.zig
@@ -0,0 +1,99 @@
+const std = @import("std");
+const wws = @import("wws");
+
+const examples = &[_]Example{
+ .{
+ .name = "basic",
+ .root_source_file = .{ .path = "src/basic.zig" },
+ },
+ .{
+ .name = "envs",
+ .root_source_file = .{ .path = "src/envs.zig" },
+ .features = .{
+ .vars = &.{
+ .{ .name = "MESSAGE", .value = "Hello! This message comes from an environment variable" },
+ },
+ },
+ },
+ .{
+ .name = "workerkv",
+ .root_source_file = .{ .path = "src/worker-kv.zig" },
+ .features = .{ .kv = .{ .namespace = "workerkv" } },
+ },
+ .{
+ .name = "no-alloc-kv",
+ .root_source_file = .{ .path = "src/no-alloc-kv.zig" },
+ .features = .{ .kv = .{ .namespace = "workerkv" } },
+ },
+ .{
+ .name = "mixed-alloc-kv",
+ .root_source_file = .{ .path = "src/mixed-alloc-kv.zig" },
+ .features = .{ .kv = .{ .namespace = "workerkv" } },
+ },
+ .{
+ .name = "mount",
+ .root_source_file = .{ .path = "src/mount.zig" },
+ .features = .{
+ .folders = &.{
+ .{
+ .from = "./_images",
+ .to = "/src/images",
+ },
+ },
+ },
+ },
+ .{
+ .name = "params",
+ .root_source_file = .{ .path = "src/params.zig" },
+ .path = "params/[id]",
+ },
+ .{
+ .name = "router",
+ .root_source_file = .{ .path = "src/router.zig" },
+ .path = "router/[...path]",
+ },
+};
+
+const Example = struct {
+ name: []const u8,
+ root_source_file: std.Build.LazyPath,
+ path: ?[]const u8 = null,
+ features: ?wws.Features = null,
+};
+
+pub fn build(b: *std.Build) !void {
+ const target = wws.getTarget(b);
+ const optimize = b.standardOptimizeOption(.{});
+
+ const wws_dep = b.dependency("wws", .{});
+ const zig_router_dep = b.dependency("zig-router", .{});
+
+ const wf = b.addWriteFiles();
+
+ inline for (examples) |e| {
+ const worker = try wws.addWorker(b, .{
+ .name = e.name,
+ .path = e.path orelse e.name,
+ .root_source_file = e.root_source_file,
+ .target = target,
+ .optimize = optimize,
+ .wws = wws_dep,
+ .features = e.features orelse .{},
+ });
+
+ worker.exe.root_module.addImport("zig-router", zig_router_dep.module("zig-router"));
+
+ try worker.addToWriteFiles(b, wf);
+ }
+
+ // Add folder for mount example
+ _ = wf.addCopyFile(.{ .path = "src/_images/zig.svg" }, "_images/zig.svg");
+
+ const install = b.addInstallDirectory(.{
+ .source_dir = wf.getDirectory(),
+ .install_dir = .prefix,
+ .install_subdir = "root",
+ });
+
+ b.getInstallStep().dependOn(&install.step);
+}
diff --git a/examples/zig-examples/build.zig.zon b/examples/zig-examples/build.zig.zon
new file mode 100644
index 00000000..1945c227
--- /dev/null
+++ b/examples/zig-examples/build.zig.zon
@@ -0,0 +1,69 @@
+.{
+ .name = "zig-build",
+ // This is a [Semantic Version](https://semver.org/).
+ // In a future version of Zig it will be used for package deduplication.
+ .version = "0.0.0",
+
+ // This field is optional.
+ // This is currently advisory only; Zig does not yet do anything
+ // with this value.
+ //.minimum_zig_version = "0.11.0",
+
+ // This field is optional.
+ // Each dependency must either provide a `url` and `hash`, or a `path`.
+ // `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
+ // Once all dependencies are fetched, `zig build` no longer requires
+ // internet connectivity.
+ .dependencies = .{
+ // See `zig fetch --save ` for a command-line interface for adding dependencies.
+ //.example = .{
+ // // When updating this field to a new URL, be sure to delete the corresponding
+ // // `hash`, otherwise you are communicating that you expect to find the old hash at
+ // // the new URL.
+ // .url = "https://example.com/foo.tar.gz",
+ //
+ // // This is computed from the file contents of the directory of files that is
+ // // obtained after fetching `url` and applying the inclusion rules given by
+ // // `paths`.
+ // //
+ // // This field is the source of truth; packages do not come from a `url`; they
+ // // come from a `hash`. `url` is just one of many possible mirrors for how to
+ // // obtain a package matching this `hash`.
+ // //
+ // // Uses the [multihash](https://multiformats.io/multihash/) format.
+ // .hash = "...",
+ //
+ // // When this is provided, the package is found in a directory relative to the
+ // // build root. In this case the package's hash is irrelevant and therefore not
+ // // computed. This field and `url` are mutually exclusive.
+ // .path = "foo",
+ //},
+ .wws = .{
+ .path = "../../kits/zig/wws",
+ },
+ .@"zig-router" = .{
+ .url = "https://github.com/Cloudef/zig-router/archive/e8df16c9281969aef3cbb3c788f2c8c2b4f9ec30.tar.gz",
+ .hash = "1220fabbdaa72ff4801b62cc577e2e63849e960fbc22b67d560080726c53fb7c28cc",
+ },
+ },
+
+ // Specifies the set of files and directories that are included in this package.
+ // Only files and directories listed here are included in the `hash` that
+ // is computed for this package.
+ // Paths are relative to the build root. Use the empty string (`""`) to refer to
+ // the build root itself.
+ // A directory listed here means that all files within, recursively, are included.
+ .paths = .{
+ // This makes *all* files, recursively, included in this package. It is generally
+ // better to explicitly list the files and directories instead, to insure that
+ // fetching from tarballs, file system paths, and version control all result
+ // in the same contents hash.
+ "",
+ // For example...
+ //"build.zig",
+ //"build.zig.zon",
+ //"src",
+ //"LICENSE",
+ //"README.md",
+ },
+}
diff --git a/examples/zig-mount/zig-out/bin/_images/zig.svg b/examples/zig-examples/src/_images/zig.svg
similarity index 100%
rename from examples/zig-mount/zig-out/bin/_images/zig.svg
rename to examples/zig-examples/src/_images/zig.svg
diff --git a/examples/zig-examples/src/basic.zig b/examples/zig-examples/src/basic.zig
new file mode 100644
index 00000000..67990934
--- /dev/null
+++ b/examples/zig-examples/src/basic.zig
@@ -0,0 +1,64 @@
+const std = @import("std");
+const wws = @import("wws");
+
+const tmpl =
+ \\
+ \\
+ \\Wasm Workers Server
+ \\
+ \\
+ \\
+ \\
+ \\
+ \\
+ \\
+ \\Hello from Wasm Workers Server 👋
+ \\Replying to {s}
+ \\Method: {d}
+ \\User Agent: {s}
+ \\Payload: {s}
+ \\
+ \\This page was generated by a Zig⚡️ file running in WebAssembly.
+ \\
+ \\
+ \\
+;
+
+fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response {
+ const payload = if (request.body.len == 0) "-" else request.body;
+
+ const body = try std.fmt.allocPrint(arena, tmpl, .{ request.url, request.method, "-", payload });
+
+ var response = wws.Response{
+ .data = body,
+ };
+
+ try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server");
+
+ return response;
+}
+
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+
+ const parse_result = try wws.parseStream(gpa.allocator(), .{});
+ defer parse_result.deinit();
+
+ var arena = std.heap.ArenaAllocator.init(gpa.allocator());
+ defer arena.deinit();
+
+ const request = parse_result.value;
+ const response = try handle(arena.allocator(), request);
+
+ const stdout = std.io.getStdOut();
+ try wws.writeResponse(response, stdout.writer());
+}
diff --git a/examples/zig-examples/src/envs.zig b/examples/zig-examples/src/envs.zig
new file mode 100644
index 00000000..3905373b
--- /dev/null
+++ b/examples/zig-examples/src/envs.zig
@@ -0,0 +1,33 @@
+const std = @import("std");
+const wws = @import("wws");
+
+const tmpl =
+ \\The environment variable value is: {s}
+;
+
+fn handle(arena: std.mem.Allocator) !wws.Response {
+ const envvar = std.process.getEnvVarOwned(arena, "MESSAGE") catch "";
+
+ const body = try std.fmt.allocPrint(arena, tmpl, .{envvar});
+
+ var response = wws.Response{
+ .data = body,
+ };
+
+ try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server");
+
+ return response;
+}
+
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+
+ var arena = std.heap.ArenaAllocator.init(gpa.allocator());
+ defer arena.deinit();
+
+ const response = try handle(arena.allocator());
+
+ const stdout = std.io.getStdOut();
+ try wws.writeResponse(response, stdout.writer());
+}
diff --git a/examples/zig-examples/src/mixed-alloc-kv.zig b/examples/zig-examples/src/mixed-alloc-kv.zig
new file mode 100644
index 00000000..b8cff5c6
--- /dev/null
+++ b/examples/zig-examples/src/mixed-alloc-kv.zig
@@ -0,0 +1,66 @@
+const std = @import("std");
+const wws = @import("wws");
+
+const tmpl =
+ \\
+ \\
+ \\
+ \\Wasm Workers Server - KV example
+ \\
+ \\
+ \\
+ \\
+ \\Key / Value store in Zig
+ \\Counter: {d}
+ \\This page was generated by a Zig⚡️ file running in WebAssembly.
+ \\
+;
+
+fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response {
+ const value = request.kv.map.get("counter") orelse "0";
+ var counter = std.fmt.parseInt(i32, value, 10) catch 0;
+
+ counter += 1;
+
+ const body = try std.fmt.allocPrint(arena, tmpl, .{counter});
+
+ var response = wws.Response{
+ .data = body,
+ };
+
+ const num_s = try std.fmt.allocPrint(arena, "{d}", .{counter});
+ try response.kv.map.put(arena, "counter", num_s);
+
+ try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server");
+
+ return response;
+}
+
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+
+ var arena = std.heap.ArenaAllocator.init(gpa.allocator());
+ defer arena.deinit();
+
+ var sfa = std.heap.stackFallback(4096 * 2, arena.allocator());
+ const allocator = sfa.get();
+
+ const parse_result = wws.parseStream(allocator, .{}) catch {
+ std.debug.print("Failed to read request\n", .{});
+ return error.ReadRequestFailed;
+ };
+ defer parse_result.deinit();
+
+ const request = parse_result.value;
+ const response = handle(allocator, request) catch {
+ std.debug.print("Failed to handle request\n", .{});
+ return error.HandleRequestFailed;
+ };
+
+ const stdout = std.io.getStdOut();
+ wws.writeResponse(response, stdout.writer()) catch {
+ std.debug.print("Failed to write response\n", .{});
+ return error.WriteResponseFailed;
+ };
+}
diff --git a/examples/zig-examples/src/mount.zig b/examples/zig-examples/src/mount.zig
new file mode 100644
index 00000000..a374cec9
--- /dev/null
+++ b/examples/zig-examples/src/mount.zig
@@ -0,0 +1,34 @@
+const std = @import("std");
+const wws = @import("wws");
+
+fn handle(arena: std.mem.Allocator) !wws.Response {
+ const file = try std.fs.Dir.openFile(std.fs.cwd(), "zig.svg", .{
+ .mode = std.fs.File.OpenMode.read_only,
+ .lock = std.fs.File.Lock.none,
+ });
+ defer file.close();
+
+ const mb = (1 << 10) << 10;
+ const file_contents = try file.readToEndAlloc(arena, mb);
+
+ var response = wws.Response{
+ .data = file_contents,
+ };
+
+ try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server");
+
+ return response;
+}
+
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+
+ var arena = std.heap.ArenaAllocator.init(gpa.allocator());
+ defer arena.deinit();
+
+ const response = try handle(arena.allocator());
+
+ const stdout = std.io.getStdOut();
+ try wws.writeResponse(response, stdout.writer());
+}
diff --git a/examples/zig-examples/src/no-alloc-kv.zig b/examples/zig-examples/src/no-alloc-kv.zig
new file mode 100644
index 00000000..368e2568
--- /dev/null
+++ b/examples/zig-examples/src/no-alloc-kv.zig
@@ -0,0 +1,51 @@
+const std = @import("std");
+const wws = @import("wws");
+
+const tmpl =
+ \\
+ \\
+ \\
+ \\Wasm Workers Server - KV example
+ \\
+ \\
+ \\
+ \\
+ \\Key / Value store in Zig
+ \\Counter: {d}
+ \\This page was generated by a Zig⚡️ file running in WebAssembly.
+ \\
+;
+
+fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response {
+ const value = request.kv.map.get("counter") orelse "0";
+ var counter = std.fmt.parseInt(i32, value, 10) catch 0;
+
+ counter += 1;
+
+ const body = try std.fmt.allocPrint(arena, tmpl, .{counter});
+
+ var response = wws.Response{
+ .data = body,
+ };
+
+ const num_s = try std.fmt.allocPrint(arena, "{d}", .{counter});
+ try response.kv.map.put(arena, "counter", num_s);
+
+ try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server");
+
+ return response;
+}
+
+pub fn main() !void {
+ var buf: [4096 * 2]u8 = undefined;
+ var fba = std.heap.FixedBufferAllocator.init(&buf);
+
+ const parse_result = try wws.parseStream(fba.allocator(), .{});
+ defer parse_result.deinit();
+
+ const request = parse_result.value;
+ const response = try handle(fba.allocator(), request);
+
+ const stdout = std.io.getStdOut();
+ try wws.writeResponse(response, stdout.writer());
+}
diff --git a/examples/zig-examples/src/params.zig b/examples/zig-examples/src/params.zig
new file mode 100644
index 00000000..c6c1289e
--- /dev/null
+++ b/examples/zig-examples/src/params.zig
@@ -0,0 +1,37 @@
+const std = @import("std");
+const wws = @import("wws");
+
+const tmpl =
+ \\Hey! The parameter is: {s}
+;
+
+fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response {
+ const id = request.params.map.get("id") orelse "the value is not available";
+
+ const body = try std.fmt.allocPrint(arena, tmpl, .{id});
+
+ var response = wws.Response{
+ .data = body,
+ };
+
+ try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server");
+
+ return response;
+}
+
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+
+ const parse_result = try wws.parseStream(gpa.allocator(), .{});
+ defer parse_result.deinit();
+
+ var arena = std.heap.ArenaAllocator.init(gpa.allocator());
+ defer arena.deinit();
+
+ const request = parse_result.value;
+ const response = try handle(arena.allocator(), request);
+
+ const stdout = std.io.getStdOut();
+ try wws.writeResponse(response, stdout.writer());
+}
diff --git a/examples/zig-examples/src/router.zig b/examples/zig-examples/src/router.zig
new file mode 100644
index 00000000..72c252be
--- /dev/null
+++ b/examples/zig-examples/src/router.zig
@@ -0,0 +1,75 @@
+const std = @import("std");
+const wws = @import("wws");
+const zig_router = @import("zig-router");
+
+fn getHello(arena: std.mem.Allocator) !wws.Response {
+ var response = wws.Response{
+ .data = "Hello, world!",
+ };
+
+ try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server");
+ return response;
+}
+
+const GetBlogPostParams = struct {
+ id: []const u8,
+};
+fn getBlogPost(arena: std.mem.Allocator, params: GetBlogPostParams) !wws.Response {
+ var body = std.ArrayList(u8).init(arena);
+
+ try body.writer().print("Blog article contents for id {s}", .{params.id});
+
+ var response = wws.Response{
+ .data = try body.toOwnedSlice(),
+ };
+
+ try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server");
+ return response;
+}
+
+fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response {
+ const router = zig_router.Router(
+ .{},
+ .{
+ zig_router.Route(.GET, "/router/hello", getHello, .{}),
+ zig_router.Route(.GET, "/router/post/:id", getBlogPost, .{}),
+ },
+ );
+
+ const uri = try std.Uri.parse(request.url);
+
+ const response = router.match(
+ arena,
+ .{
+ .method = request.method,
+ .path = uri.path,
+ .query = uri.query orelse "",
+ },
+ .{arena},
+ ) catch |err| switch (err) {
+ error.not_found => wws.Response{ .status = 404, .data = "not found" },
+ error.bad_request => wws.Response{ .status = 400, .data = "bad request" },
+ else => wws.Response{ .status = 500, .data = "internal server error" },
+ };
+
+ return response;
+}
+
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+
+ const parse_result = try wws.parseStream(gpa.allocator(), .{});
+ defer parse_result.deinit();
+
+ var arena = std.heap.ArenaAllocator.init(gpa.allocator());
+ defer arena.deinit();
+
+ const request = parse_result.value;
+ const response = try handle(arena.allocator(), request);
+
+ std.debug.print("response {s}\n", .{response.data});
+
+ const stdout = std.io.getStdOut();
+ try wws.writeResponse(response, stdout.writer());
+}
diff --git a/examples/zig-examples/src/worker-kv.zig b/examples/zig-examples/src/worker-kv.zig
new file mode 100644
index 00000000..8dadba6f
--- /dev/null
+++ b/examples/zig-examples/src/worker-kv.zig
@@ -0,0 +1,54 @@
+const std = @import("std");
+const wws = @import("wws");
+
+const tmpl =
+ \\
+ \\
+ \\
+ \\Wasm Workers Server - KV example
+ \\
+ \\
+ \\
+ \\
+ \\Key / Value store in Zig
+ \\Counter: {d}
+ \\This page was generated by a Zig⚡️ file running in WebAssembly.
+ \\
+;
+
+fn handle(arena: std.mem.Allocator, request: wws.Request) !wws.Response {
+ const value = request.kv.map.get("counter") orelse "0";
+ var counter = std.fmt.parseInt(i32, value, 10) catch 0;
+
+ counter += 1;
+
+ const body = try std.fmt.allocPrint(arena, tmpl, .{counter});
+
+ var response = wws.Response{
+ .data = body,
+ };
+
+ const num_s = try std.fmt.allocPrint(arena, "{d}", .{counter});
+ try response.kv.map.put(arena, "counter", num_s);
+
+ try response.headers.map.put(arena, "x-generated-by", "wasm-workers-server");
+
+ return response;
+}
+
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+
+ const parse_result = try wws.parseStream(gpa.allocator(), .{});
+ defer parse_result.deinit();
+
+ var arena = std.heap.ArenaAllocator.init(gpa.allocator());
+ defer arena.deinit();
+
+ const request = parse_result.value;
+ const response = try handle(arena.allocator(), request);
+
+ const stdout = std.io.getStdOut();
+ try wws.writeResponse(response, stdout.writer());
+}
diff --git a/examples/zig-kv/README.md b/examples/zig-kv/README.md
deleted file mode 100644
index 2230a277..00000000
--- a/examples/zig-kv/README.md
+++ /dev/null
@@ -1,33 +0,0 @@
-# Key/Value example
-
-Compile a Zig worker to WebAssembly and run it in Wasm Workers Server.
-
-## Prerequisites
-
-* Wasm Workers Server (wws):
-
- ```shell-session
- curl -fsSL https://workers.wasmlabs.dev/install | bash
- ```
-
-* [Zig](https://ziglang.org/download/) `0.11.0`
-
-## Build
-
-All specific build confiugrations are in `build.zig` file.
-
-```shell-session
-zig build
-```
-
-## Run
-
-```shell-session
-wws ./zig-out/bin/
-```
-
-## Resources
-
-* [Key / Value store](https://workers.wasmlabs.dev/docs/features/key-value)
-* [Zig documentation](https://workers.wasmlabs.dev/docs/languages/zig)
-* [Announcing Zig support for Wasm Workers Server](https://wasmlabs.dev/articles/Zig-support-on-wasm-workers-server/)
diff --git a/examples/zig-kv/build.zig b/examples/zig-kv/build.zig
deleted file mode 100644
index 8f5b624a..00000000
--- a/examples/zig-kv/build.zig
+++ /dev/null
@@ -1,26 +0,0 @@
-const std = @import("std");
-
-const examples = [1][]const u8{ "worker-kv" };
-
-pub fn build(b: *std.Build) !void {
- const target = try std.zig.CrossTarget.parse(.{ .arch_os_abi = "wasm32-wasi" });
- const optimize = b.standardOptimizeOption(.{});
-
- const worker_module = b.createModule(.{
- .source_file = .{ .path = "../../kits/zig/worker/src/worker.zig" },
- });
-
- inline for (examples) |example| {
- const exe = b.addExecutable(.{
- .name = example,
- .root_source_file = .{ .path = "src/" ++ example ++ ".zig" },
- .target = target,
- .optimize = optimize,
- });
-
- exe.wasi_exec_model = .reactor;
- exe.addModule("worker", worker_module);
-
- b.installArtifact(exe);
- }
-}
diff --git a/examples/zig-kv/src/worker-kv.zig b/examples/zig-kv/src/worker-kv.zig
deleted file mode 100644
index 3f1bcd78..00000000
--- a/examples/zig-kv/src/worker-kv.zig
+++ /dev/null
@@ -1,46 +0,0 @@
-const std = @import("std");
-const worker = @import("worker");
-
-var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
-const allocator = arena.allocator();
-
-fn requestFn(resp: *worker.Response, r: *worker.Request) void {
- var cache = r.context.cache;
- var counter: i32 = 0;
-
- var v = cache.getOrPut("counter") catch undefined;
-
- if (!v.found_existing) {
- v.value_ptr.* = "0";
- } else {
- var counterValue = v.value_ptr.*;
- var num = std.fmt.parseInt(i32, counterValue, 10) catch undefined;
- counter = num + 1;
- var num_s = std.fmt.allocPrint(allocator, "{d}", .{ counter }) catch undefined;
- _ = cache.put("counter", num_s) catch undefined;
- }
-
- const s =
- \\
- \\
- \\
- \\Wasm Workers Server - KV example
- \\
- \\
- \\
- \\
- \\Key / Value store in Zig
- \\Counter: {d}
- \\This page was generated by a Zig⚡️ file running in WebAssembly.
- \\
- ;
-
- var body = std.fmt.allocPrint(allocator, s, .{ counter }) catch undefined; // add useragent
-
- _ = &resp.headers.append("x-generated-by", "wasm-workers-server");
- _ = &resp.writeAll(body);
-}
-
-pub fn main() !void {
- worker.ServeFunc(requestFn);
-}
diff --git a/examples/zig-kv/zig-out/bin/worker-kv.toml b/examples/zig-kv/zig-out/bin/worker-kv.toml
deleted file mode 100644
index d1987525..00000000
--- a/examples/zig-kv/zig-out/bin/worker-kv.toml
+++ /dev/null
@@ -1,6 +0,0 @@
-name = "workerkv"
-version = "1"
-
-[data]
-[data.kv]
-namespace = "workerkv"
\ No newline at end of file
diff --git a/examples/zig-mount/build.zig b/examples/zig-mount/build.zig
deleted file mode 100644
index 351f3858..00000000
--- a/examples/zig-mount/build.zig
+++ /dev/null
@@ -1,26 +0,0 @@
-const std = @import("std");
-
-const examples = [1][]const u8{ "mount" };
-
-pub fn build(b: *std.Build) !void {
- const target = try std.zig.CrossTarget.parse(.{ .arch_os_abi = "wasm32-wasi" });
- const optimize = b.standardOptimizeOption(.{});
-
- const worker_module = b.createModule(.{
- .source_file = .{ .path = "../../kits/zig/worker/src/worker.zig" },
- });
-
- inline for (examples) |example| {
- const exe = b.addExecutable(.{
- .name = example,
- .root_source_file = .{ .path = "src/" ++ example ++ ".zig" },
- .target = target,
- .optimize = optimize,
- });
-
- exe.wasi_exec_model = .reactor;
- exe.addModule("worker", worker_module);
-
- b.installArtifact(exe);
- }
-}
diff --git a/examples/zig-mount/src/mount.zig b/examples/zig-mount/src/mount.zig
deleted file mode 100644
index 72a64962..00000000
--- a/examples/zig-mount/src/mount.zig
+++ /dev/null
@@ -1,26 +0,0 @@
-const std = @import("std");
-const worker = @import("worker");
-
-var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
-const allocator = arena.allocator();
-
-fn requestFn(resp: *worker.Response, r: *worker.Request) void {
- _ = r;
-
- const file = std.fs.Dir.openFileWasi(
- std.fs.cwd(), "zig.svg", .{
- .mode = std.fs.File.OpenMode.read_only,
- .lock = std.fs.File.Lock.none,
- }) catch unreachable;
- defer file.close();
-
- const mb = (1 << 10) << 10;
- const file_contents = file.readToEndAlloc(allocator, mb) catch "";
-
- _ = &resp.headers.append("x-generated-by", "wasm-workers-server");
- _ = &resp.writeAll(file_contents);
-}
-
-pub fn main() !void {
- worker.ServeFunc(requestFn);
-}
diff --git a/examples/zig-mount/zig-out/bin/mount.toml b/examples/zig-mount/zig-out/bin/mount.toml
deleted file mode 100644
index 02a5493c..00000000
--- a/examples/zig-mount/zig-out/bin/mount.toml
+++ /dev/null
@@ -1,6 +0,0 @@
-name = "envs"
-version = "1"
-
-[[folders]]
-from = "./_images"
-to = "/src/images"
\ No newline at end of file
diff --git a/examples/zig-params/build.zig b/examples/zig-params/build.zig
deleted file mode 100644
index 657dc469..00000000
--- a/examples/zig-params/build.zig
+++ /dev/null
@@ -1,26 +0,0 @@
-const std = @import("std");
-
-const examples = [1][]const u8{ "worker-params" };
-
-pub fn build(b: *std.Build) !void {
- const target = try std.zig.CrossTarget.parse(.{ .arch_os_abi = "wasm32-wasi" });
- const optimize = b.standardOptimizeOption(.{});
-
- const worker_module = b.createModule(.{
- .source_file = .{ .path = "../../kits/zig/worker/src/worker.zig" },
- });
-
- inline for (examples) |example| {
- const exe = b.addExecutable(.{
- .name = example,
- .root_source_file = .{ .path = "src/" ++ example ++ ".zig" },
- .target = target,
- .optimize = optimize,
- });
-
- exe.wasi_exec_model = .reactor;
- exe.addModule("worker", worker_module);
-
- b.installArtifact(exe);
- }
-}
diff --git a/examples/zig-params/src/worker-params.zig b/examples/zig-params/src/worker-params.zig
deleted file mode 100644
index a571d28b..00000000
--- a/examples/zig-params/src/worker-params.zig
+++ /dev/null
@@ -1,30 +0,0 @@
-const std = @import("std");
-const worker = @import("worker");
-
-var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
-const allocator = arena.allocator();
-
-fn requestFn(resp: *worker.Response, r: *worker.Request) void {
- var params = r.context.params;
-
- var id: []const u8 = "the value is not available";
-
- var v = params.get("id");
-
- if (v) |val| {
- id = val;
- }
-
- const s =
- \\Hey! The parameter is: {s}
- ;
-
- var body = std.fmt.allocPrint(allocator, s, .{ id }) catch undefined; // add useragent
-
- _ = &resp.headers.append("x-generated-by", "wasm-workers-server");
- _ = &resp.writeAll(body);
-}
-
-pub fn main() !void {
- worker.ServeFunc(requestFn);
-}
diff --git a/kits/zig/worker/README.md b/kits/zig/worker/README.md
deleted file mode 100644
index 403fd470..00000000
--- a/kits/zig/worker/README.md
+++ /dev/null
@@ -1,49 +0,0 @@
-# Zig kit
-
-This folder contains the Zig kit or SDK for Wasm Workers Server. Currently, it uses the regular STDIN / STDOUT approach to receive the request and provide the response.
-
-> *Note: this assumes Zig `0.11.0`*
-
-## Build
-
-To build all examples in ./examples
-
-```shell-session
-$ zig build -Dtarget="wasm32-wasi"
-```
-
-To build a specific example:
-
-```shell-session
-$ zig build-exe examples/.zig -target wasm32-wasi
-```
-
-## Testing
-
-At `./kits/zig/worker` execute:
-
-```shell-session
-$ zig build -Dtarget="wasm32-wasi"
-$ wws ./zig-out/bin/
-```
-
-## sockaddr issue
-
-Using `http.Server.Response` was unsuccessful and lead to following error:
-
-```
-$ worker git:(144_-_add_support_for_zig) ✗ zig build -Dtarget="wasm32-wasi"
-zig build-exe main Debug wasm32-wasi: error: the following command failed with 1 compilation errors:
-/Users/c.voigt/.asdf/installs/zig/0.11.0/zig build-exe /Users/c.voigt/go/src/github.com/voigt/wasm-workers-server/kits/zig/worker/examples/main.zig --cache-dir /Users/c.voigt/go/src/github.com/voigt/wasm-workers-server/kits/zig/worker/zig-cache --global-cache-dir /Users/c.voigt/.cache/zig --name main -target wasm32-wasi -mcpu generic --mod worker::/Users/c.voigt/go/src/github.com/voigt/wasm-workers-server/kits/zig/worker/src/worker.zig --deps worker --listen=-
-Build Summary: 6/9 steps succeeded; 1 failed (disable with --summary none)
-install transitive failure
-└─ install main transitive failure
- └─ zig build-exe main Debug wasm32-wasi 1 errors
-/Users/c.voigt/.asdf/installs/zig/0.11.0/lib/std/os.zig:182:28: error: root struct of file 'os.wasi' has no member named 'sockaddr'
-pub const sockaddr = system.sockaddr;
- ~~~~~~^~~~~~~~~
-referenced by:
- Address: /Users/c.voigt/.asdf/installs/zig/0.11.0/lib/std/net.zig:18:12
- Address: /Users/c.voigt/.asdf/installs/zig/0.11.0/lib/std/net.zig:17:28
- remaining reference traces hidden; use '-freference-trace' to see all reference traces
-```
\ No newline at end of file
diff --git a/kits/zig/worker/src/worker.zig b/kits/zig/worker/src/worker.zig
deleted file mode 100644
index 0bfbfa1c..00000000
--- a/kits/zig/worker/src/worker.zig
+++ /dev/null
@@ -1,266 +0,0 @@
-const std = @import("std");
-const io = std.io;
-const http = std.http;
-
-var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
-const allocator = arena.allocator();
-
-pub var cache = std.StringHashMap([]const u8).init(allocator);
-pub var params = std.StringHashMap([]const u8).init(allocator);
-
-pub const Request = struct {
- url: std.Uri,
- method: []const u8, // TODO: change to http.Method enum
- headers: http.Headers,
- body: []const u8,
- context: Context,
-};
-
-pub const Response = struct {
- body: []const u8,
- base64: bool,
- headers: http.Headers,
- request: Request,
-
- pub fn writeAll(res: *Response, data: []const u8) !u32 {
- res.body = data;
- return res.body.len;
- }
-
- pub fn writeAllBase64(res: *Response, data: []const u8) !u32 {
- res.body = base64Encode(data);
- res.base64 = true;
- return res.body.len;
- }
-};
-
-// Note: as Zig does not support multiple return types, we use this struct
-// to wrap both the request and the output to keep code a bit more clean
-const RequestAndOutput = struct {
- request: Request,
- output: Output,
-};
-
-pub const Input = struct {
- url: []const u8,
- method: []const u8,
- headers: std.StringArrayHashMap([]const u8),
- body: []const u8,
-};
-
-pub const Output = struct {
- data: []const u8,
- headers: std.StringArrayHashMap([]const u8),
- status: u16,
- base64: bool,
-
- httpHeader: http.Headers,
-
- const Self = @This();
-
- pub fn init() Self {
- return .{
- .data = "",
- .headers = std.StringArrayHashMap([]const u8).init(allocator),
- .status = 0,
- .base64 = false,
- .httpHeader = http.Headers.init(allocator),
- };
- }
-
- pub fn header(self: *Self) http.Headers {
- if (self.httpHeader == undefined) {
- self.httpHeader = http.Headers.init(allocator);
- }
-
- return self.httpHeader;
- }
-
- pub fn setStatus(self: *Self, statusCode: u16) void {
- self.status = statusCode;
- }
-
- pub fn write(self: *Self, response: Response) !u32 {
- self.base64 = response.base64;
- if (response.base64) {
- self.data = base64Encode(response.body);
- } else {
- self.data = response.body;
- }
-
- if (self.status == 0) {
- self.setStatus(200);
- }
-
- for (self.httpHeader.list.items) |item| {
- try self.headers.put(item.name, item.value);
- }
-
- // prepare writer for json
- var out_buf: [4096]u8 = undefined;
- var slice_stream = std.io.fixedBufferStream(&out_buf);
- const out = slice_stream.writer();
- var w = std.json.writeStream(out, .{ .whitespace = .minified });
-
- slice_stream.reset();
- try w.beginObject();
-
- try w.objectField("data");
- try w.write(self.data);
-
- try w.objectField("status");
- try w.write(self.status);
-
- try w.objectField("base64");
- try w.write(self.base64);
-
- try w.objectField("headers");
- try w.write(try getHeadersJsonObject(self.headers));
-
- try w.objectField("kv");
- try w.write(try getCacheJsonObject(cache));
-
- try w.endObject();
- const result = slice_stream.getWritten();
-
- const stdout = std.io.getStdOut().writer();
- try stdout.print("{s}", .{ result });
-
- return self.data.len;
- }
-};
-
-fn base64Encode(data: []const u8) []const u8 {
- // This initializing Base64Encoder throws weird error if not wrapped in function (maybe Zig bug?)
- var enc = std.base64.Base64Encoder.init(std.base64.standard_alphabet_chars, '=');
- var data_len = enc.calcSize(data.len);
- var buf: [16384]u8 = undefined;
- return enc.encode(buf[0..data_len], data);
-}
-
-fn getHeadersJsonObject(s: std.StringArrayHashMap([]const u8)) !std.json.Value {
- var value = std.json.Value{ .object = std.json.ObjectMap.init(allocator) };
-
- var i = s.iterator();
- while (i.next()) |kv| {
- try value.object.put(kv.key_ptr.*, std.json.Value{ .string = kv.value_ptr.*});
- }
-
- return value;
-}
-
-fn getCacheJsonObject(s: std.StringHashMap([]const u8)) !std.json.Value {
- var value = std.json.Value{ .object = std.json.ObjectMap.init(allocator) };
-
- var i = s.iterator();
- while (i.next()) |entry| {
- try value.object.put(entry.key_ptr.*, std.json.Value{ .string = entry.value_ptr.*});
- }
-
- return value;
-}
-
-pub fn readInput() !Input {
- const in = std.io.getStdIn();
- var buf = std.io.bufferedReader(in.reader());
- var r = buf.reader();
-
- var msg = try r.readAllAlloc(allocator, std.math.maxInt(u32));
- return getInput(msg);
-}
-
-fn getInput(s: []const u8) !Input {
- var parsed = try std.json.parseFromSlice(std.json.Value, allocator, s, .{});
- defer parsed.deinit();
-
- var input = Input{
- .url = parsed.value.object.get("url").?.string,
- .method = parsed.value.object.get("method").?.string,
- .body = parsed.value.object.get("body").?.string,
- .headers = std.StringArrayHashMap([]const u8).init(allocator),
- };
-
- var headers_map = parsed.value.object.get("headers").?.object;
- var headersIterator = headers_map.iterator();
- while (headersIterator.next()) |entry| {
- try input.headers.put(entry.key_ptr.*, entry.value_ptr.*.string);
- }
-
- var kv = parsed.value.object.get("kv").?.object;
- var kvIterator = kv.iterator();
- while (kvIterator.next()) |entry| {
- try cache.put(entry.key_ptr.*, entry.value_ptr.*.string);
- }
-
- var p = parsed.value.object.get("params").?.object;
- var paramsIterator = p.iterator();
- while (paramsIterator.next()) |entry| {
- try params.put(entry.key_ptr.*, entry.value_ptr.*.string);
- }
-
- return input;
-}
-
-pub fn createRequest(in: *Input) !Request {
- var req = Request{
- .url = try std.Uri.parseWithoutScheme(in.url),
- .method = in.method,
- .headers = http.Headers.init(allocator),
- .body = in.body,
- .context = Context.init(),
- };
-
- var i = in.headers.iterator();
- while (i.next()) |kv| {
- try req.headers.append(kv.key_ptr.*, kv.value_ptr.*);
- }
-
- return req;
-}
-
-pub fn getWriterRequest() !RequestAndOutput {
- var in = readInput() catch |err| {
- std.debug.print("error reading input: {!}\n", .{err});
- return std.os.exit(1);
- };
-
- var req = createRequest(&in) catch |err| {
- std.debug.print("error creating request : {!}\n", .{err});
- return std.os.exit(1);
- };
-
- var output = Output.init();
-
- return RequestAndOutput{
- .request = req,
- .output = output,
- };
-}
-
-pub const Context = struct {
- cache: *std.StringHashMap([]const u8),
- params: *std.StringHashMap([]const u8),
-
- pub fn init() Context {
- return .{
- .cache = &cache,
- .params = ¶ms,
- };
- }
-};
-
-pub fn ServeFunc(requestFn: *const fn (*Response, *Request) void) void {
- var r = try getWriterRequest();
- var request = r.request;
- var output = r.output;
-
- var response = Response{ .body = "", .base64 = false, .headers = http.Headers.init(allocator), .request = request, };
-
- requestFn(&response, &request);
-
- output.httpHeader = response.headers;
-
- _ = output.write(response) catch |err| {
- std.debug.print("error writing data: {!} \n", .{ err });
- };
-}
diff --git a/kits/zig/wws/README.md b/kits/zig/wws/README.md
new file mode 100644
index 00000000..365dba5b
--- /dev/null
+++ b/kits/zig/wws/README.md
@@ -0,0 +1,119 @@
+# Zig kit
+
+This folder contains the Zig kit or SDK for Wasm Workers Server. Currently, it uses the regular STDIN / STDOUT approach to receive the request and provide the response.
+
+> *Note: Last tested with Zig `0.12.0-dev.2208+4debd4338`*
+
+## Usage
+
+Add wws as a dependency to your Zig project:
+
+```shell-session
+$ zig fetch --save
+```
+
+Include it in your build.zig:
+
+```zig
+const std = @import("std");
+const wws = @import("wws");
+
+pub fn build(b: *std.Build) !void {
+ const target = wws.getTarget(b);
+ const optimize = b.standardOptimizeOption(.{});
+
+ const wws_dep = b.dependency("wws", .{});
+
+ const exe = b.addExecutable(.{
+ .name = "example",
+ .root_source_file = .{ .path = "src/main.zig" },
+ .target = target,
+ .optimize = optimize,
+ });
+ exe.wasi_exec_model = .reactor;
+ exe.root_module.addImport("wws", wws_dep.module("wws"));
+
+ b.installArtifact(exe);
+
+ const config =
+ \\name = "example"
+ \\version = "1"
+ \\[data]
+ \\[data.kv]
+ \\namespace = "example"
+ ;
+ const wf = b.addWriteFiles();
+ const config_path = wf.add("example.toml", config);
+
+ const install_config = b.addInstallBinFile(config_path, "example.toml");
+
+ b.getInstallStep().dependOn(&install_config.step);
+}
+```
+
+Read request from stdin and write response to stdout:
+
+```zig
+const std=@import("std");
+const wws=@import("wws");
+
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer {
+ switch (gpa.deinit()) {
+ .ok => {},
+ .leak => {
+ // Handle memory leaks however you want (e.g. error logging).
+ // Note that if your main function returns an error,
+ // wws will not return the response you've crafted.
+ },
+ }
+ }
+ const allocator = gpa.allocator();
+
+ // Read request from stdin
+ const parse_result = try wws.parseStream(allocator, .{});
+ defer parse_result.deinit();
+
+ const request = parse_result.value;
+
+ // Simple echo response
+ const response = wws.Response{
+ .data = request.url,
+ };
+
+ const stdout = std.io.getStdOut();
+ try wws.writeResponse(response, stdout.writer());
+}
+```
+
+## Build
+
+To build all examples in ./examples/zig-examples
+
+```shell-session
+$ zig build all
+```
+
+To build a specific example, for example kv:
+
+```shell-session
+$ zig build kv
+```
+
+## Run
+
+This assumes you have `wws` available in your PATH:
+
+```shell-session
+$ zig build run
+```
+
+## Testing
+
+At the Zig SDK path, execute:
+
+```shell-session
+$ zig build test
+```
+
diff --git a/kits/zig/wws/build.zig b/kits/zig/wws/build.zig
new file mode 100644
index 00000000..92deec51
--- /dev/null
+++ b/kits/zig/wws/build.zig
@@ -0,0 +1,163 @@
+const std = @import("std");
+
+const module_name = "wws";
+
+pub fn build(b: *std.Build) void {
+ _ = b.standardTargetOptions(.{});
+ _ = b.standardOptimizeOption(.{});
+
+ const module = b.addModule(module_name, .{
+ .root_source_file = .{ .path = "src/wws.zig" },
+ .target = getTarget(b),
+ });
+
+ _ = module;
+}
+
+// Returns the wasm32-wasi target
+pub inline fn getTarget(b: *std.Build) std.Build.ResolvedTarget {
+ return b.resolveTargetQuery(.{
+ .cpu_arch = .wasm32,
+ .os_tag = .wasi,
+ });
+}
+
+pub const EnvVar = struct {
+ name: []const u8,
+ value: []const u8,
+};
+
+pub const WwsKv = struct {
+ namespace: []const u8,
+};
+
+pub const Mount = struct {
+ from: []const u8,
+ to: []const u8,
+};
+
+pub const Features = struct {
+ vars: ?[]const EnvVar = null,
+ kv: ?WwsKv = null,
+ folders: ?[]const Mount = null,
+};
+
+pub const WwsWorker = struct {
+ exe: *std.Build.Step.Compile,
+ options: WorkerOptions,
+
+ pub fn addToWriteFiles(self: WwsWorker, b: *std.Build, wf: *std.Build.Step.WriteFile) !void {
+ _ = wf.addCopyFile(self.exe.getEmittedBin(), b.fmt("{s}.wasm", .{self.options.path}));
+
+ const config = try self.formatConfig(b.allocator);
+ defer b.allocator.free(config);
+ _ = wf.add(b.fmt("{s}.toml", .{self.options.path}), config);
+ }
+
+ // Very naively write the worker config to a string. Environment variable values
+ // and KV namespace name are automatically quoted.
+ pub fn formatConfig(self: WwsWorker, allocator: std.mem.Allocator) ![]u8 {
+ var buf = std.ArrayList(u8).init(allocator);
+ errdefer buf.deinit();
+
+ try buf.writer().print(
+ \\name = "{s}"
+ \\version = "1"
+ \\
+ ,
+ .{
+ self.options.name,
+ },
+ );
+
+ if (self.options.features.vars) |vars| {
+ try buf.writer().print(
+ \\[vars]
+ \\
+ ,
+ .{},
+ );
+ for (vars) |v| {
+ try buf.writer().print(
+ \\{s} = "{s}"
+ \\
+ ,
+ .{ v.name, v.value },
+ );
+ }
+ }
+
+ if (self.options.features.kv) |kv| {
+ try buf.writer().print(
+ \\[data]
+ \\[data.kv]
+ \\namespace = "{s}"
+ \\
+ ,
+ .{
+ kv.namespace,
+ },
+ );
+ }
+
+ if (self.options.features.folders) |folders| {
+ try buf.writer().print(
+ \\[[folders]]
+ \\
+ ,
+ .{},
+ );
+
+ for (folders) |f| {
+ try buf.writer().print(
+ \\from = "{s}"
+ \\to = "{s}"
+ \\
+ ,
+ .{
+ f.from,
+ f.to,
+ },
+ );
+ }
+ }
+
+ return try buf.toOwnedSlice();
+ }
+};
+
+pub const WorkerOptions = struct {
+ // The name of the module, written to the worker config
+ name: []const u8,
+ root_source_file: std.Build.LazyPath,
+ // The relative path of the module and its worker config.
+ // E.g. `.{ .path = "api/[...params]" }` will write to
+ // `api/[...params].wasm` and `api/[...params].toml`.
+ path: []const u8,
+ // The worker features/config including KV namespace,
+ // environment variables, and more. These are written
+ // to the worker config.
+ features: Features = .{},
+ // You must provide the wws dependency to ensure the
+ // wws module is linked to your project.
+ wws: *std.Build.Dependency,
+ target: std.Build.ResolvedTarget,
+ optimize: std.builtin.OptimizeMode,
+};
+
+pub fn addWorker(b: *std.Build, options: WorkerOptions) !WwsWorker {
+ const exe = b.addExecutable(.{
+ .name = options.name,
+ .root_source_file = options.root_source_file,
+ .target = options.target,
+ .optimize = options.optimize,
+ });
+
+ exe.wasi_exec_model = .reactor;
+ exe.root_module.addImport("wws", options.wws.module(module_name));
+
+ return .{
+ .exe = exe,
+ .options = options,
+ };
+}
diff --git a/kits/zig/wws/build.zig.zon b/kits/zig/wws/build.zig.zon
new file mode 100644
index 00000000..12078d0f
--- /dev/null
+++ b/kits/zig/wws/build.zig.zon
@@ -0,0 +1,62 @@
+.{
+ .name = "wws",
+ // This is a [Semantic Version](https://semver.org/).
+ // In a future version of Zig it will be used for package deduplication.
+ .version = "0.0.0",
+
+ // This field is optional.
+ // This is currently advisory only; Zig does not yet do anything
+ // with this value.
+ //.minimum_zig_version = "0.11.0",
+
+ // This field is optional.
+ // Each dependency must either provide a `url` and `hash`, or a `path`.
+ // `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
+ // Once all dependencies are fetched, `zig build` no longer requires
+ // internet connectivity.
+ .dependencies = .{
+ // See `zig fetch --save ` for a command-line interface for adding dependencies.
+ //.example = .{
+ // // When updating this field to a new URL, be sure to delete the corresponding
+ // // `hash`, otherwise you are communicating that you expect to find the old hash at
+ // // the new URL.
+ // .url = "https://example.com/foo.tar.gz",
+ //
+ // // This is computed from the file contents of the directory of files that is
+ // // obtained after fetching `url` and applying the inclusion rules given by
+ // // `paths`.
+ // //
+ // // This field is the source of truth; packages do not come from a `url`; they
+ // // come from a `hash`. `url` is just one of many possible mirrors for how to
+ // // obtain a package matching this `hash`.
+ // //
+ // // Uses the [multihash](https://multiformats.io/multihash/) format.
+ // .hash = "...",
+ //
+ // // When this is provided, the package is found in a directory relative to the
+ // // build root. In this case the package's hash is irrelevant and therefore not
+ // // computed. This field and `url` are mutually exclusive.
+ // .path = "foo",
+ //},
+ },
+
+ // Specifies the set of files and directories that are included in this package.
+ // Only files and directories listed here are included in the `hash` that
+ // is computed for this package.
+ // Paths are relative to the build root. Use the empty string (`""`) to refer to
+ // the build root itself.
+ // A directory listed here means that all files within, recursively, are included.
+ .paths = .{
+ // This makes *all* files, recursively, included in this package. It is generally
+ // better to explicitly list the files and directories instead, to insure that
+ // fetching from tarballs, file system paths, and version control all result
+ // in the same contents hash.
+ "",
+ // For example...
+ //"build.zig",
+ //"build.zig.zon",
+ //"src",
+ //"LICENSE",
+ //"README.md",
+ },
+}
diff --git a/kits/zig/wws/src/wws.zig b/kits/zig/wws/src/wws.zig
new file mode 100644
index 00000000..5ade8ce3
--- /dev/null
+++ b/kits/zig/wws/src/wws.zig
@@ -0,0 +1,64 @@
+const std = @import("std");
+
+pub const Method = std.http.Method;
+
+pub const Request = struct {
+ url: []const u8,
+ method: Method,
+ body: []const u8,
+ headers: std.json.ArrayHashMap([]const u8),
+ kv: std.json.ArrayHashMap([]const u8),
+ params: std.json.ArrayHashMap([]const u8),
+};
+
+pub const Response = struct {
+ headers: std.json.ArrayHashMap([]const u8) = .{},
+ data: []const u8 = "",
+ // TODO Use std.http.Status when Response has its own
+ // json serialization helper functions
+ status: usize = 200,
+ kv: std.json.ArrayHashMap([]const u8) = .{},
+};
+
+const InputStreamType = enum {
+ stdin,
+ bytes,
+};
+
+const InputStream = union(InputStreamType) {
+ stdin: void,
+ bytes: []const u8,
+};
+
+const ParseStreamError = error{Unknown};
+
+const ParseStreamOptions = struct {
+ input_stream: InputStream = .stdin,
+};
+
+/// Caller must call deinit on the returned result to free the associated memory
+pub inline fn parseStream(allocator: std.mem.Allocator, options: ParseStreamOptions) ParseStreamError!std.json.Parsed(Request) {
+ var input = std.ArrayList(u8).init(allocator);
+ defer input.deinit();
+
+ switch (options.input_stream) {
+ .stdin => {
+ const stdin = std.io.getStdIn().reader();
+ stdin.readAllArrayList(&input, std.math.maxInt(usize)) catch return ParseStreamError.Unknown;
+ },
+ .bytes => |s| {
+ input.appendSlice(s) catch return ParseStreamError.Unknown;
+ },
+ }
+
+ return std.json.parseFromSlice(
+ Request,
+ allocator,
+ input.items,
+ .{ .allocate = .alloc_always },
+ ) catch ParseStreamError.Unknown;
+}
+
+pub inline fn writeResponse(response: Response, writer: anytype) !void {
+ try std.json.stringify(response, .{}, writer);
+}