ziex 0.1.0-dev.966 → 0.1.0-dev.987

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/index.js CHANGED
@@ -171,6 +171,10 @@ function createMemoryKV() {
171
171
  }
172
172
  };
173
173
  }
174
+ function isSyncKVNamespace(binding) {
175
+ const candidate = binding;
176
+ return typeof candidate.getSync === "function" && typeof candidate.putSync === "function" && typeof candidate.deleteSync === "function" && typeof candidate.listSync === "function";
177
+ }
174
178
  function createKVImports(bindings, getMemory) {
175
179
  const encoder2 = new TextEncoder;
176
180
  const decoder2 = new TextDecoder;
@@ -188,11 +192,42 @@ function createKVImports(bindings, getMemory) {
188
192
  }
189
193
  const Suspending = WebAssembly.Suspending;
190
194
  if (typeof Suspending !== "function") {
195
+ let syncBinding = function(ns) {
196
+ const candidate = binding(ns);
197
+ return candidate && isSyncKVNamespace(candidate) ? candidate : null;
198
+ };
191
199
  return {
192
- kv_get: (_ns, _nsLen, _key, _keyLen, _buf, _max) => -1,
193
- kv_put: (_ns, _nsLen, _key, _keyLen, _val, _valLen) => 0,
194
- kv_delete: (_ns, _nsLen, _key, _keyLen) => 0,
195
- kv_list: (_ns, _nsLen, _pfx, _pfxLen, buf_ptr, buf_max) => writeBytes(buf_ptr, buf_max, encoder2.encode("[]"))
200
+ kv_get: (ns_ptr, ns_len, key_ptr, key_len, buf_ptr, buf_max) => {
201
+ const b = syncBinding(readStr(ns_ptr, ns_len));
202
+ if (!b)
203
+ return -1;
204
+ const value = b.getSync(readStr(key_ptr, key_len));
205
+ if (value === null)
206
+ return -1;
207
+ return writeBytes(buf_ptr, buf_max, encoder2.encode(value));
208
+ },
209
+ kv_put: (ns_ptr, ns_len, key_ptr, key_len, val_ptr, val_len) => {
210
+ const b = syncBinding(readStr(ns_ptr, ns_len));
211
+ if (!b)
212
+ return 0;
213
+ b.putSync(readStr(key_ptr, key_len), readStr(val_ptr, val_len));
214
+ return 0;
215
+ },
216
+ kv_delete: (ns_ptr, ns_len, key_ptr, key_len) => {
217
+ const b = syncBinding(readStr(ns_ptr, ns_len));
218
+ if (!b)
219
+ return 0;
220
+ b.deleteSync(readStr(key_ptr, key_len));
221
+ return 0;
222
+ },
223
+ kv_list: (ns_ptr, ns_len, pfx_ptr, pfx_len, buf_ptr, buf_max) => {
224
+ const b = syncBinding(readStr(ns_ptr, ns_len));
225
+ if (!b)
226
+ return writeBytes(buf_ptr, buf_max, encoder2.encode("[]"));
227
+ const prefix = readStr(pfx_ptr, pfx_len);
228
+ const result = b.listSync(prefix.length > 0 ? { prefix } : undefined);
229
+ return writeBytes(buf_ptr, buf_max, encoder2.encode(JSON.stringify(result.keys.map((k) => k.name))));
230
+ }
196
231
  };
197
232
  }
198
233
  return {
@@ -801,18 +836,20 @@ async function run({
801
836
  ctx?.waitUntil(wasmPromise);
802
837
  return new Response(null, { status: 101, webSocket: server.client });
803
838
  }
804
- const { stderrText } = collectOutput();
805
- const meta = parseEdgeMeta(stderrText);
806
- if (meta.streaming) {
839
+ const { stderrText: earlyStderrText } = collectOutput();
840
+ const earlyMeta = parseEdgeMeta(earlyStderrText);
841
+ if (earlyMeta.streaming) {
807
842
  const { readable, writable } = new TransformStream;
808
843
  streamWriter = writable.getWriter();
809
844
  for (const chunk of stdoutChunks)
810
845
  streamWriter.write(chunk);
811
846
  stdoutChunks.length = 0;
812
847
  wasmPromise.finally(() => streamWriter?.close());
813
- return new Response(readable, { status: meta.status, headers: meta.headers });
848
+ return new Response(readable, { status: earlyMeta.status, headers: earlyMeta.headers });
814
849
  }
815
850
  await wasmPromise;
851
+ const { stderrText } = collectOutput();
852
+ const meta = parseEdgeMeta(stderrText);
816
853
  const body = mergeUint8Arrays(stdoutChunks);
817
854
  meta.headers.delete("transfer-encoding");
818
855
  if (!meta.headers.has("content-length"))
package/kv.d.ts CHANGED
@@ -13,6 +13,21 @@ export interface KVNamespace {
13
13
  }[];
14
14
  }>;
15
15
  }
16
+ export interface SyncKVNamespace extends KVNamespace {
17
+ getSync(key: string): string | null;
18
+ putSync(key: string, value: string, options?: {
19
+ expiration?: number;
20
+ expirationTtl?: number;
21
+ }): void;
22
+ deleteSync(key: string): void;
23
+ listSync(options?: {
24
+ prefix?: string;
25
+ }): {
26
+ keys: {
27
+ name: string;
28
+ }[];
29
+ };
30
+ }
16
31
  /**
17
32
  * In-memory KV namespace. Used as the default shim on platforms that don't
18
33
  * provide a real KV binding (e.g. Vercel). Data lives only for the lifetime
@@ -21,7 +36,7 @@ export interface KVNamespace {
21
36
  export declare function createMemoryKV(): KVNamespace;
22
37
  /**
23
38
  * Create a `__zx_kv` import object for use with `run({ kv: ... })`.
24
- * Always returns a valid import object. When JSPI is unavailable all KV
25
- * operations are stubbed (get → not-found, put/delete success, list []).
39
+ * Always returns a valid import object. When JSPI is unavailable it uses
40
+ * synchronous bindings when available, otherwise falls back to stubbed no-ops.
26
41
  */
27
42
  export declare function createKVImports(bindings: Record<string, KVNamespace>, getMemory: () => WebAssembly.Memory): Record<string, unknown>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ziex",
3
- "version": "0.1.0-dev.966",
3
+ "version": "0.1.0-dev.987",
4
4
  "description": "ZX 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.966"
40
+ "@ziex/cli": "0.1.0-dev.987"
41
41
  },
42
42
  "peerDependenciesMeta": {
43
43
  "react": {
package/wasm/core.d.ts CHANGED
@@ -32,6 +32,8 @@ export declare function getMemoryView(): Uint8Array;
32
32
  export declare function readString(ptr: number, len: number): string;
33
33
  /** Write bytes to WASM memory at a specific location */
34
34
  export declare function writeBytes(ptr: number, data: Uint8Array): void;
35
+ export declare function wrapPromisingExport<F extends (...args: any[]) => any>(fn: F | undefined): F | undefined;
36
+ export declare function invokeWasmExport<F extends (...args: any[]) => any>(fn: F | undefined, ...args: Parameters<F>): void;
35
37
  /**
36
38
  * Core ZX Bridge — works in both browser and edge runtimes.
37
39
  * Contains fetch, timers, and logging. No DOM or browser-WebSocket references.
package/wasm/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { CallbackType, jsz, storeValueGetRef, textDecoder, textEncoder, getMemoryView, readString, writeBytes, ZxBridgeCore, } from "./core";
2
2
  export type { CallbackTypeValue } from "./core";
3
3
  import { ZxBridgeCore } from "./core";
4
+ import { type KVNamespace } from "../kv";
4
5
  /**
5
6
  * Browser ZX Bridge — extends ZxBridgeCore with DOM, WebSocket, and form-action support.
6
7
  * Import this from environments that have access to browser globals.
@@ -9,6 +10,9 @@ import { ZxBridgeCore } from "./core";
9
10
  export declare class ZxBridge extends ZxBridgeCore {
10
11
  #private;
11
12
  constructor(exports: WebAssembly.Exports);
13
+ eventMaySuspend(velementId: bigint, eventTypeId: number): boolean;
14
+ setEventHandlerMode(velementId: bigint, eventTypeId: number, maySuspend: boolean): void;
15
+ clearEventHandlerModes(velementId: bigint): void;
12
16
  /** Submit a form action with bound-state round-trip. */
13
17
  submitFormActionAsync(form: HTMLFormElement, statesJson: string, fetchId: bigint): void;
14
18
  /**
@@ -34,6 +38,19 @@ export type InitOptions = {
34
38
  url?: string;
35
39
  eventDelegationRoot?: string;
36
40
  importObject?: WebAssembly.Imports;
41
+ kv?: Record<string, KVNamespace>;
42
+ };
43
+ type ZiexDevtoolsHook = {
44
+ location: {
45
+ href: string;
46
+ origin: string;
47
+ host: string;
48
+ pathname: string;
49
+ };
50
+ reinit: () => Promise<{
51
+ source: WebAssembly.WebAssemblyInstantiatedSource;
52
+ bridge: ZxBridge;
53
+ }>;
37
54
  };
38
55
  /** Initialize WASM with the ZX Bridge */
39
56
  export declare function init(options?: InitOptions): Promise<{
@@ -49,5 +66,6 @@ declare global {
49
66
  source: WebAssembly.WebAssemblyInstantiatedSource;
50
67
  bridge: ZxBridge;
51
68
  }>;
69
+ __ZIEX_DEVTOOLS_GLOBAL_HOOK__?: ZiexDevtoolsHook;
52
70
  }
53
71
  }
package/wasm/index.js CHANGED
@@ -232,6 +232,24 @@ function readString(ptr, len) {
232
232
  function writeBytes(ptr, data) {
233
233
  getMemoryView().set(data, ptr);
234
234
  }
235
+ function wrapPromisingExport(fn) {
236
+ if (!fn)
237
+ return;
238
+ const promising = WebAssembly.promising;
239
+ if (typeof promising !== "function")
240
+ return fn;
241
+ return promising(fn);
242
+ }
243
+ function invokeWasmExport(fn, ...args) {
244
+ if (!fn)
245
+ return;
246
+ const result = fn(...args);
247
+ if (result && typeof result.then === "function") {
248
+ result.then(undefined, (error) => {
249
+ console.error(error);
250
+ });
251
+ }
252
+ }
235
253
 
236
254
  class ZxBridgeCore {
237
255
  #intervals = new Map;
@@ -240,8 +258,12 @@ class ZxBridgeCore {
240
258
  #fetchCompleteHandler;
241
259
  constructor(exports) {
242
260
  this._alloc = exports.__zx_alloc;
243
- this.#handler = exports.__zx_cb;
244
- this.#fetchCompleteHandler = exports.__zx_fetch_complete;
261
+ this.#handler = wrapPromisingExport(exports.__zx_cb);
262
+ const fetchCompleteHandler = wrapPromisingExport(exports.__zx_fetch_complete);
263
+ if (!fetchCompleteHandler) {
264
+ throw new Error("__zx_fetch_complete not exported from WASM");
265
+ }
266
+ this.#fetchCompleteHandler = fetchCompleteHandler;
245
267
  if (exports.memory)
246
268
  jsz.memory = exports.memory;
247
269
  }
@@ -252,7 +274,7 @@ class ZxBridgeCore {
252
274
  return;
253
275
  }
254
276
  const dataRef = storeValueGetRef(data);
255
- handler(type, id, dataRef);
277
+ invokeWasmExport(handler, type, id, dataRef);
256
278
  }
257
279
  fetchAsync(urlPtr, urlLen, methodPtr, methodLen, headersPtr, headersLen, bodyPtr, bodyLen, timeoutMs, fetchId) {
258
280
  const url = readString(urlPtr, urlLen);
@@ -297,7 +319,7 @@ class ZxBridgeCore {
297
319
  const encoded = textEncoder.encode(body);
298
320
  const ptr = this._alloc(encoded.length);
299
321
  writeBytes(ptr, encoded);
300
- handler(fetchId, statusCode, ptr, encoded.length, isError ? 1 : 0);
322
+ invokeWasmExport(handler, fetchId, statusCode, ptr, encoded.length, isError ? 1 : 0);
301
323
  }
302
324
  setTimeout(callbackId, delayMs) {
303
325
  setTimeout(() => {
@@ -369,6 +391,253 @@ class ZxBridgeCore {
369
391
  };
370
392
  }
371
393
  }
394
+ // src/kv.ts
395
+ var exports_kv = {};
396
+ __export(exports_kv, {
397
+ createMemoryKV: () => createMemoryKV,
398
+ createKVImports: () => createKVImports
399
+ });
400
+ function createMemoryKV() {
401
+ const store = new Map;
402
+ return {
403
+ async get(key) {
404
+ return store.get(key) ?? null;
405
+ },
406
+ async put(key, value) {
407
+ store.set(key, value);
408
+ },
409
+ async delete(key) {
410
+ store.delete(key);
411
+ },
412
+ async list(options) {
413
+ const keys = [...store.keys()].filter((k) => !options?.prefix || k.startsWith(options.prefix)).map((name) => ({ name }));
414
+ return { keys };
415
+ }
416
+ };
417
+ }
418
+ function isSyncKVNamespace(binding) {
419
+ const candidate = binding;
420
+ return typeof candidate.getSync === "function" && typeof candidate.putSync === "function" && typeof candidate.deleteSync === "function" && typeof candidate.listSync === "function";
421
+ }
422
+ function createKVImports(bindings, getMemory) {
423
+ const encoder2 = new TextEncoder;
424
+ const decoder2 = new TextDecoder;
425
+ function readStr(ptr, len) {
426
+ return decoder2.decode(new Uint8Array(getMemory().buffer, ptr, len));
427
+ }
428
+ function writeBytes2(buf_ptr, buf_max, data) {
429
+ if (data.length > buf_max)
430
+ return -2;
431
+ new Uint8Array(getMemory().buffer, buf_ptr, data.length).set(data);
432
+ return data.length;
433
+ }
434
+ function binding(ns) {
435
+ return bindings[ns] ?? bindings["default"] ?? null;
436
+ }
437
+ const Suspending = WebAssembly.Suspending;
438
+ if (typeof Suspending !== "function") {
439
+ let syncBinding = function(ns) {
440
+ const candidate = binding(ns);
441
+ return candidate && isSyncKVNamespace(candidate) ? candidate : null;
442
+ };
443
+ return {
444
+ kv_get: (ns_ptr, ns_len, key_ptr, key_len, buf_ptr, buf_max) => {
445
+ const b = syncBinding(readStr(ns_ptr, ns_len));
446
+ if (!b)
447
+ return -1;
448
+ const value = b.getSync(readStr(key_ptr, key_len));
449
+ if (value === null)
450
+ return -1;
451
+ return writeBytes2(buf_ptr, buf_max, encoder2.encode(value));
452
+ },
453
+ kv_put: (ns_ptr, ns_len, key_ptr, key_len, val_ptr, val_len) => {
454
+ const b = syncBinding(readStr(ns_ptr, ns_len));
455
+ if (!b)
456
+ return 0;
457
+ b.putSync(readStr(key_ptr, key_len), readStr(val_ptr, val_len));
458
+ return 0;
459
+ },
460
+ kv_delete: (ns_ptr, ns_len, key_ptr, key_len) => {
461
+ const b = syncBinding(readStr(ns_ptr, ns_len));
462
+ if (!b)
463
+ return 0;
464
+ b.deleteSync(readStr(key_ptr, key_len));
465
+ return 0;
466
+ },
467
+ kv_list: (ns_ptr, ns_len, pfx_ptr, pfx_len, buf_ptr, buf_max) => {
468
+ const b = syncBinding(readStr(ns_ptr, ns_len));
469
+ if (!b)
470
+ return writeBytes2(buf_ptr, buf_max, encoder2.encode("[]"));
471
+ const prefix = readStr(pfx_ptr, pfx_len);
472
+ const result = b.listSync(prefix.length > 0 ? { prefix } : undefined);
473
+ return writeBytes2(buf_ptr, buf_max, encoder2.encode(JSON.stringify(result.keys.map((k) => k.name))));
474
+ }
475
+ };
476
+ }
477
+ return {
478
+ kv_get: new Suspending(async (ns_ptr, ns_len, key_ptr, key_len, buf_ptr, buf_max) => {
479
+ const b = binding(readStr(ns_ptr, ns_len));
480
+ if (!b)
481
+ return -1;
482
+ const value = await b.get(readStr(key_ptr, key_len));
483
+ if (value === null)
484
+ return -1;
485
+ return writeBytes2(buf_ptr, buf_max, encoder2.encode(value));
486
+ }),
487
+ kv_put: new Suspending(async (ns_ptr, ns_len, key_ptr, key_len, val_ptr, val_len) => {
488
+ const b = binding(readStr(ns_ptr, ns_len));
489
+ if (!b)
490
+ return -1;
491
+ await b.put(readStr(key_ptr, key_len), readStr(val_ptr, val_len));
492
+ return 0;
493
+ }),
494
+ kv_delete: new Suspending(async (ns_ptr, ns_len, key_ptr, key_len) => {
495
+ const b = binding(readStr(ns_ptr, ns_len));
496
+ if (!b)
497
+ return -1;
498
+ await b.delete(readStr(key_ptr, key_len));
499
+ return 0;
500
+ }),
501
+ kv_list: new Suspending(async (ns_ptr, ns_len, prefix_ptr, prefix_len, buf_ptr, buf_max) => {
502
+ const b = binding(readStr(ns_ptr, ns_len));
503
+ if (!b)
504
+ return writeBytes2(buf_ptr, buf_max, encoder2.encode("[]"));
505
+ const prefix = readStr(prefix_ptr, prefix_len);
506
+ const result = await b.list(prefix.length > 0 ? { prefix } : undefined);
507
+ return writeBytes2(buf_ptr, buf_max, encoder2.encode(JSON.stringify(result.keys.map((k) => k.name))));
508
+ })
509
+ };
510
+ }
511
+
512
+ // src/browser/kv.ts
513
+ function getIndexedDb() {
514
+ if (typeof indexedDB === "undefined") {
515
+ throw new Error("IndexedDB is not available in this environment");
516
+ }
517
+ return indexedDB;
518
+ }
519
+ function requestToPromise(request) {
520
+ return new Promise((resolve, reject) => {
521
+ request.onsuccess = () => resolve(request.result);
522
+ request.onerror = () => reject(request.error ?? new Error("IndexedDB request failed"));
523
+ });
524
+ }
525
+ function transactionToPromise(transaction) {
526
+ return new Promise((resolve, reject) => {
527
+ transaction.oncomplete = () => resolve();
528
+ transaction.onabort = () => reject(transaction.error ?? new Error("IndexedDB transaction aborted"));
529
+ transaction.onerror = () => reject(transaction.error ?? new Error("IndexedDB transaction failed"));
530
+ });
531
+ }
532
+ function createIndexedDbKV(options = {}) {
533
+ const databaseName = options.databaseName ?? "ziex-kv";
534
+ const storeName = options.storeName ?? "kv";
535
+ const namespace = options.namespace ?? "default";
536
+ const dbPromise = new Promise((resolve, reject) => {
537
+ const request = getIndexedDb().open(databaseName, 1);
538
+ request.onupgradeneeded = () => {
539
+ const db = request.result;
540
+ if (!db.objectStoreNames.contains(storeName)) {
541
+ db.createObjectStore(storeName);
542
+ }
543
+ };
544
+ request.onsuccess = () => resolve(request.result);
545
+ request.onerror = () => reject(request.error ?? new Error("Failed to open IndexedDB"));
546
+ });
547
+ const scopedKey = (key) => `${namespace}:${key}`;
548
+ return {
549
+ async get(key) {
550
+ const db = await dbPromise;
551
+ const tx = db.transaction(storeName, "readonly");
552
+ const store = tx.objectStore(storeName);
553
+ const value = await requestToPromise(store.get(scopedKey(key)));
554
+ await transactionToPromise(tx);
555
+ console.debug(`KV GET - Key: ${key}, Value: ${value}`);
556
+ return typeof value === "string" ? value : null;
557
+ },
558
+ async put(key, value) {
559
+ const db = await dbPromise;
560
+ const tx = db.transaction(storeName, "readwrite");
561
+ tx.objectStore(storeName).put(value, scopedKey(key));
562
+ await transactionToPromise(tx);
563
+ console.debug(`KV PUT - Key: ${key}, Value: ${value}`);
564
+ },
565
+ async delete(key) {
566
+ const db = await dbPromise;
567
+ const tx = db.transaction(storeName, "readwrite");
568
+ tx.objectStore(storeName).delete(scopedKey(key));
569
+ await transactionToPromise(tx);
570
+ },
571
+ async list(options2) {
572
+ const db = await dbPromise;
573
+ const tx = db.transaction(storeName, "readonly");
574
+ const store = tx.objectStore(storeName);
575
+ const keys = await requestToPromise(store.getAllKeys());
576
+ await transactionToPromise(tx);
577
+ const prefix = scopedKey(options2?.prefix ?? "");
578
+ return {
579
+ keys: keys.filter((key) => typeof key === "string" && key.startsWith(prefix)).map((key) => ({ name: key.slice(namespace.length + 1) }))
580
+ };
581
+ }
582
+ };
583
+ }
584
+ function getLocalStorage() {
585
+ if (typeof localStorage === "undefined") {
586
+ throw new Error("localStorage is not available in this environment");
587
+ }
588
+ return localStorage;
589
+ }
590
+ function createLocalStorageKV(options = {}) {
591
+ const storage = getLocalStorage();
592
+ const namespace = options.namespace ?? "default";
593
+ const storagePrefix = options.storagePrefix ?? "ziex-kv";
594
+ const scopedKey = (key) => `${storagePrefix}:${namespace}:${key}`;
595
+ const namespacePrefix = scopedKey("");
596
+ return {
597
+ getSync(key) {
598
+ return storage.getItem(scopedKey(key));
599
+ },
600
+ async get(key) {
601
+ return this.getSync(key);
602
+ },
603
+ putSync(key, value) {
604
+ storage.setItem(scopedKey(key), value);
605
+ },
606
+ async put(key, value) {
607
+ this.putSync(key, value);
608
+ },
609
+ deleteSync(key) {
610
+ storage.removeItem(scopedKey(key));
611
+ },
612
+ async delete(key) {
613
+ this.deleteSync(key);
614
+ },
615
+ listSync(options2) {
616
+ const prefix = namespacePrefix + (options2?.prefix ?? "");
617
+ const keys = [];
618
+ for (let i = 0;i < storage.length; i += 1) {
619
+ const key = storage.key(i);
620
+ if (!key || !key.startsWith(prefix))
621
+ continue;
622
+ keys.push({ name: key.slice(namespacePrefix.length) });
623
+ }
624
+ return { keys };
625
+ },
626
+ async list(options2) {
627
+ return this.listSync(options2);
628
+ }
629
+ };
630
+ }
631
+ function hasJSPI() {
632
+ return typeof WebAssembly.Suspending === "function" && typeof WebAssembly.promising === "function";
633
+ }
634
+ function createBrowserKVBindings(options = {}) {
635
+ const namespace = options.namespace ?? "default";
636
+ return {
637
+ [namespace]: hasJSPI() ? createIndexedDbKV(options) : createLocalStorageKV(options)
638
+ };
639
+ }
640
+
372
641
  // src/wasm/index.ts
373
642
  class ZxBridge extends ZxBridgeCore {
374
643
  #websockets = new Map;
@@ -377,13 +646,30 @@ class ZxBridge extends ZxBridgeCore {
377
646
  #wsOnErrorHandler;
378
647
  #wsOnCloseHandler;
379
648
  #eventbridge;
649
+ #eventbridgeAsync;
380
650
  constructor(exports) {
381
651
  super(exports);
382
- this.#wsOnOpenHandler = exports.__zx_ws_onopen;
383
- this.#wsOnMessageHandler = exports.__zx_ws_onmessage;
384
- this.#wsOnErrorHandler = exports.__zx_ws_onerror;
385
- this.#wsOnCloseHandler = exports.__zx_ws_onclose;
652
+ this.#wsOnOpenHandler = wrapPromisingExport(exports.__zx_ws_onopen);
653
+ this.#wsOnMessageHandler = wrapPromisingExport(exports.__zx_ws_onmessage);
654
+ this.#wsOnErrorHandler = wrapPromisingExport(exports.__zx_ws_onerror);
655
+ this.#wsOnCloseHandler = wrapPromisingExport(exports.__zx_ws_onclose);
386
656
  this.#eventbridge = exports.__zx_eventbridge;
657
+ this.#eventbridgeAsync = wrapPromisingExport(exports.__zx_eventbridge_async ?? exports.__zx_eventbridge);
658
+ }
659
+ eventMaySuspend(velementId, eventTypeId) {
660
+ return !!((eventHandlerModes.get(velementId) ?? 0) & 1 << eventTypeId);
661
+ }
662
+ setEventHandlerMode(velementId, eventTypeId, maySuspend) {
663
+ const bit = 1 << eventTypeId;
664
+ const current = eventHandlerModes.get(velementId) ?? 0;
665
+ const next = maySuspend ? current | bit : current & ~bit;
666
+ if (next === 0)
667
+ eventHandlerModes.delete(velementId);
668
+ else
669
+ eventHandlerModes.set(velementId, next);
670
+ }
671
+ clearEventHandlerModes(velementId) {
672
+ eventHandlerModes.delete(velementId);
387
673
  }
388
674
  submitFormActionAsync(form, statesJson, fetchId) {
389
675
  const formData = new FormData(form);
@@ -413,7 +699,7 @@ class ZxBridge extends ZxBridgeCore {
413
699
  return;
414
700
  const protocol = ws.protocol || "";
415
701
  const { ptr, len } = this._writeStringToWasm(protocol);
416
- handler(wsId, ptr, len);
702
+ invokeWasmExport(handler, wsId, ptr, len);
417
703
  };
418
704
  ws.onmessage = (event) => {
419
705
  const handler = this.#wsOnMessageHandler;
@@ -422,14 +708,14 @@ class ZxBridge extends ZxBridgeCore {
422
708
  const isBinary = event.data instanceof ArrayBuffer;
423
709
  const data = isBinary ? new Uint8Array(event.data) : textEncoder.encode(event.data);
424
710
  const { ptr, len } = this._writeBytesToWasm(data);
425
- handler(wsId, ptr, len, isBinary ? 1 : 0);
711
+ invokeWasmExport(handler, wsId, ptr, len, isBinary ? 1 : 0);
426
712
  };
427
713
  ws.onerror = (_event) => {
428
714
  const handler = this.#wsOnErrorHandler;
429
715
  if (!handler)
430
716
  return;
431
717
  const { ptr, len } = this._writeStringToWasm("WebSocket error");
432
- handler(wsId, ptr, len);
718
+ invokeWasmExport(handler, wsId, ptr, len);
433
719
  };
434
720
  ws.onclose = (event) => {
435
721
  const handler = this.#wsOnCloseHandler;
@@ -437,7 +723,7 @@ class ZxBridge extends ZxBridgeCore {
437
723
  return;
438
724
  const reason = event.reason || "";
439
725
  const { ptr, len } = this._writeStringToWasm(reason);
440
- handler(wsId, event.code, ptr, len, event.wasClean ? 1 : 0);
726
+ invokeWasmExport(handler, wsId, event.code, ptr, len, event.wasClean ? 1 : 0);
441
727
  this.#websockets.delete(wsId);
442
728
  };
443
729
  this.#websockets.set(wsId, ws);
@@ -446,7 +732,7 @@ class ZxBridge extends ZxBridgeCore {
446
732
  if (handler) {
447
733
  const msg = error instanceof Error ? error.message : "WebSocket connection failed";
448
734
  const { ptr, len } = this._writeStringToWasm(msg);
449
- handler(wsId, ptr, len);
735
+ invokeWasmExport(handler, wsId, ptr, len);
450
736
  }
451
737
  }
452
738
  }
@@ -477,6 +763,7 @@ class ZxBridge extends ZxBridgeCore {
477
763
  }
478
764
  dispose() {
479
765
  super.dispose();
766
+ eventHandlerModes.clear();
480
767
  for (const ws of this.#websockets.values()) {
481
768
  try {
482
769
  ws.close();
@@ -485,16 +772,24 @@ class ZxBridge extends ZxBridgeCore {
485
772
  this.#websockets.clear();
486
773
  }
487
774
  eventbridge(velementId, eventTypeId, event) {
488
- if (!this.#eventbridge)
489
- return;
490
775
  const eventRef = storeValueGetRef(event);
491
- this.#eventbridge(velementId, eventTypeId, eventRef);
776
+ if (this.eventMaySuspend(velementId, eventTypeId)) {
777
+ invokeWasmExport(this.#eventbridgeAsync, velementId, eventTypeId, eventRef);
778
+ return;
779
+ }
780
+ invokeWasmExport(this.#eventbridge, velementId, eventTypeId, eventRef);
492
781
  }
493
782
  static createImportObject(bridgeRef) {
494
783
  return {
495
784
  ...jsz.importObject(),
496
785
  __zx: {
497
786
  _log: (level, ptr, len) => ZxBridgeCore.log(level, ptr, len),
787
+ _setEventHandlerMode: (vnodeId, eventTypeId, maySuspend) => {
788
+ bridgeRef.current?.setEventHandlerMode(vnodeId, eventTypeId, maySuspend !== 0);
789
+ },
790
+ _clearEventHandlerModes: (vnodeId) => {
791
+ bridgeRef.current?.clearEventHandlerModes(vnodeId);
792
+ },
498
793
  _fetchAsync: (urlPtr, urlLen, methodPtr, methodLen, headersPtr, headersLen, bodyPtr, bodyLen, timeoutMs, fetchId) => {
499
794
  bridgeRef.current?.fetchAsync(urlPtr, urlLen, methodPtr, methodLen, headersPtr, headersLen, bodyPtr, bodyLen, timeoutMs, fetchId);
500
795
  },
@@ -863,6 +1158,7 @@ var EVENT_TYPE_MAP = {
863
1158
  touchmove: 17,
864
1159
  scroll: 18
865
1160
  };
1161
+ var eventHandlerModes = new Map;
866
1162
  function initEventDelegation(bridge, rootSelector = "body") {
867
1163
  const root = document.querySelector(rootSelector);
868
1164
  if (!root)
@@ -892,17 +1188,31 @@ function initEventDelegation(bridge, rootSelector = "body") {
892
1188
  }
893
1189
  var DEFAULT_URL = "/assets/_/main.wasm";
894
1190
  var activeRuntime = null;
1191
+ function buildDevtoolsLocation() {
1192
+ return {
1193
+ href: window.location.href,
1194
+ origin: window.location.origin,
1195
+ host: window.location.host,
1196
+ pathname: window.location.pathname
1197
+ };
1198
+ }
895
1199
  function normalizeOptions(options = {}) {
896
1200
  return {
897
1201
  url: options.url,
898
1202
  eventDelegationRoot: options.eventDelegationRoot,
899
- importObject: options.importObject
1203
+ importObject: options.importObject,
1204
+ kv: options.kv
900
1205
  };
901
1206
  }
902
1207
  function registerDevReinit(options) {
903
1208
  if (typeof window === "undefined")
904
1209
  return;
905
- window.__zx_dev_reinit = () => init(options);
1210
+ const reinit = () => init(options);
1211
+ window.__zx_dev_reinit = reinit;
1212
+ window.__ZIEX_DEVTOOLS_GLOBAL_HOOK__ = {
1213
+ location: buildDevtoolsLocation(),
1214
+ reinit
1215
+ };
906
1216
  }
907
1217
  async function init(options = {}) {
908
1218
  const normalizedOptions = normalizeOptions(options);
@@ -912,17 +1222,28 @@ async function init(options = {}) {
912
1222
  }
913
1223
  const url = options.url ?? document.getElementById("__$wasmlink")?.href ?? DEFAULT_URL;
914
1224
  const bridgeRef = { current: null };
915
- const importObject = Object.assign({}, ZxBridge.createImportObject(bridgeRef), options.importObject);
1225
+ let wasmMemory = null;
1226
+ const kvBindings = options.kv ?? createBrowserKVBindings();
1227
+ const kvImportObject = {
1228
+ __zx_kv: createKVImports(kvBindings, () => {
1229
+ if (wasmMemory)
1230
+ return wasmMemory;
1231
+ if (jsz.memory)
1232
+ return jsz.memory;
1233
+ throw new Error("WASM memory is not ready");
1234
+ })
1235
+ };
1236
+ const importObject = Object.assign({}, ZxBridge.createImportObject(bridgeRef), kvImportObject, options.importObject);
916
1237
  const source = await WebAssembly.instantiateStreaming(fetch(url), importObject);
917
1238
  const { instance } = source;
918
- jsz.memory = instance.exports.memory;
1239
+ wasmMemory = instance.exports.memory;
1240
+ jsz.memory = wasmMemory;
919
1241
  const bridge = new ZxBridge(instance.exports);
920
1242
  bridgeRef.current = bridge;
921
1243
  domNodes.clear();
922
1244
  const disposeDelegation = initEventDelegation(bridge, options.eventDelegationRoot ?? "body");
923
- const main = instance.exports.mainClient;
924
- if (typeof main === "function")
925
- main();
1245
+ const main = wrapPromisingExport(instance.exports.mainClient);
1246
+ invokeWasmExport(main);
926
1247
  activeRuntime = {
927
1248
  options: normalizedOptions,
928
1249
  dispose: () => {