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/wasm/init.js CHANGED
@@ -217,6 +217,24 @@ function readString(ptr, len) {
217
217
  function writeBytes(ptr, data) {
218
218
  getMemoryView().set(data, ptr);
219
219
  }
220
+ function wrapPromisingExport(fn) {
221
+ if (!fn)
222
+ return;
223
+ const promising = WebAssembly.promising;
224
+ if (typeof promising !== "function")
225
+ return fn;
226
+ return promising(fn);
227
+ }
228
+ function invokeWasmExport(fn, ...args) {
229
+ if (!fn)
230
+ return;
231
+ const result = fn(...args);
232
+ if (result && typeof result.then === "function") {
233
+ result.then(undefined, (error) => {
234
+ console.error(error);
235
+ });
236
+ }
237
+ }
220
238
 
221
239
  class ZxBridgeCore {
222
240
  #intervals = new Map;
@@ -225,8 +243,12 @@ class ZxBridgeCore {
225
243
  #fetchCompleteHandler;
226
244
  constructor(exports) {
227
245
  this._alloc = exports.__zx_alloc;
228
- this.#handler = exports.__zx_cb;
229
- this.#fetchCompleteHandler = exports.__zx_fetch_complete;
246
+ this.#handler = wrapPromisingExport(exports.__zx_cb);
247
+ const fetchCompleteHandler = wrapPromisingExport(exports.__zx_fetch_complete);
248
+ if (!fetchCompleteHandler) {
249
+ throw new Error("__zx_fetch_complete not exported from WASM");
250
+ }
251
+ this.#fetchCompleteHandler = fetchCompleteHandler;
230
252
  if (exports.memory)
231
253
  jsz.memory = exports.memory;
232
254
  }
@@ -237,7 +259,7 @@ class ZxBridgeCore {
237
259
  return;
238
260
  }
239
261
  const dataRef = storeValueGetRef(data);
240
- handler(type, id, dataRef);
262
+ invokeWasmExport(handler, type, id, dataRef);
241
263
  }
242
264
  fetchAsync(urlPtr, urlLen, methodPtr, methodLen, headersPtr, headersLen, bodyPtr, bodyLen, timeoutMs, fetchId) {
243
265
  const url = readString(urlPtr, urlLen);
@@ -282,7 +304,7 @@ class ZxBridgeCore {
282
304
  const encoded = textEncoder.encode(body);
283
305
  const ptr = this._alloc(encoded.length);
284
306
  writeBytes(ptr, encoded);
285
- handler(fetchId, statusCode, ptr, encoded.length, isError ? 1 : 0);
307
+ invokeWasmExport(handler, fetchId, statusCode, ptr, encoded.length, isError ? 1 : 0);
286
308
  }
287
309
  setTimeout(callbackId, delayMs) {
288
310
  setTimeout(() => {
@@ -354,6 +376,230 @@ class ZxBridgeCore {
354
376
  };
355
377
  }
356
378
  }
379
+ // src/kv.ts
380
+ function isSyncKVNamespace(binding) {
381
+ const candidate = binding;
382
+ return typeof candidate.getSync === "function" && typeof candidate.putSync === "function" && typeof candidate.deleteSync === "function" && typeof candidate.listSync === "function";
383
+ }
384
+ function createKVImports(bindings, getMemory) {
385
+ const encoder2 = new TextEncoder;
386
+ const decoder2 = new TextDecoder;
387
+ function readStr(ptr, len) {
388
+ return decoder2.decode(new Uint8Array(getMemory().buffer, ptr, len));
389
+ }
390
+ function writeBytes2(buf_ptr, buf_max, data) {
391
+ if (data.length > buf_max)
392
+ return -2;
393
+ new Uint8Array(getMemory().buffer, buf_ptr, data.length).set(data);
394
+ return data.length;
395
+ }
396
+ function binding(ns) {
397
+ return bindings[ns] ?? bindings["default"] ?? null;
398
+ }
399
+ const Suspending = WebAssembly.Suspending;
400
+ if (typeof Suspending !== "function") {
401
+ let syncBinding = function(ns) {
402
+ const candidate = binding(ns);
403
+ return candidate && isSyncKVNamespace(candidate) ? candidate : null;
404
+ };
405
+ return {
406
+ kv_get: (ns_ptr, ns_len, key_ptr, key_len, buf_ptr, buf_max) => {
407
+ const b = syncBinding(readStr(ns_ptr, ns_len));
408
+ if (!b)
409
+ return -1;
410
+ const value = b.getSync(readStr(key_ptr, key_len));
411
+ if (value === null)
412
+ return -1;
413
+ return writeBytes2(buf_ptr, buf_max, encoder2.encode(value));
414
+ },
415
+ kv_put: (ns_ptr, ns_len, key_ptr, key_len, val_ptr, val_len) => {
416
+ const b = syncBinding(readStr(ns_ptr, ns_len));
417
+ if (!b)
418
+ return 0;
419
+ b.putSync(readStr(key_ptr, key_len), readStr(val_ptr, val_len));
420
+ return 0;
421
+ },
422
+ kv_delete: (ns_ptr, ns_len, key_ptr, key_len) => {
423
+ const b = syncBinding(readStr(ns_ptr, ns_len));
424
+ if (!b)
425
+ return 0;
426
+ b.deleteSync(readStr(key_ptr, key_len));
427
+ return 0;
428
+ },
429
+ kv_list: (ns_ptr, ns_len, pfx_ptr, pfx_len, buf_ptr, buf_max) => {
430
+ const b = syncBinding(readStr(ns_ptr, ns_len));
431
+ if (!b)
432
+ return writeBytes2(buf_ptr, buf_max, encoder2.encode("[]"));
433
+ const prefix = readStr(pfx_ptr, pfx_len);
434
+ const result = b.listSync(prefix.length > 0 ? { prefix } : undefined);
435
+ return writeBytes2(buf_ptr, buf_max, encoder2.encode(JSON.stringify(result.keys.map((k) => k.name))));
436
+ }
437
+ };
438
+ }
439
+ return {
440
+ kv_get: new Suspending(async (ns_ptr, ns_len, key_ptr, key_len, buf_ptr, buf_max) => {
441
+ const b = binding(readStr(ns_ptr, ns_len));
442
+ if (!b)
443
+ return -1;
444
+ const value = await b.get(readStr(key_ptr, key_len));
445
+ if (value === null)
446
+ return -1;
447
+ return writeBytes2(buf_ptr, buf_max, encoder2.encode(value));
448
+ }),
449
+ kv_put: new Suspending(async (ns_ptr, ns_len, key_ptr, key_len, val_ptr, val_len) => {
450
+ const b = binding(readStr(ns_ptr, ns_len));
451
+ if (!b)
452
+ return -1;
453
+ await b.put(readStr(key_ptr, key_len), readStr(val_ptr, val_len));
454
+ return 0;
455
+ }),
456
+ kv_delete: new Suspending(async (ns_ptr, ns_len, key_ptr, key_len) => {
457
+ const b = binding(readStr(ns_ptr, ns_len));
458
+ if (!b)
459
+ return -1;
460
+ await b.delete(readStr(key_ptr, key_len));
461
+ return 0;
462
+ }),
463
+ kv_list: new Suspending(async (ns_ptr, ns_len, prefix_ptr, prefix_len, buf_ptr, buf_max) => {
464
+ const b = binding(readStr(ns_ptr, ns_len));
465
+ if (!b)
466
+ return writeBytes2(buf_ptr, buf_max, encoder2.encode("[]"));
467
+ const prefix = readStr(prefix_ptr, prefix_len);
468
+ const result = await b.list(prefix.length > 0 ? { prefix } : undefined);
469
+ return writeBytes2(buf_ptr, buf_max, encoder2.encode(JSON.stringify(result.keys.map((k) => k.name))));
470
+ })
471
+ };
472
+ }
473
+
474
+ // src/browser/kv.ts
475
+ function getIndexedDb() {
476
+ if (typeof indexedDB === "undefined") {
477
+ throw new Error("IndexedDB is not available in this environment");
478
+ }
479
+ return indexedDB;
480
+ }
481
+ function requestToPromise(request) {
482
+ return new Promise((resolve, reject) => {
483
+ request.onsuccess = () => resolve(request.result);
484
+ request.onerror = () => reject(request.error ?? new Error("IndexedDB request failed"));
485
+ });
486
+ }
487
+ function transactionToPromise(transaction) {
488
+ return new Promise((resolve, reject) => {
489
+ transaction.oncomplete = () => resolve();
490
+ transaction.onabort = () => reject(transaction.error ?? new Error("IndexedDB transaction aborted"));
491
+ transaction.onerror = () => reject(transaction.error ?? new Error("IndexedDB transaction failed"));
492
+ });
493
+ }
494
+ function createIndexedDbKV(options = {}) {
495
+ const databaseName = options.databaseName ?? "ziex-kv";
496
+ const storeName = options.storeName ?? "kv";
497
+ const namespace = options.namespace ?? "default";
498
+ const dbPromise = new Promise((resolve, reject) => {
499
+ const request = getIndexedDb().open(databaseName, 1);
500
+ request.onupgradeneeded = () => {
501
+ const db = request.result;
502
+ if (!db.objectStoreNames.contains(storeName)) {
503
+ db.createObjectStore(storeName);
504
+ }
505
+ };
506
+ request.onsuccess = () => resolve(request.result);
507
+ request.onerror = () => reject(request.error ?? new Error("Failed to open IndexedDB"));
508
+ });
509
+ const scopedKey = (key) => `${namespace}:${key}`;
510
+ return {
511
+ async get(key) {
512
+ const db = await dbPromise;
513
+ const tx = db.transaction(storeName, "readonly");
514
+ const store = tx.objectStore(storeName);
515
+ const value = await requestToPromise(store.get(scopedKey(key)));
516
+ await transactionToPromise(tx);
517
+ console.debug(`KV GET - Key: ${key}, Value: ${value}`);
518
+ return typeof value === "string" ? value : null;
519
+ },
520
+ async put(key, value) {
521
+ const db = await dbPromise;
522
+ const tx = db.transaction(storeName, "readwrite");
523
+ tx.objectStore(storeName).put(value, scopedKey(key));
524
+ await transactionToPromise(tx);
525
+ console.debug(`KV PUT - Key: ${key}, Value: ${value}`);
526
+ },
527
+ async delete(key) {
528
+ const db = await dbPromise;
529
+ const tx = db.transaction(storeName, "readwrite");
530
+ tx.objectStore(storeName).delete(scopedKey(key));
531
+ await transactionToPromise(tx);
532
+ },
533
+ async list(options2) {
534
+ const db = await dbPromise;
535
+ const tx = db.transaction(storeName, "readonly");
536
+ const store = tx.objectStore(storeName);
537
+ const keys = await requestToPromise(store.getAllKeys());
538
+ await transactionToPromise(tx);
539
+ const prefix = scopedKey(options2?.prefix ?? "");
540
+ return {
541
+ keys: keys.filter((key) => typeof key === "string" && key.startsWith(prefix)).map((key) => ({ name: key.slice(namespace.length + 1) }))
542
+ };
543
+ }
544
+ };
545
+ }
546
+ function getLocalStorage() {
547
+ if (typeof localStorage === "undefined") {
548
+ throw new Error("localStorage is not available in this environment");
549
+ }
550
+ return localStorage;
551
+ }
552
+ function createLocalStorageKV(options = {}) {
553
+ const storage = getLocalStorage();
554
+ const namespace = options.namespace ?? "default";
555
+ const storagePrefix = options.storagePrefix ?? "ziex-kv";
556
+ const scopedKey = (key) => `${storagePrefix}:${namespace}:${key}`;
557
+ const namespacePrefix = scopedKey("");
558
+ return {
559
+ getSync(key) {
560
+ return storage.getItem(scopedKey(key));
561
+ },
562
+ async get(key) {
563
+ return this.getSync(key);
564
+ },
565
+ putSync(key, value) {
566
+ storage.setItem(scopedKey(key), value);
567
+ },
568
+ async put(key, value) {
569
+ this.putSync(key, value);
570
+ },
571
+ deleteSync(key) {
572
+ storage.removeItem(scopedKey(key));
573
+ },
574
+ async delete(key) {
575
+ this.deleteSync(key);
576
+ },
577
+ listSync(options2) {
578
+ const prefix = namespacePrefix + (options2?.prefix ?? "");
579
+ const keys = [];
580
+ for (let i = 0;i < storage.length; i += 1) {
581
+ const key = storage.key(i);
582
+ if (!key || !key.startsWith(prefix))
583
+ continue;
584
+ keys.push({ name: key.slice(namespacePrefix.length) });
585
+ }
586
+ return { keys };
587
+ },
588
+ async list(options2) {
589
+ return this.listSync(options2);
590
+ }
591
+ };
592
+ }
593
+ function hasJSPI() {
594
+ return typeof WebAssembly.Suspending === "function" && typeof WebAssembly.promising === "function";
595
+ }
596
+ function createBrowserKVBindings(options = {}) {
597
+ const namespace = options.namespace ?? "default";
598
+ return {
599
+ [namespace]: hasJSPI() ? createIndexedDbKV(options) : createLocalStorageKV(options)
600
+ };
601
+ }
602
+
357
603
  // src/wasm/index.ts
358
604
  class ZxBridge extends ZxBridgeCore {
359
605
  #websockets = new Map;
@@ -362,13 +608,30 @@ class ZxBridge extends ZxBridgeCore {
362
608
  #wsOnErrorHandler;
363
609
  #wsOnCloseHandler;
364
610
  #eventbridge;
611
+ #eventbridgeAsync;
365
612
  constructor(exports) {
366
613
  super(exports);
367
- this.#wsOnOpenHandler = exports.__zx_ws_onopen;
368
- this.#wsOnMessageHandler = exports.__zx_ws_onmessage;
369
- this.#wsOnErrorHandler = exports.__zx_ws_onerror;
370
- this.#wsOnCloseHandler = exports.__zx_ws_onclose;
614
+ this.#wsOnOpenHandler = wrapPromisingExport(exports.__zx_ws_onopen);
615
+ this.#wsOnMessageHandler = wrapPromisingExport(exports.__zx_ws_onmessage);
616
+ this.#wsOnErrorHandler = wrapPromisingExport(exports.__zx_ws_onerror);
617
+ this.#wsOnCloseHandler = wrapPromisingExport(exports.__zx_ws_onclose);
371
618
  this.#eventbridge = exports.__zx_eventbridge;
619
+ this.#eventbridgeAsync = wrapPromisingExport(exports.__zx_eventbridge_async ?? exports.__zx_eventbridge);
620
+ }
621
+ eventMaySuspend(velementId, eventTypeId) {
622
+ return !!((eventHandlerModes.get(velementId) ?? 0) & 1 << eventTypeId);
623
+ }
624
+ setEventHandlerMode(velementId, eventTypeId, maySuspend) {
625
+ const bit = 1 << eventTypeId;
626
+ const current = eventHandlerModes.get(velementId) ?? 0;
627
+ const next = maySuspend ? current | bit : current & ~bit;
628
+ if (next === 0)
629
+ eventHandlerModes.delete(velementId);
630
+ else
631
+ eventHandlerModes.set(velementId, next);
632
+ }
633
+ clearEventHandlerModes(velementId) {
634
+ eventHandlerModes.delete(velementId);
372
635
  }
373
636
  submitFormActionAsync(form, statesJson, fetchId) {
374
637
  const formData = new FormData(form);
@@ -398,7 +661,7 @@ class ZxBridge extends ZxBridgeCore {
398
661
  return;
399
662
  const protocol = ws.protocol || "";
400
663
  const { ptr, len } = this._writeStringToWasm(protocol);
401
- handler(wsId, ptr, len);
664
+ invokeWasmExport(handler, wsId, ptr, len);
402
665
  };
403
666
  ws.onmessage = (event) => {
404
667
  const handler = this.#wsOnMessageHandler;
@@ -407,14 +670,14 @@ class ZxBridge extends ZxBridgeCore {
407
670
  const isBinary = event.data instanceof ArrayBuffer;
408
671
  const data = isBinary ? new Uint8Array(event.data) : textEncoder.encode(event.data);
409
672
  const { ptr, len } = this._writeBytesToWasm(data);
410
- handler(wsId, ptr, len, isBinary ? 1 : 0);
673
+ invokeWasmExport(handler, wsId, ptr, len, isBinary ? 1 : 0);
411
674
  };
412
675
  ws.onerror = (_event) => {
413
676
  const handler = this.#wsOnErrorHandler;
414
677
  if (!handler)
415
678
  return;
416
679
  const { ptr, len } = this._writeStringToWasm("WebSocket error");
417
- handler(wsId, ptr, len);
680
+ invokeWasmExport(handler, wsId, ptr, len);
418
681
  };
419
682
  ws.onclose = (event) => {
420
683
  const handler = this.#wsOnCloseHandler;
@@ -422,7 +685,7 @@ class ZxBridge extends ZxBridgeCore {
422
685
  return;
423
686
  const reason = event.reason || "";
424
687
  const { ptr, len } = this._writeStringToWasm(reason);
425
- handler(wsId, event.code, ptr, len, event.wasClean ? 1 : 0);
688
+ invokeWasmExport(handler, wsId, event.code, ptr, len, event.wasClean ? 1 : 0);
426
689
  this.#websockets.delete(wsId);
427
690
  };
428
691
  this.#websockets.set(wsId, ws);
@@ -431,7 +694,7 @@ class ZxBridge extends ZxBridgeCore {
431
694
  if (handler) {
432
695
  const msg = error instanceof Error ? error.message : "WebSocket connection failed";
433
696
  const { ptr, len } = this._writeStringToWasm(msg);
434
- handler(wsId, ptr, len);
697
+ invokeWasmExport(handler, wsId, ptr, len);
435
698
  }
436
699
  }
437
700
  }
@@ -462,6 +725,7 @@ class ZxBridge extends ZxBridgeCore {
462
725
  }
463
726
  dispose() {
464
727
  super.dispose();
728
+ eventHandlerModes.clear();
465
729
  for (const ws of this.#websockets.values()) {
466
730
  try {
467
731
  ws.close();
@@ -470,16 +734,24 @@ class ZxBridge extends ZxBridgeCore {
470
734
  this.#websockets.clear();
471
735
  }
472
736
  eventbridge(velementId, eventTypeId, event) {
473
- if (!this.#eventbridge)
474
- return;
475
737
  const eventRef = storeValueGetRef(event);
476
- this.#eventbridge(velementId, eventTypeId, eventRef);
738
+ if (this.eventMaySuspend(velementId, eventTypeId)) {
739
+ invokeWasmExport(this.#eventbridgeAsync, velementId, eventTypeId, eventRef);
740
+ return;
741
+ }
742
+ invokeWasmExport(this.#eventbridge, velementId, eventTypeId, eventRef);
477
743
  }
478
744
  static createImportObject(bridgeRef) {
479
745
  return {
480
746
  ...jsz.importObject(),
481
747
  __zx: {
482
748
  _log: (level, ptr, len) => ZxBridgeCore.log(level, ptr, len),
749
+ _setEventHandlerMode: (vnodeId, eventTypeId, maySuspend) => {
750
+ bridgeRef.current?.setEventHandlerMode(vnodeId, eventTypeId, maySuspend !== 0);
751
+ },
752
+ _clearEventHandlerModes: (vnodeId) => {
753
+ bridgeRef.current?.clearEventHandlerModes(vnodeId);
754
+ },
483
755
  _fetchAsync: (urlPtr, urlLen, methodPtr, methodLen, headersPtr, headersLen, bodyPtr, bodyLen, timeoutMs, fetchId) => {
484
756
  bridgeRef.current?.fetchAsync(urlPtr, urlLen, methodPtr, methodLen, headersPtr, headersLen, bodyPtr, bodyLen, timeoutMs, fetchId);
485
757
  },
@@ -848,6 +1120,7 @@ var EVENT_TYPE_MAP = {
848
1120
  touchmove: 17,
849
1121
  scroll: 18
850
1122
  };
1123
+ var eventHandlerModes = new Map;
851
1124
  function initEventDelegation(bridge, rootSelector = "body") {
852
1125
  const root = document.querySelector(rootSelector);
853
1126
  if (!root)
@@ -877,17 +1150,31 @@ function initEventDelegation(bridge, rootSelector = "body") {
877
1150
  }
878
1151
  var DEFAULT_URL = "/assets/_/main.wasm";
879
1152
  var activeRuntime = null;
1153
+ function buildDevtoolsLocation() {
1154
+ return {
1155
+ href: window.location.href,
1156
+ origin: window.location.origin,
1157
+ host: window.location.host,
1158
+ pathname: window.location.pathname
1159
+ };
1160
+ }
880
1161
  function normalizeOptions(options = {}) {
881
1162
  return {
882
1163
  url: options.url,
883
1164
  eventDelegationRoot: options.eventDelegationRoot,
884
- importObject: options.importObject
1165
+ importObject: options.importObject,
1166
+ kv: options.kv
885
1167
  };
886
1168
  }
887
1169
  function registerDevReinit(options) {
888
1170
  if (typeof window === "undefined")
889
1171
  return;
890
- window.__zx_dev_reinit = () => init(options);
1172
+ const reinit = () => init(options);
1173
+ window.__zx_dev_reinit = reinit;
1174
+ window.__ZIEX_DEVTOOLS_GLOBAL_HOOK__ = {
1175
+ location: buildDevtoolsLocation(),
1176
+ reinit
1177
+ };
891
1178
  }
892
1179
  async function init(options = {}) {
893
1180
  const normalizedOptions = normalizeOptions(options);
@@ -897,17 +1184,28 @@ async function init(options = {}) {
897
1184
  }
898
1185
  const url = options.url ?? document.getElementById("__$wasmlink")?.href ?? DEFAULT_URL;
899
1186
  const bridgeRef = { current: null };
900
- const importObject = Object.assign({}, ZxBridge.createImportObject(bridgeRef), options.importObject);
1187
+ let wasmMemory = null;
1188
+ const kvBindings = options.kv ?? createBrowserKVBindings();
1189
+ const kvImportObject = {
1190
+ __zx_kv: createKVImports(kvBindings, () => {
1191
+ if (wasmMemory)
1192
+ return wasmMemory;
1193
+ if (jsz.memory)
1194
+ return jsz.memory;
1195
+ throw new Error("WASM memory is not ready");
1196
+ })
1197
+ };
1198
+ const importObject = Object.assign({}, ZxBridge.createImportObject(bridgeRef), kvImportObject, options.importObject);
901
1199
  const source = await WebAssembly.instantiateStreaming(fetch(url), importObject);
902
1200
  const { instance } = source;
903
- jsz.memory = instance.exports.memory;
1201
+ wasmMemory = instance.exports.memory;
1202
+ jsz.memory = wasmMemory;
904
1203
  const bridge = new ZxBridge(instance.exports);
905
1204
  bridgeRef.current = bridge;
906
1205
  domNodes.clear();
907
1206
  const disposeDelegation = initEventDelegation(bridge, options.eventDelegationRoot ?? "body");
908
- const main = instance.exports.mainClient;
909
- if (typeof main === "function")
910
- main();
1207
+ const main = wrapPromisingExport(instance.exports.mainClient);
1208
+ invokeWasmExport(main);
911
1209
  activeRuntime = {
912
1210
  options: normalizedOptions,
913
1211
  dispose: () => {