ziex 0.1.0-dev.526 → 0.1.0-dev.547

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
@@ -1,7 +1,7 @@
1
1
  // src/index.ts
2
2
  var zx = {
3
3
  name: "zx",
4
- version: "0.1.0-dev.526",
4
+ version: "0.1.0-dev.547",
5
5
  description: "ZX is a framework for building web applications with Zig.",
6
6
  repository: "https://github.com/nurulhudaapon/zx",
7
7
  fingerprint: 14616285862371232000,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ziex",
3
- "version": "0.1.0-dev.526",
3
+ "version": "0.1.0-dev.547",
4
4
  "description": "ZX is a framework for building web applications with Zig.",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/react/dom.d.ts CHANGED
@@ -127,8 +127,8 @@ export type DiscoveredComponent = {
127
127
  * Finds all React component markers in the DOM and returns their metadata.
128
128
  *
129
129
  * This is a DOM-first approach that:
130
- * 1. Walks the DOM once to find all `<!--$id-->` markers
131
- * 2. Reads metadata from companion `<script data-zx="id">` elements
130
+ * 1. Walks the DOM to find all `<!--$id name props-->` comment markers
131
+ * 2. Extracts name and props directly from the comment content (JSON-encoded)
132
132
  * 3. Creates containers for React to render into
133
133
  *
134
134
  * @returns Array of discovered components with their containers and props
package/react/index.js CHANGED
@@ -1,15 +1,28 @@
1
1
  // src/react/dom.ts
2
2
  function findCommentMarker(id) {
3
- const startMarker = `$${id}`;
3
+ const startPrefix = `$${id} `;
4
4
  const endMarker = `/$${id}`;
5
5
  const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT, null);
6
6
  let startComment = null;
7
7
  let endComment = null;
8
+ let name = "";
9
+ let props = {};
8
10
  let node;
9
11
  while (node = walker.nextNode()) {
10
12
  const text = node.textContent?.trim() || "";
11
- if (text === startMarker) {
13
+ if (text.startsWith(startPrefix)) {
12
14
  startComment = node;
15
+ const content = text.slice(startPrefix.length);
16
+ const jsonStart = content.indexOf("{");
17
+ if (jsonStart !== -1) {
18
+ name = content.slice(0, jsonStart).trim();
19
+ const jsonStr = content.slice(jsonStart);
20
+ try {
21
+ props = JSON.parse(jsonStr);
22
+ } catch {}
23
+ } else {
24
+ name = content.trim();
25
+ }
13
26
  }
14
27
  if (text === endMarker) {
15
28
  endComment = node;
@@ -17,20 +30,10 @@ function findCommentMarker(id) {
17
30
  }
18
31
  }
19
32
  if (startComment && endComment) {
20
- return { startComment, endComment };
33
+ return { startComment, endComment, name, props };
21
34
  }
22
35
  return null;
23
36
  }
24
- function getComponentMetadata(id) {
25
- const script = document.querySelector(`script[data-zx="${id}"]`);
26
- if (script?.textContent) {
27
- try {
28
- const data = JSON.parse(script.textContent);
29
- return { name: data.name || "", props: data.props || {} };
30
- } catch {}
31
- }
32
- return { name: "", props: {} };
33
- }
34
37
  function createContainerBetweenMarkers(startComment, endComment) {
35
38
  const container = document.createElement("div");
36
39
  container.style.display = "contents";
@@ -48,8 +51,7 @@ async function prepareComponent(component) {
48
51
  if (!marker) {
49
52
  throw new Error(`Comment marker for ${component.id} not found`, { cause: component });
50
53
  }
51
- const metadata = getComponentMetadata(component.id);
52
- const props = metadata.props;
54
+ const props = marker.props;
53
55
  const domNode = createContainerBetweenMarkers(marker.startComment, marker.endComment);
54
56
  const Component = await component.import();
55
57
  return { domNode, props, Component };
@@ -60,25 +62,47 @@ function filterComponents(components) {
60
62
  }
61
63
  function discoverComponents() {
62
64
  const components = [];
63
- const scripts = Array.from(document.querySelectorAll("script[data-zx]"));
64
- for (const script of scripts) {
65
- const id = script.getAttribute("data-zx");
66
- if (!id)
67
- continue;
68
- let name = "";
69
- let props = {};
70
- try {
71
- const data = JSON.parse(script.textContent || "{}");
72
- name = data.name || "";
73
- props = data.props || {};
74
- } catch {
75
- continue;
65
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT, null);
66
+ const markers = [];
67
+ let node;
68
+ while (node = walker.nextNode()) {
69
+ const text = node.textContent?.trim() || "";
70
+ if (text.startsWith("$") && !text.startsWith("/$")) {
71
+ const spaceIdx = text.indexOf(" ");
72
+ if (spaceIdx !== -1) {
73
+ const id = text.slice(1, spaceIdx);
74
+ const content = text.slice(spaceIdx + 1);
75
+ const jsonStart = content.indexOf("{");
76
+ let name = "";
77
+ let props = {};
78
+ if (jsonStart !== -1) {
79
+ name = content.slice(0, jsonStart).trim();
80
+ try {
81
+ props = JSON.parse(content.slice(jsonStart));
82
+ } catch {}
83
+ } else {
84
+ name = content.trim();
85
+ }
86
+ markers.push({ id, name, props, startComment: node, endComment: null });
87
+ }
88
+ } else if (text.startsWith("/$")) {
89
+ const id = text.slice(2);
90
+ const marker = markers.find((m) => m.id === id && !m.endComment);
91
+ if (marker) {
92
+ marker.endComment = node;
93
+ }
76
94
  }
77
- const marker = findCommentMarker(id);
78
- if (!marker)
95
+ }
96
+ for (const marker of markers) {
97
+ if (!marker.endComment)
79
98
  continue;
80
99
  const container = createContainerBetweenMarkers(marker.startComment, marker.endComment);
81
- components.push({ id, name, props, container });
100
+ components.push({
101
+ id: marker.id,
102
+ name: marker.name,
103
+ props: marker.props,
104
+ container
105
+ });
82
106
  }
83
107
  return components;
84
108
  }
package/wasm/index.d.ts CHANGED
@@ -18,8 +18,11 @@ export declare function storeValueGetRef(val: any): bigint;
18
18
  export declare class ZxBridge {
19
19
  #private;
20
20
  constructor(exports: WebAssembly.Exports);
21
- /** Fetch a URL and callback with the response */
22
- fetch(urlPtr: number, urlLen: number, callbackId: bigint): void;
21
+ /**
22
+ * Async fetch with full options support.
23
+ * Calls __zx_fetch_complete when done.
24
+ */
25
+ fetchAsync(urlPtr: number, urlLen: number, methodPtr: number, methodLen: number, headersPtr: number, headersLen: number, bodyPtr: number, bodyLen: number, timeoutMs: number, fetchId: bigint): void;
23
26
  /** Set a timeout and callback when it fires */
24
27
  setTimeout(callbackId: bigint, delayMs: number): void;
25
28
  /** Set an interval and callback each time it fires */
package/wasm/index.js CHANGED
@@ -189,10 +189,13 @@ function readString(ptr, len) {
189
189
  const memory = new Uint8Array(jsz.memory.buffer);
190
190
  return new TextDecoder().decode(memory.slice(ptr, ptr + len));
191
191
  }
192
+ function writeBytes(ptr, data) {
193
+ const memory = new Uint8Array(jsz.memory.buffer);
194
+ memory.set(data, ptr);
195
+ }
192
196
 
193
197
  class ZxBridge {
194
198
  #exports;
195
- #nextCallbackId = BigInt(1);
196
199
  #intervals = new Map;
197
200
  constructor(exports) {
198
201
  this.#exports = exports;
@@ -200,8 +203,8 @@ class ZxBridge {
200
203
  get #handler() {
201
204
  return this.#exports.__zx_cb;
202
205
  }
203
- #getNextId() {
204
- return this.#nextCallbackId++;
206
+ get #fetchCompleteHandler() {
207
+ return this.#exports.__zx_fetch_complete;
205
208
  }
206
209
  #invoke(type, id, data) {
207
210
  const handler = this.#handler;
@@ -212,14 +215,62 @@ class ZxBridge {
212
215
  const dataRef = storeValueGetRef(data);
213
216
  handler(type, id, dataRef);
214
217
  }
215
- fetch(urlPtr, urlLen, callbackId) {
218
+ fetchAsync(urlPtr, urlLen, methodPtr, methodLen, headersPtr, headersLen, bodyPtr, bodyLen, timeoutMs, fetchId) {
216
219
  const url = readString(urlPtr, urlLen);
217
- fetch(url).then((response) => response.text()).then((text) => {
218
- this.#invoke(CallbackType.FetchSuccess, callbackId, text);
220
+ const method = methodLen > 0 ? readString(methodPtr, methodLen) : "GET";
221
+ const headersJson = headersLen > 0 ? readString(headersPtr, headersLen) : "{}";
222
+ const body = bodyLen > 0 ? readString(bodyPtr, bodyLen) : undefined;
223
+ let headers = {};
224
+ try {
225
+ headers = JSON.parse(headersJson);
226
+ } catch {
227
+ for (const line of headersJson.split(`
228
+ `)) {
229
+ const colonIdx = line.indexOf(":");
230
+ if (colonIdx > 0) {
231
+ headers[line.slice(0, colonIdx)] = line.slice(colonIdx + 1);
232
+ }
233
+ }
234
+ }
235
+ const controller = new AbortController;
236
+ const timeout = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : null;
237
+ const fetchOptions = {
238
+ method,
239
+ headers: Object.keys(headers).length > 0 ? headers : undefined,
240
+ body: method !== "GET" && method !== "HEAD" ? body : undefined,
241
+ signal: controller.signal
242
+ };
243
+ fetch(url, fetchOptions).then(async (response) => {
244
+ if (timeout)
245
+ clearTimeout(timeout);
246
+ const text = await response.text();
247
+ this.#notifyFetchComplete(fetchId, response.status, text, false);
219
248
  }).catch((error) => {
220
- this.#invoke(CallbackType.FetchError, callbackId, error.message ?? "Fetch failed");
249
+ if (timeout)
250
+ clearTimeout(timeout);
251
+ const isAbort = error.name === "AbortError";
252
+ const errorMsg = isAbort ? "Request timeout" : error.message ?? "Fetch failed";
253
+ this.#notifyFetchComplete(fetchId, 0, errorMsg, true);
221
254
  });
222
255
  }
256
+ #notifyFetchComplete(fetchId, statusCode, body, isError) {
257
+ const handler = this.#fetchCompleteHandler;
258
+ if (!handler) {
259
+ console.warn("__zx_fetch_complete not exported from WASM");
260
+ return;
261
+ }
262
+ const encoded = new TextEncoder().encode(body);
263
+ const allocFn = this.#exports.__zx_alloc;
264
+ let ptr = 0;
265
+ if (allocFn) {
266
+ ptr = allocFn(encoded.length);
267
+ } else {
268
+ const heapBase = this.#exports.__heap_base?.value ?? 65536;
269
+ ptr = heapBase + Number(fetchId % BigInt(256)) * 65536;
270
+ }
271
+ writeBytes(ptr, encoded);
272
+ handler(fetchId, statusCode, ptr, encoded.length, isError ? 1 : 0);
273
+ }
223
274
  setTimeout(callbackId, delayMs) {
224
275
  setTimeout(() => {
225
276
  this.#invoke(CallbackType.Timeout, callbackId, null);
@@ -248,8 +299,8 @@ class ZxBridge {
248
299
  return {
249
300
  ...jsz.importObject(),
250
301
  __zx: {
251
- _fetch: (urlPtr, urlLen, callbackId) => {
252
- bridgeRef.current?.fetch(urlPtr, urlLen, callbackId);
302
+ _fetchAsync: (urlPtr, urlLen, methodPtr, methodLen, headersPtr, headersLen, bodyPtr, bodyLen, timeoutMs, fetchId) => {
303
+ bridgeRef.current?.fetchAsync(urlPtr, urlLen, methodPtr, methodLen, headersPtr, headersLen, bodyPtr, bodyLen, timeoutMs, fetchId);
253
304
  },
254
305
  _setTimeout: (callbackId, delayMs) => {
255
306
  bridgeRef.current?.setTimeout(callbackId, delayMs);
package/wasm/init.js CHANGED
@@ -189,10 +189,13 @@ function readString(ptr, len) {
189
189
  const memory = new Uint8Array(jsz.memory.buffer);
190
190
  return new TextDecoder().decode(memory.slice(ptr, ptr + len));
191
191
  }
192
+ function writeBytes(ptr, data) {
193
+ const memory = new Uint8Array(jsz.memory.buffer);
194
+ memory.set(data, ptr);
195
+ }
192
196
 
193
197
  class ZxBridge {
194
198
  #exports;
195
- #nextCallbackId = BigInt(1);
196
199
  #intervals = new Map;
197
200
  constructor(exports) {
198
201
  this.#exports = exports;
@@ -200,8 +203,8 @@ class ZxBridge {
200
203
  get #handler() {
201
204
  return this.#exports.__zx_cb;
202
205
  }
203
- #getNextId() {
204
- return this.#nextCallbackId++;
206
+ get #fetchCompleteHandler() {
207
+ return this.#exports.__zx_fetch_complete;
205
208
  }
206
209
  #invoke(type, id, data) {
207
210
  const handler = this.#handler;
@@ -212,14 +215,62 @@ class ZxBridge {
212
215
  const dataRef = storeValueGetRef(data);
213
216
  handler(type, id, dataRef);
214
217
  }
215
- fetch(urlPtr, urlLen, callbackId) {
218
+ fetchAsync(urlPtr, urlLen, methodPtr, methodLen, headersPtr, headersLen, bodyPtr, bodyLen, timeoutMs, fetchId) {
216
219
  const url = readString(urlPtr, urlLen);
217
- fetch(url).then((response) => response.text()).then((text) => {
218
- this.#invoke(CallbackType.FetchSuccess, callbackId, text);
220
+ const method = methodLen > 0 ? readString(methodPtr, methodLen) : "GET";
221
+ const headersJson = headersLen > 0 ? readString(headersPtr, headersLen) : "{}";
222
+ const body = bodyLen > 0 ? readString(bodyPtr, bodyLen) : undefined;
223
+ let headers = {};
224
+ try {
225
+ headers = JSON.parse(headersJson);
226
+ } catch {
227
+ for (const line of headersJson.split(`
228
+ `)) {
229
+ const colonIdx = line.indexOf(":");
230
+ if (colonIdx > 0) {
231
+ headers[line.slice(0, colonIdx)] = line.slice(colonIdx + 1);
232
+ }
233
+ }
234
+ }
235
+ const controller = new AbortController;
236
+ const timeout = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : null;
237
+ const fetchOptions = {
238
+ method,
239
+ headers: Object.keys(headers).length > 0 ? headers : undefined,
240
+ body: method !== "GET" && method !== "HEAD" ? body : undefined,
241
+ signal: controller.signal
242
+ };
243
+ fetch(url, fetchOptions).then(async (response) => {
244
+ if (timeout)
245
+ clearTimeout(timeout);
246
+ const text = await response.text();
247
+ this.#notifyFetchComplete(fetchId, response.status, text, false);
219
248
  }).catch((error) => {
220
- this.#invoke(CallbackType.FetchError, callbackId, error.message ?? "Fetch failed");
249
+ if (timeout)
250
+ clearTimeout(timeout);
251
+ const isAbort = error.name === "AbortError";
252
+ const errorMsg = isAbort ? "Request timeout" : error.message ?? "Fetch failed";
253
+ this.#notifyFetchComplete(fetchId, 0, errorMsg, true);
221
254
  });
222
255
  }
256
+ #notifyFetchComplete(fetchId, statusCode, body, isError) {
257
+ const handler = this.#fetchCompleteHandler;
258
+ if (!handler) {
259
+ console.warn("__zx_fetch_complete not exported from WASM");
260
+ return;
261
+ }
262
+ const encoded = new TextEncoder().encode(body);
263
+ const allocFn = this.#exports.__zx_alloc;
264
+ let ptr = 0;
265
+ if (allocFn) {
266
+ ptr = allocFn(encoded.length);
267
+ } else {
268
+ const heapBase = this.#exports.__heap_base?.value ?? 65536;
269
+ ptr = heapBase + Number(fetchId % BigInt(256)) * 65536;
270
+ }
271
+ writeBytes(ptr, encoded);
272
+ handler(fetchId, statusCode, ptr, encoded.length, isError ? 1 : 0);
273
+ }
223
274
  setTimeout(callbackId, delayMs) {
224
275
  setTimeout(() => {
225
276
  this.#invoke(CallbackType.Timeout, callbackId, null);
@@ -248,8 +299,8 @@ class ZxBridge {
248
299
  return {
249
300
  ...jsz.importObject(),
250
301
  __zx: {
251
- _fetch: (urlPtr, urlLen, callbackId) => {
252
- bridgeRef.current?.fetch(urlPtr, urlLen, callbackId);
302
+ _fetchAsync: (urlPtr, urlLen, methodPtr, methodLen, headersPtr, headersLen, bodyPtr, bodyLen, timeoutMs, fetchId) => {
303
+ bridgeRef.current?.fetchAsync(urlPtr, urlLen, methodPtr, methodLen, headersPtr, headersLen, bodyPtr, bodyLen, timeoutMs, fetchId);
253
304
  },
254
305
  _setTimeout: (callbackId, delayMs) => {
255
306
  bridgeRef.current?.setTimeout(callbackId, delayMs);