ziex 0.1.0-dev.1309 → 0.1.0-dev.1367

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +19 -25
  2. package/cloudflare/index.js +140 -22
  3. package/index.js +125 -18
  4. package/package.json +2 -2
  5. package/pkg/ziex/src/fetch.d.ts +5 -0
  6. package/{wasm → pkg/ziex/src/wasm}/core.d.ts +2 -2
  7. package/vendor/jsz/js/src/index.d.ts +1 -0
  8. package/vendor/jsz/js/src/zigjs.d.ts +107 -0
  9. package/wasm/index.js +120 -12
  10. package/wasm/init.dev.js +93 -8
  11. package/wasm/init.js +3 -2
  12. /package/{app.d.ts → pkg/ziex/src/app.d.ts} +0 -0
  13. /package/{aws-lambda → pkg/ziex/src/aws-lambda}/index.d.ts +0 -0
  14. /package/{browser → pkg/ziex/src/browser}/kv.d.ts +0 -0
  15. /package/{cloudflare → pkg/ziex/src/cloudflare}/app.d.ts +0 -0
  16. /package/{cloudflare → pkg/ziex/src/cloudflare}/db.d.ts +0 -0
  17. /package/{cloudflare → pkg/ziex/src/cloudflare}/do.d.ts +0 -0
  18. /package/{cloudflare → pkg/ziex/src/cloudflare}/index.d.ts +0 -0
  19. /package/{cloudflare → pkg/ziex/src/cloudflare}/kv.d.ts +0 -0
  20. /package/{cloudflare → pkg/ziex/src/cloudflare}/worker.d.ts +0 -0
  21. /package/{db.d.ts → pkg/ziex/src/db.d.ts} +0 -0
  22. /package/{index.d.ts → pkg/ziex/src/index.d.ts} +0 -0
  23. /package/{kv.d.ts → pkg/ziex/src/kv.d.ts} +0 -0
  24. /package/{react → pkg/ziex/src/react}/dom.d.ts +0 -0
  25. /package/{react → pkg/ziex/src/react}/index.d.ts +0 -0
  26. /package/{react → pkg/ziex/src/react}/types.d.ts +0 -0
  27. /package/{runtime.d.ts → pkg/ziex/src/runtime.d.ts} +0 -0
  28. /package/{vercel → pkg/ziex/src/vercel}/index.d.ts +0 -0
  29. /package/{wasi.d.ts → pkg/ziex/src/wasi.d.ts} +0 -0
  30. /package/{wasm → pkg/ziex/src/wasm}/dom.d.ts +0 -0
  31. /package/{wasm → pkg/ziex/src/wasm}/index.d.ts +0 -0
  32. /package/{wasm → pkg/ziex/src/wasm}/init.d.ts +0 -0
  33. /package/{wasm → pkg/ziex/src/wasm}/types.d.ts +0 -0
  34. /package/{wasm → pkg/ziex/src/wasm}/wasi.d.ts +0 -0
  35. /package/{zx.d.ts → pkg/ziex/src/zx.d.ts} +0 -0
package/README.md CHANGED
@@ -8,37 +8,29 @@ Ziex combines the power and performance of Zig with the expressiveness and simpl
8
8
 
