diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 0000000..f430fe3
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,20 @@
+name: Build and Deploy
+on: [push]
+permissions:
+ contents: write
+jobs:
+ build-and-deploy:
+ concurrency: ci-${{ github.ref }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout 🛎️
+ uses: actions/checkout@v4
+ - name: Install and Build 🔧
+ run: |
+ cd docs
+ npm install
+ npm run build
+ - name: Deploy 🚀
+ uses: JamesIves/github-pages-deploy-action@v4
+ with:
+ folder: docs/dist
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 0000000..2a6deef
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,9 @@
+.DS_Store
+dist
+node_modules
+_lib
+tsconfig.tsbuildinfo
+tsconfig.*.tsbuildinfo
+vocs.config.ts.timestamp-*
+.vercel
+.vocs
diff --git a/docs/package.json b/docs/package.json
new file mode 100644
index 0000000..ae7aa80
--- /dev/null
+++ b/docs/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "zig-aio",
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vocs dev",
+ "build": "vocs build",
+ "preview": "vocs preview"
+ },
+ "dependencies": {
+ "@types/react": "latest",
+ "react": "latest",
+ "react-dom": "latest",
+ "typescript": "latest",
+ "vocs": "latest"
+ }
+}
diff --git a/docs/pages/aio-dynamic.mdx b/docs/pages/aio-dynamic.mdx
new file mode 100644
index 0000000..365d0a6
--- /dev/null
+++ b/docs/pages/aio-dynamic.mdx
@@ -0,0 +1,49 @@
+# AIO API
+
+## Dynamic IO
+
+In case the amount of IO operations isn't known ahead of time the dynamic api can be used.
+
+### Initializing Dynamic instance
+
+Creating a Dynamic instance requires an allocator and upper bound for non-completed operations.
+The instance allocates only during the `init`, and frees the memory during `deinit`.
+Same allocator must be used in `deinit` that was used in `init`.
+
+```zig
+const max_operations = 32;
+var work = try Dynamic.init(std.heap.page_allocator, max_operations);
+defer work.deinit(std.heap.page_allocator);
+```
+
+### Queuing operations
+
+It is possible to queue either single or multiple operations just like with the immediate api.
+The call to queue is atomic, if the call fails, none of the operations will be actually performed.
+
+```zig
+// Multiple operations
+try work.queue(.{
+ aio.Read{...},
+ aio.Write{...},
+ aio.Fsync{...},
+});
+
+// Single operation
+try work.queue(aio.Timeout{...});
+```
+
+### Completing operations
+
+It is possible to complete the operations either in blocking or non-blocking fashion.
+The blocking mode will wait for at least one operation to complete.
+The non-blocking always returns immediately even if no operations were completed.
+The call to complete returns `aio.CompletionResult` containing the number of operations that were completed
+and the number of errors that occured.
+
+```zig
+// blocks until at least 1 operation completes
+const res = try work.complete(.blocking);
+// returns immediately
+const res = try work.complete(.nonblocking);
+```
diff --git a/docs/pages/aio-immediate.mdx b/docs/pages/aio-immediate.mdx
new file mode 100644
index 0000000..b5ac3d8
--- /dev/null
+++ b/docs/pages/aio-immediate.mdx
@@ -0,0 +1,62 @@
+# AIO API
+
+## Immediate IO
+
+For immediate blocking IO, `zig-aio` provides the following functions in the `aio` module.
+
+### Perform a single operation
+
+Completes a single operation, the call blocks until it's complete.
+Returns error of the operation if the operation failed.
+Returns `void` if there was no error.
+
+```zig
+try aio.single(aio.Write{.file = f, .buffer = "contents"});
+```
+
+### Perform multiple operations
+
+`zig-aio` provides two methods for batching IO operations.
+
+#### Using multi
+
+Completes a list of operations immediately, blocks until complete
+Returns `error.SomeOperationFailed` if any operation failed
+Returns `void` if there were no errors.
+
+```zig
+var my_buffer: [1024]u8 = undefined;
+var my_len: usize = undefined;
+
+try aio.multi(.{
+ aio.Write{.file = f, .buffer = "contents", .link_next = true},
+ aio.Read{.file = f, .buffer = &my_buffer, .out_read = &my_len},
+});
+```
+
+The `.link_next` field of operation can be used to link the operation to the next operation.
+When linking operations, the next operation won't start until the previous operation is complete.
+
+#### Using batch
+
+Batch is similar to multi, but it will not return `error.SomeOperationFailed` in case any of the operations fail.
+Instead batch returns `aio.CompletionResult` which contains the number of operations that was completed, and number of
+errors that occured. To find out which operations failed, errors have to be stored somewhere by setting the `.out_error`
+field of the operation. The batch call may still fail in implementation defined ways, such as running out of system resources.
+
+```zig
+var my_buffer: [1024]u8 = undefined;
+var my_len: usize = undefined;
+var write_error: std.posix.WriteError = undefined;
+var read_error: std.posix.ReadError = undefined;
+
+const res = try aio.batch(.{
+ aio.Write{.file = f, .buffer = "contents", .out_error = &write_error, .link_next = true},
+ aio.Read{.file = f, .buffer = &my_buffer, .out_error = &read_error, .out_read = &my_len},
+});
+
+if (res.num_errors > 0) {
+ if (write_error != error.Success) @panic("write failed");
+ if (read_error != error.Success) @panic("read failed");
+}
+```
diff --git a/docs/pages/aio-operations.mdx b/docs/pages/aio-operations.mdx
new file mode 100644
index 0000000..da9ff11
--- /dev/null
+++ b/docs/pages/aio-operations.mdx
@@ -0,0 +1,262 @@
+# AIO API
+
+## Operations
+
+A handful of IO operations are supported.
+
+### Common fields
+
+Every operation supports these common fields.
+
+```zig
+const Counter = union(enum) {
+ inc: *u16,
+ dec: *u16,
+ nop: void,
+};
+
+out_id: ?*Id = null,
+out_error: ?*(E || SharedError) = null,
+counter: Counter = .nop,
+link_next: bool = false,
+```
+
+If `out_id` is set, the id of the operation will be written into that address.
+The `id` can then be used in future operations to refer to this operation.
+If `out_error` is set, the error of the operation will be written into that address, in case the operation failed.
+If there was no failure a `error.Success` will be store in that address.
+`counter` can be used to set either decreasing or increasing counter.
+When operation completes it will either decrease or increase the `u16` stored at the address.
+`link_next` can be used to link the next operation into this operation.
+When operations are linked, the next operation won't start until this operation has completed first.
+
+### Fsync
+
+Synchronizes the contents of a `file` onto the disk.
+
+```zig
+pub const Fsync = Define(struct {
+ file: std.fs.File,
+}, std.fs.File.SyncError);
+```
+
+### Read
+
+Reads a `file` into a `buffer` from a `offset`.
+The amount of bytes read is stored in the location pointed by `out_read`.
+
+```zig
+pub const Read = Define(struct {
+ file: std.fs.File,
+ buffer: []u8,
+ offset: u64 = 0,
+ out_read: *usize,
+}, std.fs.File.ReadError);
+```
+
+### Write
+
+Writes contents of `buffer` from `offset` into a `file`.
+The amount of bytes written is stored in the location pointed by `out_written`.
+
+```zig
+pub const Write = Define(struct {
+ file: std.fs.File,
+ buffer: []const u8,
+ offset: u64 = 0,
+ out_written: ?*usize = null,
+}, std.fs.File.WriteError);
+```
+
+### Accept
+
+See `man accept(2)`
+
+```zig
+pub const Accept = Define(struct {
+ socket: std.posix.socket_t,
+ addr: ?*sockaddr = null,
+ inout_addrlen: ?*std.posix.socklen_t = null,
+ out_socket: *std.posix.socket_t,
+}, std.posix.AcceptError);
+```
+
+### Connect
+
+See `man connect(2)`
+
+```zig
+pub const Connect = Define(struct {
+ socket: std.posix.socket_t,
+ addr: *const sockaddr,
+ addrlen: std.posix.socklen_t,
+}, std.posix.ConnectError);
+```
+
+### Recv
+
+See `man recv(2)`
+
+```zig
+pub const Recv = Define(struct {
+ socket: std.posix.socket_t,
+ buffer: []u8,
+ out_read: *usize,
+}, std.posix.RecvFromError);
+```
+
+### Send
+
+See `man send(2)`
+
+```zig
+pub const Send = Define(struct {
+ socket: std.posix.socket_t,
+ buffer: []const u8,
+ out_written: ?*usize = null,
+}, std.posix.SendError);
+```
+
+### OpenAt
+
+Opens `path` relative to a `dir`, opening is customized by `flags`.
+The opened file is stored into the location pointed by `out_file`.
+
+```zig
+pub const OpenAt = Define(struct {
+ dir: std.fs.Dir,
+ path: [*:0]const u8,
+ flags: std.fs.File.OpenFlags,
+ out_file: *std.fs.File,
+}, std.fs.File.OpenError);
+```
+
+### Close
+
+Closes a `file`.
+
+```zig
+pub const Close = Define(struct {
+ file: std.fs.File,
+}, error{});
+```
+
+### Timeout
+
+Starts a timeout. Once the timeout expires the operation completes.
+The timeout uses a monotnic clock source.
+
+```zig
+pub const Timeout = Define(struct {
+ ts: struct { sec: i64 = 0, nsec: i64 = 0 },
+}, error{});
+```
+
+### TimeoutRemove
+
+Cancel existing timeout referenced by `id`.
+
+```zig
+pub const TimeoutRemove = Define(struct {
+ id: Id,
+}, error{ InProgress, NotFound });
+```
+
+### LinkTimeout
+
+Timeout linked to a operation.
+The operation before must have set `link_next` to `true`.
+If the operation finishes before the timeout, then the timeout will be canceled.
+If the timeout finishes before the operation, then the operation will be canceled.
+
+```zig
+pub const LinkTimeout = Define(struct {
+ ts: struct { sec: i64 = 0, nsec: i64 = 0 },
+ out_expired: ?*bool = null,
+}, error{InProgress});
+```
+
+### Cancel
+
+Cancel existing operation referenced by `id`.
+
+```zig
+pub const Cancel = Define(struct {
+ id: Id,
+}, error{ InProgress, NotFound });
+```
+
+### RenameAt
+
+Rename a `old_path` relative to `old_dir` into `new_path` relative to `new_dir`.
+
+```zig
+pub const RenameAt = Define(struct {
+ old_dir: std.fs.Dir,
+ old_path: [*:0]const u8,
+ new_dir: std.fs.Dir,
+ new_path: [*:0]const u8,
+}, std.fs.Dir.RenameError);
+```
+
+### UnlinkAt
+
+Delete a file or directory locating in `path` relative to `dir`.
+
+```zig
+pub const UnlinkAt = Define(struct {
+ dir: std.fs.Dir,
+ path: [*:0]const u8,
+}, std.posix.UnlinkatError);
+```
+
+### MkDirAt
+
+Create directory relative to `dir` at `path`.
+The `mode` parameter can specify the mode of the directory on supporting operating systems.
+
+```zig
+pub const MkDirAt = Define(struct {
+ dir: std.fs.Dir,
+ path: [*:0]const u8,
+ mode: u32 = std.fs.Dir.default_mode,
+}, std.fs.Dir.MakeError);
+```
+
+### SymlinkAt
+
+Create a symlink relative to `dir` at `link_path` linking to the `target`.
+
+```zig
+pub const SymlinkAt = Define(struct {
+ dir: std.fs.Dir,
+ target: [*:0]const u8,
+ link_path: [*:0]const u8,
+}, std.posix.SymLinkError);
+```
+
+### Socket
+
+See `man socket(2)`
+
+```zig
+pub const Socket = Define(struct {
+ /// std.posix.AF
+ domain: u32,
+ /// std.posix.SOCK
+ flags: u32,
+ /// std.posix.IPPROTO
+ protocol: u32,
+ out_socket: *std.posix.socket_t,
+}, std.posix.SocketError);
+```
+
+### CloseSocket
+
+Closes a `socket`.
+
+```zig
+pub const CloseSocket = Define(struct {
+ socket: std.posix.socket_t,
+}, error{});
+```
diff --git a/docs/pages/coro-context-switches.mdx b/docs/pages/coro-context-switches.mdx
new file mode 100644
index 0000000..9d933cc
--- /dev/null
+++ b/docs/pages/coro-context-switches.mdx
@@ -0,0 +1,23 @@
+# CORO API
+
+:::warning
+
+This part of the API is likely to change.
+
+:::
+
+
+## Context switches
+
+To yield running task to the caller use the following.
+
+```zig
+coro.yield();
+```
+
+To continue running the task from where it left, use the following.
+This can also be used to cancel any IO operations.
+
+```zig
+coro.wakeup();
+```
diff --git a/docs/pages/coro-io.mdx b/docs/pages/coro-io.mdx
new file mode 100644
index 0000000..639a463
--- /dev/null
+++ b/docs/pages/coro-io.mdx
@@ -0,0 +1,27 @@
+# CORO API
+
+## IO
+
+Inside a task it is possible to use the IO functions inside the `coro.io` namespace to perform cooperative
+IO with the `Scheduler`. When calling a `coro.io` operation from a task, the task setups some internal state,
+queues the IO operations for `Scheduler` and then yields, allowing other code to run in the program.
+
+All the IO operations are merged into one `aio.Dynamic` instance for completition during the next scheduler tick.
+While this may not be beneficial on all backends, the io_uring backend allows the kernel to execute all
+the yielding tasks IO operations with a single syscall.
+
+### Performing io operations
+
+Performing operations is similar to `aio` module. The api is the same, but instead use the `coro.io` namespace.
+Below is a full example of simple server / client program using the `coro` api.
+
+```zig
+// [!include ~/../examples/coro.zig]
+```
+
+### Cancellations
+
+Use `aio.Cancel` operation to cancel the currently running operations in a task.
+The `out_error` of such operation will then be set as `error.OperationCanceled`.
+
+Alternatively it's possible to call `scheduler.wakeup(task);` which also cancels all currently running io on that task.
diff --git a/docs/pages/coro-scheduler.mdx b/docs/pages/coro-scheduler.mdx
new file mode 100644
index 0000000..04b320e
--- /dev/null
+++ b/docs/pages/coro-scheduler.mdx
@@ -0,0 +1,67 @@
+# CORO API
+
+## Scheduler
+
+To do a non-blocking IO while still maintaining the imperative blocking style of coding.
+We need a coroutines and a scheduler that schedules the context switches of said coroutines.
+In this guide we refer to the coroutines as tasks.
+
+### Instanting a Scheduler
+
+Scheduler requires an `allocator` and optional `InitOptions`.
+The scheduler stores the `allocator` and uses it for managed task creation and destruction.
+
+```zig
+var scheduler = try coro.Scheduler.init(gpa.allocator(), .{});
+defer scheduler.deinit();
+```
+
+### Spawning tasks
+
+A new task can be spawned by specifying `entrypoint`, which must be a function with either `void` or `!void` return type.
+If the function is `!void` and it returns error, a stacktrace of the error is dumped, similarly to how `std.Thread` api works.
+Supply arguments to the `entrypoint` by providing a tuple as the second parameter.
+For third parameter, spawn takes a optional `SpawnOptions`, which can be used to specify the stack size of the Task, or
+provide a pre-allocated unmanaged task.
+
+When task is spawned, the `entrypoint` is immediately called and the code in the `entrypoint` runs until the task either
+yields or performs a IO operation using one of the `coro.io` namespace functions.
+
+```zig
+var task = try scheduler.spawn(entrypoint, .{ 1, "args" }, .{});
+```
+
+### Reaping tasks
+
+Following removes a task, frees its memory and cancels all potential running IO operations.
+
+```zig
+scheduler.reap(task);
+```
+
+Alternatively reap all the tasks using the following.
+
+```zig
+scheduler.reapAll();
+```
+
+Call to `deinit` also reaps all tasks.
+
+### Running
+
+The scheduler can process tasks and io one step a time with the tick method.
+By running tick the scheduler will reap tasks that returned (dead tasks) and context switch back to the
+tasks in case they completed their IO operations.
+
+```zig
+// if there are pending io operations, blocks until at least one completes
+try scheduler.tick(.blocking);
+// returns immediately regardless of the current io state
+try scheduler.tick(.nonblocking);
+```
+
+To run the scheduler until all tasks have returned aka died, then use the following.
+
+```zig
+try scheduler.run();
+```
diff --git a/docs/pages/index.mdx b/docs/pages/index.mdx
new file mode 100644
index 0000000..245989a
--- /dev/null
+++ b/docs/pages/index.mdx
@@ -0,0 +1,21 @@
+---
+title: 'zig-aio: io_uring like asynchronous API and coroutine powered IO tasks for zig'
+---
+
+# Overview
+
+zig-aio provides io_uring like asynchronous API and coroutine powered IO tasks for zig
+
+```zig
+// [!include ~/../examples/aio_static.zig]
+```
+
+## Features
+
+- Blocking and asynchronous API
+- Atomic operations
+- Parallel execution
+- Cancellation
+- Timeouts
+- Comes with a runtime and scheduler for coroutines
+- Tightly tied into io_uring
diff --git a/docs/pages/integration.mdx b/docs/pages/integration.mdx
new file mode 100644
index 0000000..96b80d0
--- /dev/null
+++ b/docs/pages/integration.mdx
@@ -0,0 +1,34 @@
+# Integrating zig-aio
+
+## Zig Package Manager
+
+### Fetching and updating the zig-aio dependency
+
+Run the following command in zig project root directory.
+
+```sh
+zig fetch --save git+https://github.com/Cloudef/zig-aio.git
+```
+
+### Using zig-aio modules in zig project
+
+In `build.zig` file add the following for whichever modules `zig-aio` is required.
+
+```zig
+// get the "zig-aio" dependency from "build.zig.zon"
+const zig_aio = b.dependency("zig-aio", .{});
+// for exe, lib, tests, etc.
+exe.root_module.addImport("aio", zig_aio.module("aio"));
+// for coroutines api
+exe.root_module.addImport("coro", zig_aio.module("coro"));
+```
+
+### Using zig-aio in zig code
+
+It's possible to import the modules like this.
+
+```zig
+const aio = @import("aio");
+const coro = @import("coro");
+```
+
diff --git a/docs/tsconfig.json b/docs/tsconfig.json
new file mode 100644
index 0000000..d2636aa
--- /dev/null
+++ b/docs/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["**/*.ts", "**/*.tsx"]
+}
diff --git a/docs/vocs.config.tsx b/docs/vocs.config.tsx
new file mode 100644
index 0000000..b513b51
--- /dev/null
+++ b/docs/vocs.config.tsx
@@ -0,0 +1,81 @@
+import { defineConfig } from 'vocs'
+import plainText from 'vite-plugin-virtual-plain-text';
+
+export default defineConfig({
+ title: 'zig-aio',
+ titleTemplate: '%s - zig-aio',
+ description: 'IO-uring like asynchronous API and coroutine powered IO tasks for zig',
+ editLink: {
+ pattern: 'https://github.com/Cloudef/zig-aio/edit/master/docs/pages/:path',
+ text: 'Suggest changes to this page',
+ },
+ rootDir: '.',
+ socials: [
+ {
+ icon: "github",
+ link: "https://github.com/Cloudef/zig-aio",
+ },
+ ],
+ head: (
+ <>
+
+
+ >
+ ),
+ topNav: [
+ { text: "Docs", link: "/" },
+ {
+ text: "Examples",
+ link: "https://github.com/Cloudef/zig-aio/tree/master/examples",
+ },
+ ],
+ sidebar: [
+ {
+ text: 'Getting Started',
+ items: [{text: 'Overview', link: '/'}],
+ },
+ {
+ text: 'Integration',
+ items: [{text: 'Integrating zig-aio', link: '/integration'}],
+ },
+ {
+ text: 'AIO API',
+ collapsed: false,
+ items: [
+ {
+ text: 'Operations',
+ link: '/aio-operations',
+ },
+ {
+ text: 'Immediate',
+ link: '/aio-immediate',
+ },
+ {
+ text: 'Dynamic',
+ link: '/aio-dynamic',
+ },
+ ],
+ },
+ {
+ text: 'CORO API',
+ collapsed: false,
+ items: [
+ {
+ text: 'Scheduler',
+ link: '/coro-scheduler',
+ },
+ {
+ text: 'IO',
+ link: '/coro-io',
+ },
+ {
+ text: 'Context switches',
+ link: '/coro-context-switches',
+ },
+ ],
+ },
+ ],
+})