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/browser/kv.d.ts +13 -0
- package/cloudflare/index.js +350 -110
- package/index.js +45 -8
- package/kv.d.ts +17 -2
- package/package.json +2 -2
- package/wasm/core.d.ts +2 -0
- package/wasm/index.d.ts +18 -0
- package/wasm/index.js +344 -23
- package/wasm/init.js +321 -23
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: (
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
|
806
|
-
if (
|
|
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:
|
|
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
|
|
25
|
-
*
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
925
|
-
main();
|
|
1245
|
+
const main = wrapPromisingExport(instance.exports.mainClient);
|
|
1246
|
+
invokeWasmExport(main);
|
|
926
1247
|
activeRuntime = {
|
|
927
1248
|
options: normalizedOptions,
|
|
928
1249
|
dispose: () => {
|