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.
- package/README.md +19 -25
- package/cloudflare/index.js +140 -22
- package/index.js +125 -18
- package/package.json +2 -2
- package/pkg/ziex/src/fetch.d.ts +5 -0
- package/{wasm → pkg/ziex/src/wasm}/core.d.ts +2 -2
- package/vendor/jsz/js/src/index.d.ts +1 -0
- package/vendor/jsz/js/src/zigjs.d.ts +107 -0
- package/wasm/index.js +120 -12
- package/wasm/init.dev.js +93 -8
- package/wasm/init.js +3 -2
- /package/{app.d.ts → pkg/ziex/src/app.d.ts} +0 -0
- /package/{aws-lambda → pkg/ziex/src/aws-lambda}/index.d.ts +0 -0
- /package/{browser → pkg/ziex/src/browser}/kv.d.ts +0 -0
- /package/{cloudflare → pkg/ziex/src/cloudflare}/app.d.ts +0 -0
- /package/{cloudflare → pkg/ziex/src/cloudflare}/db.d.ts +0 -0
- /package/{cloudflare → pkg/ziex/src/cloudflare}/do.d.ts +0 -0
- /package/{cloudflare → pkg/ziex/src/cloudflare}/index.d.ts +0 -0
- /package/{cloudflare → pkg/ziex/src/cloudflare}/kv.d.ts +0 -0
- /package/{cloudflare → pkg/ziex/src/cloudflare}/worker.d.ts +0 -0
- /package/{db.d.ts → pkg/ziex/src/db.d.ts} +0 -0
- /package/{index.d.ts → pkg/ziex/src/index.d.ts} +0 -0
- /package/{kv.d.ts → pkg/ziex/src/kv.d.ts} +0 -0
- /package/{react → pkg/ziex/src/react}/dom.d.ts +0 -0
- /package/{react → pkg/ziex/src/react}/index.d.ts +0 -0
- /package/{react → pkg/ziex/src/react}/types.d.ts +0 -0
- /package/{runtime.d.ts → pkg/ziex/src/runtime.d.ts} +0 -0
- /package/{vercel → pkg/ziex/src/vercel}/index.d.ts +0 -0
- /package/{wasi.d.ts → pkg/ziex/src/wasi.d.ts} +0 -0
- /package/{wasm → pkg/ziex/src/wasm}/dom.d.ts +0 -0
- /package/{wasm → pkg/ziex/src/wasm}/index.d.ts +0 -0
- /package/{wasm → pkg/ziex/src/wasm}/init.d.ts +0 -0
- /package/{wasm → pkg/ziex/src/wasm}/types.d.ts +0 -0
- /package/{wasm → pkg/ziex/src/wasm}/wasi.d.ts +0 -0
- /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
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
|
package/cloudflare/index.js
CHANGED
|
@@ -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 ?
|
|
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
|
|
319
|
-
this._notifyFetchComplete(fetchId, response.status,
|
|
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
|
|
514
|
+
return read(key);
|
|
416
515
|
},
|
|
417
|
-
async put(key, value) {
|
|
418
|
-
|
|
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) =>
|
|
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.#
|
|
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
|
-
|
|
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("
|
|
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("
|
|
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
|
|
2063
|
+
function parseZiexMeta(stderrText) {
|
|
1953
2064
|
const meta = { status: 200, headers: new Headers, streaming: false };
|
|
1954
|
-
const metaPrefix = "
|
|
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 =
|
|
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 =
|
|
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.#
|
|
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
|
-
|
|
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
|
|
185
|
+
return read(key);
|
|
161
186
|
},
|
|
162
|
-
async put(key, value) {
|
|
163
|
-
|
|
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) =>
|
|
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("
|
|
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("
|
|
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
|
|
920
|
+
function parseZiexMeta(stderrText) {
|
|
820
921
|
const meta = { status: 200, headers: new Headers, streaming: false };
|
|
821
|
-
const metaPrefix = "
|
|
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 =
|
|
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 =
|
|
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.
|
|
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.
|
|
40
|
+
"@ziex/cli": "0.1.0-dev.1367"
|
|
41
41
|
},
|
|
42
42
|
"peerDependenciesMeta": {
|
|
43
43
|
"react": {
|