Zig SDK
Allocator-aware, transport-first Linkit Zig SDK — Zig 0.16.0+, pluggable HTTP, fluent builders, canonical route parity, and zero forced runtime dependencies.
Zig SDK
The Linkit Zig SDK provides an allocator-aware, transport-first client for the Linkit e-commerce API. Built for Zig 0.16.0+, it uses the idiomatic std.ArrayList (unmanaged-by-default pattern), std.Io for realtime delays (for example retry backoff), fluent builders with compile-time + runtime validation, and pluggable transport injection.
Design Goal
Lean and explicit: allocator-aware APIs, pluggable transport, fluent builders, and canonical /api/v1/* route parity. Zero forced runtime dependencies — inject your own HTTP stack.
Installation
// In your build.zig — add linkit as a module dependency
const linkit_dep = b.dependency("linkit", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("linkit", linkit_dep.module("linkit"));
// build.zig.zon
.dependencies = .{
.linkit = .{
.url = "https://linkit.works/downloads/sdks/zig/linkit-zig-0.2.0.tar.gz",
.hash = "...",
},
},# Download and extract
tar xf linkit-zig-0.2.0.tar.gz
# In your build.zig:
const linkit_module = b.addModule("linkit", .{
.root_source_file = b.path("linkit/src/linkit.zig"),
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("linkit", linkit_module);# Copy SDK into your project
cp -r sdks/zig/linkit ./deps/linkit
# build.zig:
const linkit_module = b.addModule("linkit", .{
.root_source_file = b.path("deps/linkit/src/linkit.zig"),
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("linkit", linkit_module);Build & Test
cd sdks/zig/linkit
zig build testZig 0.16.0+ Required
The SDK targets Zig 0.16.0 or later — for example std.Io.sleep for retry backoff, std.mem.trimEnd, and the current std.ArrayList layout. Use a 0.16.x toolchain when building or running zig build test.
Quick Start
Implement a Transport
const std = @import("std");
const linkit = @import("linkit");
fn httpSend(ctx: *anyopaque, req: linkit.Request, allocator: std.mem.Allocator) !linkit.Response {
_ = ctx;
// Implement using std.http.Client, zig-curl, etc.
// Map req.method, req.url, req.body, req.bearer_token
return .{
.status = 200,
.body = try allocator.dupe(u8, "{\"status\":\"healthy\"}"),
};
}Initialize the Client
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var transport_state: u8 = 0;
var client = try linkit.Client.init(gpa.allocator(), .{
.base_url = "https://linkit.works/api/v1",
.jwt_token = "your-jwt-token",
.timeout_ms = 30_000,
.max_retries = 3,
}, .{
.ctx = &transport_state,
.send_fn = httpSend,
});Use Service APIs
// Health check — no auth header sent
var health = try client.health().check();
defer health.deinit(gpa.allocator());
if (!health.ok()) {
std.debug.print("API unhealthy: {d}\n", .{health.status});
return;
}
// Fetch a product by IV ID
var product = try client.products().getByIvId("PROD-001");
defer product.deinit(gpa.allocator());
std.debug.print("Product: {s}\n", .{product.body});Configuration
var client = try linkit.Client.init(allocator, .{
.base_url = "https://linkit.works/api/v1",
.jwt_token = "your-jwt-token",
.timeout_ms = 30_000,
.max_retries = 3,
.retry_base_delay_ms = 150,
.max_concurrent_requests = 16,
.user_agent = "MyApp/1.0 LinkitZigSDK/0.2.0",
}, transport);Configuration Fields
| Field | Type | Default | Description |
|---|---|---|---|
base_url | []const u8 | required | API base URL (trailing / is stripped) |
jwt_token | []const u8 | "" | JWT authentication token |
timeout_ms | u32 | 30_000 | Request timeout in milliseconds |
max_retries | u8 | 3 | Maximum retry attempts for transient errors |
retry_base_delay_ms | u32 | 150 | Base delay for exponential backoff |
max_concurrent_requests | u16 | 16 | Concurrency limit |
user_agent | []const u8 | "LinkitZigSDK/0.2.0" | User-Agent header value |
Config Validation
The client constructor validates configuration at init time:
// These will return errors at init:
// - Empty base_url → error.InvalidBaseUrl
// - Missing http:// or https:// → error.InvalidBaseUrl
// - timeout_ms < 1000 → error.InvalidTimeout
// - max_concurrent_requests == 0 → error.InvalidConcurrencyServices Reference
| Service | Methods | Description |
|---|---|---|
client.health() | check, detailed, getSystem, ping | System health (no auth) |
client.products() | create, createRequest, getByIvId, update, updateRequest, deleteByIvId, list, listAll | Product catalog |
client.skus() | create, createRequest, getByIvId, update, updateRequest, updateStock, updateStockRequest, deleteByIvId, list, listAll | SKU inventory per branch |
client.branches() | create, createRequest, getByIvId, update, updateRequest, deleteByIvId, list, listAll | Physical locations |
client.customers() | create, getById, update, deleteById, getAddresses, createAddress, createGroup, list, listAll | Customer management |
client.orders() | getById, updateStatus, list | Order management |
client.offers() | create, getById, update, updateStatus, deleteById, list, bulkUpsert | Promotions & discounts |
client.brands() | create, getById, update, deleteById | Brand management |
client.categories() | create, getById, update, deleteById | Product categories |
client.generics() | create, getById, update, deleteById | Generic entities |
client.integrations() | getConfig, execute, enable, disable | Platform connections |
Products
Create with JSON
var product = try client.products().create(
\\{"ivId":"PROD-001","nameEn":"Premium Coffee","averagePrice":29.99}
);
defer product.deinit(allocator);Create with Typed Request
var builder = linkit.ProductCreateBuilder{};
const request = try builder
.withIvId("PROD-001")
.withNameEn("Premium Coffee")
.withNameAr("قهوة ممتازة")
.withAveragePrice(29.99)
.withEnabled(true)
.build();
var product = try client.products().createRequest(request);
defer product.deinit(allocator);List with Filters
var products = try client.products().list(.{
.page = 1,
.limit = 50,
.enabled = true,
.category_id = "beverages",
.brand_id = "brand-01",
.search = "coffee",
});
defer products.deinit(allocator);Auto-Pagination Iterator
var iterator = client.products().listAll(.{ .limit = 100 });
while (try iterator.next()) |*page| {
defer page.deinit(allocator);
// Process page.body JSON
std.debug.print("Page: {s}\n", .{page.body});
}SKUs
Create with Builder
var sku_builder = linkit.SkuCreateBuilder{};
const sku_request = try sku_builder
.withIvId("SKU-001")
.inBranch("BR-RYD-001")
.forProduct("PROD-001")
.withPrice(29.99)
.withQty(1000)
.withMaxQty(5000)
.withAvailable(true)
.withBarcode("6281234567890")
.build();
var sku = try client.skus().createRequest(sku_request);
defer sku.deinit(allocator);Update Stock
var stock_builder = linkit.SkuStockUpdateBuilder{};
const stock_request = try stock_builder
.withIvId("SKU-001")
.inBranch("BR-RYD-001")
.withQty(950)
.withAvailable(true)
.build();
var result = try client.skus().updateStockRequest("SKU-001", stock_request, "BR-RYD-001");
defer result.deinit(allocator);List by Branch
var skus = try client.skus().list(.{
.branch_iv_id = "BR-RYD-001",
.available = true,
.low_stock = false,
.page = 1,
.limit = 25,
});
defer skus.deinit(allocator);Branches
Create with Builder
var branch_builder = linkit.BranchCreateBuilder{};
const branch = try branch_builder
.withIvId("BR-RYD-001")
.withNameEn("Riyadh Central")
.withNameAr("الرياض سنتر")
.withLocation(24.7136, 46.6753)
.withActive(true)
.withEmail("riyadh@example.com")
.build();
var result = try client.branches().createRequest(branch);
defer result.deinit(allocator);Paginate All Branches
var it = client.branches().listAll(.{ .limit = 100 });
while (try it.next()) |*page| {
defer page.deinit(allocator);
std.debug.print("Branches: {s}\n", .{page.body});
}Customers
// Create
var customer = try client.customers().create(
\\{"firstName":"Ahmad","lastName":"Hamdi","email":"ahmad@example.com","phone":"+966500000000"}
);
defer customer.deinit(allocator);
// Get addresses
var addresses = try client.customers().getAddresses("C-001");
defer addresses.deinit(allocator);
// Create address
var address = try client.customers().createAddress("C-001",
\\{"label":"Home","address1":"123 Main St","city":"Riyadh","country":"SA"}
);
defer address.deinit(allocator);
// Create group
var group = try client.customers().createGroup(
\\{"name":"VIP Customers"}
);
defer group.deinit(allocator);Orders & Offers
// Update order status
var order = try client.orders().updateStatus("ORD-001",
\\{"status":"shipped"}
);
defer order.deinit(allocator);
// Update offer status
var offer = try client.offers().updateStatus("OFR-001",
\\{"status":"paused"}
);
defer offer.deinit(allocator);
// Bulk upsert offers
var bulk = try client.offers().bulkUpsert(
\\[{"name":"Weekend 15%","offerType":"direct_discount","discountValue":15},
\\{"name":"BOGO Deal","offerType":"bogo","buyQty":1,"getQty":1}]
);
defer bulk.deinit(allocator);Integrations
// Get integration config
var config = try client.integrations().getConfig("hungerstation-orders-v2");
defer config.deinit(allocator);
// Execute integration
var result = try client.integrations().execute("hungerstation-orders-v2");
defer result.deinit(allocator);
// Enable / disable
var enable = try client.integrations().enable("hungerstation-orders-v2");
defer enable.deinit(allocator);
var disable = try client.integrations().disable("hungerstation-orders-v2");
defer disable.deinit(allocator);Fluent Builders
The SDK provides four fluent builders with compile-time struct defaults and runtime validation:
| Builder | Required Fields | Optional Fields |
|---|---|---|
SkuCreateBuilder | ivId, branchIvId, productIvId | categoryId, barcode, gtin, price, qty, maxQty, buffer, reorderThreshold, available |
SkuStockUpdateBuilder | ivId, branchIvId | qty, available |
ProductCreateBuilder | ivId | nameEn, nameAr, descEn, descAr, brandId, categoryId, averagePrice, isEnabled, buffer |
BranchCreateBuilder | ivId, nameEn, nameAr | location, active, email |
Validation Errors
Calling .build() on an invalid builder returns typed errors:
var bad_sku = linkit.SkuCreateBuilder{};
_ = bad_sku.withIvId(""); // empty ID
const result = bad_sku.build();
// result == error.InvalidIvId
var bad_price = linkit.SkuCreateBuilder{};
_ = bad_price.withIvId("SKU-1").inBranch("BR-1").forProduct("P-1").withPrice(-5.0);
const result2 = bad_price.build();
// result2 == error.InvalidPriceError Handling
All service methods return !Response. Check response.ok() for success:
var response = try client.products().getByIvId("NONEXISTENT");
defer response.deinit(allocator);
if (!response.ok()) {
std.debug.print("Error {d}: {s}\n", .{ response.status, response.body });
// Parse status for specific handling:
// 401 → authentication failed
// 404 → not found
// 429 → rate limited
// 5xx → server error (will be retried automatically)
}Retry Behavior
The client automatically retries on transient errors (429, 503, 504) with exponential backoff:
Attempt 0: immediate
Attempt 1: base_delay × 2^0 = 150ms
Attempt 2: base_delay × 2^1 = 300ms
Attempt 3: base_delay × 2^2 = 600msNon-transient errors (4xx except 429) propagate immediately.
Transport Interface
The SDK ships with zero HTTP dependencies — implement the Transport struct to inject any HTTP backend:
pub const SendFn = *const fn (
ctx: *anyopaque,
req: Request,
allocator: std.mem.Allocator,
) anyerror!Response;
pub const Transport = struct {
ctx: *anyopaque,
send_fn: SendFn,
pub fn send(self: Transport, req: Request, allocator: std.mem.Allocator) !Response {
return self.send_fn(self.ctx, req, allocator);
}
};Request/Response Types
pub const HttpMethod = enum { GET, POST, PUT, PATCH, DELETE };
pub const Request = struct {
method: HttpMethod,
url: []const u8, // Full URL with query params
body: ?[]const u8 = null, // JSON body (null for GET/DELETE)
bearer_token: ?[]const u8 = null, // null for health routes
};
pub const Response = struct {
status: u16,
body: []u8, // Caller-owned, must be freed
pub fn ok(self: Response) bool {
return self.status >= 200 and self.status < 300;
}
pub fn deinit(self: *Response, allocator: std.mem.Allocator) void {
allocator.free(self.body);
}
};Memory Management
Allocator Discipline
Every Response returned by service methods owns its .body slice. You must call response.deinit(allocator) to free it. Use defer immediately after the call.
// ✅ Correct — defer immediately
var response = try client.products().getByIvId("P-1");
defer response.deinit(allocator);
// ❌ Leaks memory — no deinit
var response = try client.products().getByIvId("P-1");
// forgot deinit!Core Features
- Full 11-service parity with Linkit API domains
- Canonical route mapping (
/api/v1/products/iv/{id},/api/v1/skus/iv/{id}/stock, etc.) - Health routes skip auth header attachment automatically
- Iterator-based pagination via
listAll(...)on Products, SKUs, Branches, Customers - Fluent builders with compile-time defaults and runtime validation
- Exponential backoff retry for transient HTTP errors
- Zig 0.16.0+ idiomatic
std.ArrayList(unmanaged-by-default) andstd.Iofor realtime delays
Download
Archive builds are staged into:
/downloads/sdks/zig/
C++ SDK
Lean, transport-first Linkit C++20 SDK with canonical routes, async support, header-only core, and zero forced HTTP dependencies.
SDK Downloads
Download Linkit SDK packages directly — NuGet, npm, PyPI, pub.dev, Maven/Gradle, Cargo, SPM, and Go module archives for offline or private installation.