9
9
  **[Documentation →](https://ziex.dev/learn)**
10
10
 
11
-
12
11
  > **Note:** Most of the API and syntax are finalized and stable, and server-side rendering (SSR) features are production-ready, Ziex continues to evolve with ongoing improvements to client-side rendering and state management, see the [roadmap](#roadmap). You can start using the documented features today, as they are stable and unlikely to change. Areas still under development are not yet documented and will be added as they mature. See [versions](#versions) for Zig and Ziex versions compatibility.
13
12
 
14
-
15
13
  ## Getting Started
16
14
 
17
15
  ### 1. Installing CLI (optional)
18
16
 
19
- macOS/Linux
20
17
  ```bash
18
+ # macOS/Linux
21
19
  curl -fsSL https://ziex.dev/install | bash
22
- ```
23
- Windows
24
- ```
20
+
21
+ # Windows
25
22
  powershell -c "irm ziex.dev/install.ps1 | iex"
26
23
  ```
27
24
 
25
+
28
26
  ### 2. Initializing Project
27
+
29
28
  ```powershell
30
29
  zx init
31
30
  # or
32
31
  npm init ziex
33
32
  ```
34
33
 
35
- ### 3. Running Project
36
- ```powershell
37
- zx dev
38
- # or
39
- npm run dev
40
- ```
41
-
42
34
  You will need [compatible](#versions) Zig version when using zx CLI or you can use the `Node` template from `npm init ziex`
43
35
 
44
36
  Read [Getting Started →](https://ziex.dev/learn) for more details.
@@ -159,6 +151,7 @@ const zx = @import("zx");
159
151
  </details>
160
152
 
161
153
  ## Features
154
+
162
155
  - **Declarative UI**: Declarative UI components using with full access to Zig's control flow.
163
156
  - **Full-Stack Capabilities**: Build both frontend and backend of web application.
164
157
  - **Fast**: Significantly faster at SSR than many other frameworks.
@@ -170,26 +163,27 @@ const zx = @import("zx");
170
163
  - **Developer Tooling**: CLI, hot reload, and editor extensions for the best DX.
171
164
 
172
165
  ## Versions
173
- | Zig | Ziex | Branch | Status |
174
- |-------------|---------------------|-------------------|----------------|
175
- | `0.17.x` | | `main` | Development |
176
- | `0.16.x` | `0.1.0-dev.1259` | `zig-0.16` | **Latest** |
177
- | `0.15.x` | `0.1.0-dev-1050` | `zig-0.15` | Latest (`Zig 0.15.x`) |
178
166
 
167
+ | Zig | Ziex | Branch | Status |
168
+ | -------- | --------------------------------------------------------------------------------- | ---------- | ----------- |
169
+ | `0.17.x` | | `main` | Development |
170
+ | `0.16.x` | [`0.1.0-dev.1259`](https://github.com/ziex-dev/ziex/releases/tag/v0.1.0-dev.1259) | `zig-0.16` | **Latest** |
171
+ | `0.15.x` | [`0.1.0-dev-1050`](https://github.com/ziex-dev/ziex/releases/tag/v0.1.0-dev.1050) | `zig-0.15` | Outdated |
179
172
 
180
173
  ## Roadmap
181
- [Ziex 0.1.0](https://github.com/ziex-dev/ziex/milestone/2) is planned for release after `Zig 0.17.0` is available. You can view the [roadmap](https://github.com/ziex-dev/ziex/milestone) to learn more.
182
174
 
183
- The `0.1.0` release will indicate that Ziex is production-ready, with all major features fully implemented, and will receive patch releases for bug fixes.
175
+ [Ziex 0.1.0](https://github.com/ziex-dev/ziex/milestone/2) is planned for release after `Zig 0.17.0` is available. You can view the [roadmap](https://github.com/ziex-dev/ziex/milestones) to learn more.
176
+
177
+ The [`0.1.0`](https://github.com/ziex-dev/ziex/milestone/2) release will indicate that Ziex is production-ready, with all major features fully implemented, and will receive patch releases for bug fixes.
184
178
 
185
- The `1.0.0` release will signify long-term support for that major version, receiving bug fixes and minor updates.
179
+ The [`1.0.0`](https://github.com/ziex-dev/ziex/milestone/6) release will signify long-term support for that major version, receiving bug fixes and minor updates.
186
180
 
187
181
  ## Editor Support
188
182
 
189
- * [VSCode](https://marketplace.visualstudio.com/items?itemName=ziex.ziex)/[VSCode Forks](https://open-vsx.org/extension/ziex/ziex)
190
- * [Neovim](/ide/neovim/)
191
- * [Helix](/ide/helix/)
192
- * [Zed](/ide/zed/)
183
+ - [VSCode](https://marketplace.visualstudio.com/items?itemName=ziex.ziex)/[VSCode Forks](https://open-vsx.org/extension/ziex/ziex)
184
+ - [Neovim](/ide/neovim/)
185
+ - [Helix](/ide/helix/)
186
+ - [Zed](/ide/zed/)
193
187
 
194
188
  ## Community
195
189
 
@@ -193,6 +193,76 @@ class ZigJS {
193
193
  return decoder.decode(data);
194
194
  }
195
195
  }
196
+ // src/fetch.ts
197
+ function createFetchImports(getMemory) {
198
+ const encoder2 = new TextEncoder;
199
+ const decoder2 = new TextDecoder;
200
+ function readStr(ptr, len) {
201
+ return decoder2.decode(new Uint8Array(getMemory().buffer, ptr, len));
202
+ }
203
+ function writeBytes(buf_ptr, buf_max, data) {
204
+ if (data.length > buf_max)
205
+ return -2;
206
+ new Uint8Array(getMemory().buffer, buf_ptr, data.length).set(data);
207
+ return data.length;
208
+ }
209
+ function readBytes(ptr, len) {
210
+ return new Uint8Array(getMemory().buffer, ptr, len);
211
+ }
212
+ function parseHeaders(headersJson) {
213
+ let headers = {};
214
+ try {
215
+ headers = JSON.parse(headersJson);
216
+ } catch {
217
+ for (const line of headersJson.split(`
218
+ `)) {
219
+ const colonIdx = line.indexOf(":");
220
+ if (colonIdx > 0) {
221
+ headers[line.slice(0, colonIdx)] = line.slice(colonIdx + 1);
222
+ }
223
+ }
224
+ }
225
+ return headers;
226
+ }
227
+ async function doFetch(url_ptr, url_len, method_ptr, method_len, headers_ptr, headers_len, body_ptr, body_len, timeout_ms, status_out, buf_ptr, buf_max) {
228
+ const url = readStr(url_ptr, url_len);
229
+ const method = method_len > 0 ? readStr(method_ptr, method_len) : "GET";
230
+ const headersJson = headers_len > 0 ? readStr(headers_ptr, headers_len) : "{}";
231
+ const body = body_len > 0 ? readBytes(body_ptr, body_len) : undefined;
232
+ const headers = parseHeaders(headersJson);
233
+ const controller = new AbortController;
234
+ const timeout = timeout_ms > 0 ? setTimeout(() => controller.abort(), timeout_ms) : null;
235
+ try {
236
+ const response = await fetch(url, {
237
+ method,
238
+ headers: Object.keys(headers).length > 0 ? headers : undefined,
239
+ body: method !== "GET" && method !== "HEAD" ? body : undefined,
240
+ signal: controller.signal
241
+ });
242
+ if (timeout)
243
+ clearTimeout(timeout);
244
+ new DataView(getMemory().buffer).setUint16(status_out, response.status, true);
245
+ const bytes = new Uint8Array(await response.arrayBuffer());
246
+ return writeBytes(buf_ptr, buf_max, bytes);
247
+ } catch (err) {
248
+ if (timeout)
249
+ clearTimeout(timeout);
250
+ const msg = err instanceof Error ? err.message : String(err);
251
+ console.error(`wasm fetch failed: ${url} (${msg})`);
252
+ return -1;
253
+ }
254
+ }
255
+ const Suspending = WebAssembly.Suspending;
256
+ if (typeof Suspending !== "function") {
257
+ return {
258
+ fetch: (_url_ptr, _url_len, _method_ptr, _method_len, _headers_ptr, _headers_len, _body_ptr, _body_len, _timeout_ms, _status_out, _buf_ptr, _buf_max) => -1
259
+ };
260
+ }
261
+ return {
262
+ fetch: new Suspending(doFetch)
263
+ };
264
+ }
265
+
196
266
  // src/wasm/core.ts
197
267
  var CallbackType = {
198
268
  Event: 0,
@@ -291,7 +361,7 @@ class ZxBridgeCore {
291
361
  const url = readString(urlPtr, urlLen);
292
362
  const method = methodLen > 0 ? readString(methodPtr, methodLen) : "GET";
293
363
  const headersJson = headersLen > 0 ? readString(headersPtr, headersLen) : "{}";
294
- const body = bodyLen > 0 ? readString(bodyPtr, bodyLen) : undefined;
364
+ const body = bodyLen > 0 ? getMemoryView().subarray(bodyPtr, bodyPtr + bodyLen) : undefined;
295
365
  let headers = {};
296
366
  try {
297
367
  headers = JSON.parse(headersJson);
@@ -315,8 +385,8 @@ class ZxBridgeCore {
315
385
  fetch(url, fetchOptions).then(async (response) => {
316
386
  if (timeout)
317
387
  clearTimeout(timeout);
318
- const text = await response.text();
319
- this._notifyFetchComplete(fetchId, response.status, text, false);
388
+ const bytes = new Uint8Array(await response.arrayBuffer());
389
+ this._notifyFetchComplete(fetchId, response.status, bytes, false);
320
390
  }).catch((error) => {
321
391
  if (timeout)
322
392
  clearTimeout(timeout);
@@ -327,7 +397,7 @@ class ZxBridgeCore {
327
397
  }
328
398
  _notifyFetchComplete(fetchId, statusCode, body, isError) {
329
399
  const handler = this.#fetchCompleteHandler;
330
- const encoded = textEncoder.encode(body);
400
+ const encoded = typeof body === "string" ? textEncoder.encode(body) : body;
331
401
  const ptr = this._alloc(encoded.length);
332
402
  writeBytes(ptr, encoded);
333
403
  invokeWasmExport(handler, fetchId, statusCode, ptr, encoded.length, isError ? 1 : 0);
@@ -384,6 +454,11 @@ class ZxBridgeCore {
384
454
  static createImportObject(bridgeRef) {
385
455
  return {
386
456
  ...jsz.importObject(),
457
+ __zx_net: createFetchImports(() => {
458
+ if (!jsz.memory)
459
+ throw new Error("WASM memory is not ready");
460
+ return jsz.memory;
461
+ }),
387
462
  __zx: {
388
463
  _log: (level, ptr, len) => ZxBridgeCore.log(level, ptr, len),
389
464
  _fetchAsync: (urlPtr, urlLen, methodPtr, methodLen, headersPtr, headersLen, bodyPtr, bodyLen, timeoutMs, fetchId) => {
@@ -408,20 +483,48 @@ __export(exports_kv, {
408
483
  createMemoryKV: () => createMemoryKV,
409
484
  createKVImports: () => createKVImports
410
485
  });
486
+ function putOptions(ttlSeconds) {
487
+ if (ttlSeconds < 60)
488
+ return;
489
+ return { expirationTtl: ttlSeconds };
490
+ }
411
491
  function createMemoryKV() {
412
492
  const store = new Map;
493
+ function read(key) {
494
+ const entry = store.get(key);
495
+ if (!entry)
496
+ return null;
497
+ if (entry.expiresAt !== undefined && Date.now() >= entry.expiresAt) {
498
+ store.delete(key);
499
+ return null;
500
+ }
501
+ return entry.value;
502
+ }
503
+ function write(key, value, options) {
504
+ let expiresAt;
505
+ if (options?.expiration !== undefined) {
506
+ expiresAt = options.expiration * 1000;
507
+ } else if (options?.expirationTtl !== undefined) {
508
+ expiresAt = Date.now() + options.expirationTtl * 1000;
509
+ }
510
+ store.set(key, { value, expiresAt });
511
+ }
413
512
  return {
414
513
  async get(key) {
415
- return store.get(key) ?? null;
514
+ return read(key);
416
515
  },
417
- async put(key, value) {
418
- store.set(key, value);
516
+ async put(key, value, options) {
517
+ write(key, value, options);
419
518
  },
420
519
  async delete(key) {
421
520
  store.delete(key);
422
521
  },
423
522
  async list(options) {
424
- const keys = [...store.keys()].filter((k) => !options?.prefix || k.startsWith(options.prefix)).map((name) => ({ name }));
523
+ const keys = [...store.keys()].filter((k) => {
524
+ if (options?.prefix && !k.startsWith(options.prefix))
525
+ return false;
526
+ return read(k) !== null;
527
+ }).map((name) => ({ name }));
425
528
  return { keys };
426
529
  }
427
530
  };
@@ -461,11 +564,11 @@ function createKVImports(bindings, getMemory) {
461
564
  return -1;
462
565
  return writeBytes2(buf_ptr, buf_max, encoder2.encode(value));
463
566
  },
464
- kv_put: (ns_ptr, ns_len, key_ptr, key_len, val_ptr, val_len) => {
567
+ kv_put: (ns_ptr, ns_len, key_ptr, key_len, val_ptr, val_len, ttl_seconds) => {
465
568
  const b = syncBinding(readStr(ns_ptr, ns_len));
466
569
  if (!b)
467
570
  return 0;
468
- b.putSync(readStr(key_ptr, key_len), readStr(val_ptr, val_len));
571
+ b.putSync(readStr(key_ptr, key_len), readStr(val_ptr, val_len), putOptions(ttl_seconds));
469
572
  return 0;
470
573
  },
471
574
  kv_delete: (ns_ptr, ns_len, key_ptr, key_len) => {
@@ -495,11 +598,11 @@ function createKVImports(bindings, getMemory) {
495
598
  return -1;
496
599
  return writeBytes2(buf_ptr, buf_max, encoder2.encode(value));
497
600
  }),
498
- kv_put: new Suspending(async (ns_ptr, ns_len, key_ptr, key_len, val_ptr, val_len) => {
601
+ kv_put: new Suspending(async (ns_ptr, ns_len, key_ptr, key_len, val_ptr, val_len, ttl_seconds) => {
499
602
  const b = binding(readStr(ns_ptr, ns_len));
500
603
  if (!b)
501
604
  return -1;
502
- await b.put(readStr(key_ptr, key_len), readStr(val_ptr, val_len));
605
+ await b.put(readStr(key_ptr, key_len), readStr(val_ptr, val_len), putOptions(ttl_seconds));
503
606
  return 0;
504
607
  }),
505
608
  kv_delete: new Suspending(async (ns_ptr, ns_len, key_ptr, key_len) => {
@@ -793,6 +896,11 @@ class ZxBridge extends ZxBridgeCore {
793
896
  static createImportObject(bridgeRef) {
794
897
  return {
795
898
  ...jsz.importObject(),
899
+ __zx_net: createFetchImports(() => {
900
+ if (!jsz.memory)
901
+ throw new Error("WASM memory is not ready");
902
+ return jsz.memory;
903
+ }),
796
904
  __zx: {
797
905
  _log: (level, ptr, len) => ZxBridgeCore.log(level, ptr, len),
798
906
  _setEventHandlerMode: (vnodeId, eventTypeId, maySuspend) => {
@@ -1325,7 +1433,7 @@ class ZxWasiBridge {
1325
1433
  const url = this.#readString(urlPtr, urlLen);
1326
1434
  const method = methodLen > 0 ? this.#readString(methodPtr, methodLen) : "GET";
1327
1435
  const headersJson = headersLen > 0 ? this.#readString(headersPtr, headersLen) : "{}";
1328
- const body = bodyLen > 0 ? this.#readString(bodyPtr, bodyLen) : undefined;
1436
+ const body = bodyLen > 0 ? this.#view().subarray(bodyPtr, bodyPtr + bodyLen) : undefined;
1329
1437
  let headers = {};
1330
1438
  try {
1331
1439
  headers = JSON.parse(headersJson);
@@ -1347,7 +1455,8 @@ class ZxWasiBridge {
1347
1455
  }).then(async (res) => {
1348
1456
  if (timeout)
1349
1457
  clearTimeout(timeout);
1350
- this.#notifyFetchComplete(fetchId, res.status, await res.text(), false);
1458
+ const bytes = new Uint8Array(await res.arrayBuffer());
1459
+ this.#notifyFetchComplete(fetchId, res.status, bytes, false);
1351
1460
  }).catch((err) => {
1352
1461
  if (timeout)
1353
1462
  clearTimeout(timeout);
@@ -1356,7 +1465,7 @@ class ZxWasiBridge {
1356
1465
  });
1357
1466
  }
1358
1467
  #notifyFetchComplete(fetchId, status, body, isError) {
1359
- const encoded = encoder2.encode(body);
1468
+ const encoded = typeof body === "string" ? encoder2.encode(body) : body;
1360
1469
  const ptr = this.#alloc(encoded.length);
1361
1470
  this.#writeBytes(ptr, encoded);
1362
1471
  this.#fetchCompleteHandler(fetchId, status, ptr, encoded.length, isError ? 1 : 0);
@@ -1563,7 +1672,9 @@ function createWasiImports({
1563
1672
  "--method",
1564
1673
  request.method,
1565
1674
  "--search",
1566
- url.search
1675
+ url.search,
1676
+ "--url",
1677
+ url.href
1567
1678
  ];
1568
1679
  request.headers.forEach((value, name) => {
1569
1680
  if (value)
@@ -1585,7 +1696,7 @@ function createWasiImports({
1585
1696
  `);
1586
1697
  stderrPartial = lines.pop() ?? "";
1587
1698
  for (const line of lines) {
1588
- if (line.startsWith("__EDGE_META__:")) {
1699
+ if (line.startsWith("__ZIEX_META__:")) {
1589
1700
  stderrMeta += line + `
1590
1701
  `;
1591
1702
  } else if (line.length > 0) {
@@ -1817,7 +1928,7 @@ function createWasiImports({
1817
1928
  const remaining = stderrDecoder.decode(undefined, { stream: false });
1818
1929
  const tail = stderrPartial + remaining;
1819
1930
  if (tail.length > 0) {
1820
- if (tail.startsWith("__EDGE_META__:"))
1931
+ if (tail.startsWith("__ZIEX_META__:"))
1821
1932
  stderrMeta += tail;
1822
1933
  else
1823
1934
  console.error("[ziex]", tail);
@@ -1949,9 +2060,9 @@ function executeWasm(instance, jspi, Suspending, wsState) {
1949
2060
  }
1950
2061
  });
1951
2062
  }
1952
- function parseEdgeMeta(stderrText) {
2063
+ function parseZiexMeta(stderrText) {
1953
2064
  const meta = { status: 200, headers: new Headers, streaming: false };
1954
- const metaPrefix = "__EDGE_META__:";
2065
+ const metaPrefix = "__ZIEX_META__:";
1955
2066
  const metaLine = stderrText.split(`
1956
2067
  `).find((line) => line.startsWith(metaPrefix));
1957
2068
  if (metaLine) {
@@ -2016,6 +2127,7 @@ async function run({
2016
2127
  __zx_ws: buildWsImports(jspi ? Suspending : null, mem, new TextDecoder, wsState),
2017
2128
  __zx_kv: createKVImports(kvBindings ?? { default: createMemoryKV() }, mem),
2018
2129
  __zx_db: createD1Imports(dbBindings ?? {}, mem),
2130
+ __zx_net: createFetchImports(mem),
2019
2131
  ...imports ? imports(mem) : {},
2020
2132
  ...ZxWasiBridge.createImportObject(bridgeRef)
2021
2133
  });
@@ -2029,7 +2141,7 @@ async function run({
2029
2141
  return new Response(null, { status: 101, webSocket: server.client });
2030
2142
  }
2031
2143
  const { stderrText: earlyStderrText } = collectOutput();
2032
- const earlyMeta = parseEdgeMeta(earlyStderrText);
2144
+ const earlyMeta = parseZiexMeta(earlyStderrText);
2033
2145
  if (earlyMeta.streaming) {
2034
2146
  const { readable, writable } = new TransformStream;
2035
2147
  streamWriter = writable.getWriter();
@@ -2041,9 +2153,14 @@ async function run({
2041
2153
  }
2042
2154
  await wasmPromise;
2043
2155
  const { stderrText } = collectOutput();
2044
- const meta = parseEdgeMeta(stderrText);
2156
+ const meta = parseZiexMeta(stderrText);
2045
2157
  const body = mergeUint8Arrays(stdoutChunks);
2046
2158
  meta.headers.delete("transfer-encoding");
2159
+ const nullBody = meta.status === 101 || meta.status === 204 || meta.status === 205 || meta.status === 304;
2160
+ if (nullBody) {
2161
+ meta.headers.delete("content-length");
2162
+ return new Response(null, { status: meta.status, headers: meta.headers });
2163
+ }
2047
2164
  if (!meta.headers.has("content-length"))
2048
2165
  meta.headers.set("content-length", String(body.byteLength));
2049
2166
  return new Response(body.buffer, { status: meta.status, headers: meta.headers });
@@ -2190,6 +2307,7 @@ function createWebSocketDO(module, options) {
2190
2307
  __zx_ws: wsImports,
2191
2308
  __zx_kv: createKVImports(kvBindings ?? { default: createMemoryKV() }, mem),
2192
2309
  __zx_db: createD1Imports(dbBindings ?? {}, mem),
2310
+ __zx_net: createFetchImports(mem),
2193
2311
  ...options?.imports ? options.imports(mem) : {},
2194
2312
  ...bridgeImports
2195
2313
  });
package/index.js CHANGED
@@ -74,7 +74,7 @@ class ZxWasiBridge {
74
74
  const url = this.#readString(urlPtr, urlLen);
75
75
  const method = methodLen > 0 ? this.#readString(methodPtr, methodLen) : "GET";
76
76
  const headersJson = headersLen > 0 ? this.#readString(headersPtr, headersLen) : "{}";
77
- const body = bodyLen > 0 ? this.#readString(bodyPtr, bodyLen) : undefined;
77
+ const body = bodyLen > 0 ? this.#view().subarray(bodyPtr, bodyPtr + bodyLen) : undefined;
78
78
  let headers = {};
79
79
  try {
80
80
  headers = JSON.parse(headersJson);
@@ -96,7 +96,8 @@ class ZxWasiBridge {
96
96
  }).then(async (res) => {
97
97
  if (timeout)
98
98
  clearTimeout(timeout);
99
- this.#notifyFetchComplete(fetchId, res.status, await res.text(), false);
99
+ const bytes = new Uint8Array(await res.arrayBuffer());
100
+ this.#notifyFetchComplete(fetchId, res.status, bytes, false);
100
101
  }).catch((err) => {
101
102
  if (timeout)
102
103
  clearTimeout(timeout);
@@ -105,7 +106,7 @@ class ZxWasiBridge {
105
106
  });
106
107
  }
107
108
  #notifyFetchComplete(fetchId, status, body, isError) {
108
- const encoded = encoder.encode(body);
109
+ const encoded = typeof body === "string" ? encoder.encode(body) : body;
109
110
  const ptr = this.#alloc(encoded.length);
110
111
  this.#writeBytes(ptr, encoded);
111
112
  this.#fetchCompleteHandler(fetchId, status, ptr, encoded.length, isError ? 1 : 0);
@@ -153,20 +154,48 @@ __export(exports_kv, {
153
154
  createMemoryKV: () => createMemoryKV,
154
155
  createKVImports: () => createKVImports
155
156
  });
157
+ function putOptions(ttlSeconds) {
158
+ if (ttlSeconds < 60)
159
+ return;
160
+ return { expirationTtl: ttlSeconds };
161
+ }
156
162
  function createMemoryKV() {
157
163
  const store = new Map;
164
+ function read(key) {
165
+ const entry = store.get(key);
166
+ if (!entry)
167
+ return null;
168
+ if (entry.expiresAt !== undefined && Date.now() >= entry.expiresAt) {
169
+ store.delete(key);
170
+ return null;
171
+ }
172
+ return entry.value;
173
+ }
174
+ function write(key, value, options) {
175
+ let expiresAt;
176
+ if (options?.expiration !== undefined) {
177
+ expiresAt = options.expiration * 1000;
178
+ } else if (options?.expirationTtl !== undefined) {
179
+ expiresAt = Date.now() + options.expirationTtl * 1000;
180
+ }
181
+ store.set(key, { value, expiresAt });
182
+ }
158
183
  return {
159
184
  async get(key) {
160
- return store.get(key) ?? null;
185
+ return read(key);
161
186
  },
162
- async put(key, value) {
163
- store.set(key, value);
187
+ async put(key, value, options) {
188
+ write(key, value, options);
164
189
  },
165
190
  async delete(key) {
166
191
  store.delete(key);
167
192
  },
168
193
  async list(options) {
169
- const keys = [...store.keys()].filter((k) => !options?.prefix || k.startsWith(options.prefix)).map((name) => ({ name }));
194
+ const keys = [...store.keys()].filter((k) => {
195
+ if (options?.prefix && !k.startsWith(options.prefix))
196
+ return false;
197
+ return read(k) !== null;
198
+ }).map((name) => ({ name }));
170
199
  return { keys };
171
200
  }
172
201
  };
@@ -206,11 +235,11 @@ function createKVImports(bindings, getMemory) {
206
235
  return -1;
207
236
  return writeBytes(buf_ptr, buf_max, encoder2.encode(value));
208
237
  },
209
- kv_put: (ns_ptr, ns_len, key_ptr, key_len, val_ptr, val_len) => {
238
+ kv_put: (ns_ptr, ns_len, key_ptr, key_len, val_ptr, val_len, ttl_seconds) => {
210
239
  const b = syncBinding(readStr(ns_ptr, ns_len));
211
240
  if (!b)
212
241
  return 0;
213
- b.putSync(readStr(key_ptr, key_len), readStr(val_ptr, val_len));
242
+ b.putSync(readStr(key_ptr, key_len), readStr(val_ptr, val_len), putOptions(ttl_seconds));
214
243
  return 0;
215
244
  },
216
245
  kv_delete: (ns_ptr, ns_len, key_ptr, key_len) => {
@@ -240,11 +269,11 @@ function createKVImports(bindings, getMemory) {
240
269
  return -1;
241
270
  return writeBytes(buf_ptr, buf_max, encoder2.encode(value));
242
271
  }),
243
- kv_put: new Suspending(async (ns_ptr, ns_len, key_ptr, key_len, val_ptr, val_len) => {
272
+ kv_put: new Suspending(async (ns_ptr, ns_len, key_ptr, key_len, val_ptr, val_len, ttl_seconds) => {
244
273
  const b = binding(readStr(ns_ptr, ns_len));
245
274
  if (!b)
246
275
  return -1;
247
- await b.put(readStr(key_ptr, key_len), readStr(val_ptr, val_len));
276
+ await b.put(readStr(key_ptr, key_len), readStr(val_ptr, val_len), putOptions(ttl_seconds));
248
277
  return 0;
249
278
  }),
250
279
  kv_delete: new Suspending(async (ns_ptr, ns_len, key_ptr, key_len) => {
@@ -265,6 +294,76 @@ function createKVImports(bindings, getMemory) {
265
294
  };
266
295
  }
267
296
 
297
+ // src/fetch.ts
298
+ function createFetchImports(getMemory) {
299
+ const encoder2 = new TextEncoder;
300
+ const decoder2 = new TextDecoder;
301
+ function readStr(ptr, len) {
302
+ return decoder2.decode(new Uint8Array(getMemory().buffer, ptr, len));
303
+ }
304
+ function writeBytes(buf_ptr, buf_max, data) {
305
+ if (data.length > buf_max)
306
+ return -2;
307
+ new Uint8Array(getMemory().buffer, buf_ptr, data.length).set(data);
308
+ return data.length;
309
+ }
310
+ function readBytes(ptr, len) {
311
+ return new Uint8Array(getMemory().buffer, ptr, len);
312
+ }
313
+ function parseHeaders(headersJson) {
314
+ let headers = {};
315
+ try {
316
+ headers = JSON.parse(headersJson);
317
+ } catch {
318
+ for (const line of headersJson.split(`
319
+ `)) {
320
+ const colonIdx = line.indexOf(":");
321
+ if (colonIdx > 0) {
322
+ headers[line.slice(0, colonIdx)] = line.slice(colonIdx + 1);
323
+ }
324
+ }
325
+ }
326
+ return headers;
327
+ }
328
+ async function doFetch(url_ptr, url_len, method_ptr, method_len, headers_ptr, headers_len, body_ptr, body_len, timeout_ms, status_out, buf_ptr, buf_max) {
329
+ const url = readStr(url_ptr, url_len);
330
+ const method = method_len > 0 ? readStr(method_ptr, method_len) : "GET";
331
+ const headersJson = headers_len > 0 ? readStr(headers_ptr, headers_len) : "{}";
332
+ const body = body_len > 0 ? readBytes(body_ptr, body_len) : undefined;
333
+ const headers = parseHeaders(headersJson);
334
+ const controller = new AbortController;
335
+ const timeout = timeout_ms > 0 ? setTimeout(() => controller.abort(), timeout_ms) : null;
336
+ try {
337
+ const response = await fetch(url, {
338
+ method,
339
+ headers: Object.keys(headers).length > 0 ? headers : undefined,
340
+ body: method !== "GET" && method !== "HEAD" ? body : undefined,
341
+ signal: controller.signal
342
+ });
343
+ if (timeout)
344
+ clearTimeout(timeout);
345
+ new DataView(getMemory().buffer).setUint16(status_out, response.status, true);
346
+ const bytes = new Uint8Array(await response.arrayBuffer());
347
+ return writeBytes(buf_ptr, buf_max, bytes);
348
+ } catch (err) {
349
+ if (timeout)
350
+ clearTimeout(timeout);
351
+ const msg = err instanceof Error ? err.message : String(err);
352
+ console.error(`wasm fetch failed: ${url} (${msg})`);
353
+ return -1;
354
+ }
355
+ }
356
+ const Suspending = WebAssembly.Suspending;
357
+ if (typeof Suspending !== "function") {
358
+ return {
359
+ fetch: (_url_ptr, _url_len, _method_ptr, _method_len, _headers_ptr, _headers_len, _body_ptr, _body_len, _timeout_ms, _status_out, _buf_ptr, _buf_max) => -1
360
+ };
361
+ }
362
+ return {
363
+ fetch: new Suspending(doFetch)
364
+ };
365
+ }
366
+
268
367
  // src/db.ts
269
368
  function decodeBlob(base64) {
270
369
  const binary = atob(base64);
@@ -430,7 +529,9 @@ function createWasiImports({
430
529
  "--method",
431
530
  request.method,
432
531
  "--search",
433
- url.search
532
+ url.search,
533
+ "--url",
534
+ url.href
434
535
  ];
435
536
  request.headers.forEach((value, name) => {
436
537
  if (value)
@@ -452,7 +553,7 @@ function createWasiImports({
452
553
  `);
453
554
  stderrPartial = lines.pop() ?? "";
454
555
  for (const line of lines) {
455
- if (line.startsWith("__EDGE_META__:")) {
556
+ if (line.startsWith("__ZIEX_META__:")) {
456
557
  stderrMeta += line + `
457
558
  `;
458
559
  } else if (line.length > 0) {
@@ -684,7 +785,7 @@ function createWasiImports({
684
785
  const remaining = stderrDecoder.decode(undefined, { stream: false });
685
786
  const tail = stderrPartial + remaining;
686
787
  if (tail.length > 0) {
687
- if (tail.startsWith("__EDGE_META__:"))
788
+ if (tail.startsWith("__ZIEX_META__:"))
688
789
  stderrMeta += tail;
689
790
  else
690
791
  console.error("[ziex]", tail);
@@ -816,9 +917,9 @@ function executeWasm(instance, jspi, Suspending, wsState) {
816
917
  }
817
918
  });
818
919
  }
819
- function parseEdgeMeta(stderrText) {
920
+ function parseZiexMeta(stderrText) {
820
921
  const meta = { status: 200, headers: new Headers, streaming: false };
821
- const metaPrefix = "__EDGE_META__:";
922
+ const metaPrefix = "__ZIEX_META__:";
822
923
  const metaLine = stderrText.split(`
823
924
  `).find((line) => line.startsWith(metaPrefix));
824
925
  if (metaLine) {
@@ -883,6 +984,7 @@ async function run({
883
984
  __zx_ws: buildWsImports(jspi ? Suspending : null, mem, new TextDecoder, wsState),
884
985
  __zx_kv: createKVImports(kvBindings ?? { default: createMemoryKV() }, mem),
885
986
  __zx_db: createD1Imports(dbBindings ?? {}, mem),
987
+ __zx_net: createFetchImports(mem),
886
988
  ...imports ? imports(mem) : {},
887
989
  ...ZxWasiBridge.createImportObject(bridgeRef)
888
990
  });
@@ -896,7 +998,7 @@ async function run({
896
998
  return new Response(null, { status: 101, webSocket: server.client });
897
999
  }
898
1000
  const { stderrText: earlyStderrText } = collectOutput();
899
- const earlyMeta = parseEdgeMeta(earlyStderrText);
1001
+ const earlyMeta = parseZiexMeta(earlyStderrText);
900
1002
  if (earlyMeta.streaming) {
901
1003
  const { readable, writable } = new TransformStream;
902
1004
  streamWriter = writable.getWriter();
@@ -908,9 +1010,14 @@ async function run({
908
1010
  }
909
1011
  await wasmPromise;
910
1012
  const { stderrText } = collectOutput();
911
- const meta = parseEdgeMeta(stderrText);
1013
+ const meta = parseZiexMeta(stderrText);
912
1014
  const body = mergeUint8Arrays(stdoutChunks);
913
1015
  meta.headers.delete("transfer-encoding");
1016
+ const nullBody = meta.status === 101 || meta.status === 204 || meta.status === 205 || meta.status === 304;
1017
+ if (nullBody) {
1018
+ meta.headers.delete("content-length");
1019
+ return new Response(null, { status: meta.status, headers: meta.headers });
1020
+ }
914
1021
  if (!meta.headers.has("content-length"))
915
1022
  meta.headers.set("content-length", String(body.byteLength));
916
1023
  return new Response(body.buffer, { status: meta.status, headers: meta.headers });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ziex",
3
- "version": "0.1.0-dev.1309",
3
+ "version": "0.1.0-dev.1367",
4
4
  "description": "Ziex is a framework for building web applications with Zig.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -37,7 +37,7 @@
37
37
  "license": "MIT",
38
38
  "scripts": {},
39
39
  "dependencies": {
40
- "@ziex/cli": "0.1.0-dev.1309"
40
+ "@ziex/cli": "0.1.0-dev.1367"
41
41
  },
42
42
  "peerDependenciesMeta": {
43
43
  "react": {
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Create a `fetch` import object for blocking WASM fetch via JSPI.
3
+ * When JSPI is unavailable the import always returns -1 (network error).
4
+ */
5
+ export declare function createFetchImports(getMemory: () => WebAssembly.Memory): Record<string, unknown>;