ziex 0.1.0-dev.547 → 0.1.0-dev.562

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -122,6 +122,7 @@ const zx = @import("zx");
122
122
  - [x] Page
123
123
  - [ ] Assets
124
124
  - [x] API Route
125
+ - [x] Websocket Route
125
126
  - [ ] Plugin (_Alpha_)
126
127
  - [x] Builtin TailwindCSS and Esbuild
127
128
  - [x] Command based plugin system
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.547",
4
+ version: "0.1.0-dev.562",
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.547",
3
+ "version": "0.1.0-dev.562",
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
@@ -1,162 +1,21 @@
1
1
  import type { ComponentMetadata } from "./types";
2
- /**
3
- * Result of preparing a component for hydration.
4
- *
5
- * Contains all the necessary data to render a React component into its server-rendered container.
6
- */
7
2
  export type PreparedComponent = {
8
- /**
9
- * The HTML element where the component should be rendered.
10
- *
11
- * This is a container element created between the comment markers. The server-rendered
12
- * content is moved into this container, and React will hydrate it with the interactive component.
13
- *
14
- * @example
15
- * ```tsx
16
- * // Server-rendered HTML with comment markers:
17
- * // <!--$zx-abc123-0 CounterComponent {"max_count":10}-->
18
- * // <button>0</button>
19
- * // <!--/$zx-abc123-0-->
20
- *
21
- * const { domNode } = await prepareComponent(component);
22
- * createRoot(domNode).render(<Component {...props} />);
23
- * ```
24
- */
25
3
  domNode: HTMLElement;
26
- /**
27
- * Component props parsed from the comment marker.
28
- *
29
- * Props are extracted from the start comment marker content. The comment format is:
30
- * `<!--$id name props-->` where props is JSON-encoded.
31
- *
32
- * @example
33
- * ```tsx
34
- * // Server-rendered HTML:
35
- * // <!--$zx-abc123-0 CounterComponent {"max_count":10,"label":"Counter"}-->
36
- * // <button>0</button>
37
- * // <!--/$zx-abc123-0-->
38
- *
39
- * const { props } = await prepareComponent(component);
40
- * // props = { max_count: 10, label: "Counter" }
41
- * ```
42
- */
43
4
  props: Record<string, any> & {
44
- /**
45
- * React's special prop for setting inner HTML directly.
46
- *
47
- * May be used when the component has server-rendered children that should
48
- * be preserved during hydration.
49
- */
50
5
  dangerouslySetInnerHTML?: {
51
6
  __html: string;
52
7
  };
53
8
  };
54
- /**
55
- * The loaded React component function ready to render.
56
- *
57
- * This is the default export from the component module, lazy-loaded via the component's
58
- * import function. The component is ready to be rendered with React's `createRoot().render()`.
59
- *
60
- * @example
61
- * ```tsx
62
- * const { Component, props, domNode } = await prepareComponent(component);
63
- *
64
- * // Component is the default export from the component file:
65
- * // export default function CounterComponent({ max_count }: { max_count: number }) {
66
- * // return <div>Count: {max_count}</div>;
67
- * // }
68
- *
69
- * createRoot(domNode).render(<Component {...props} />);
70
- * ```
71
- */
72
9
  Component: (props: any) => React.ReactElement;
73
10
  };
74
- /**
75
- * Prepares a client-side component for hydration by locating its comment markers, extracting
76
- * props from the marker content, and lazy-loading the component module.
77
- *
78
- * This function bridges server-rendered HTML (from ZX's Zig transpiler) and client-side React
79
- * components. It searches for comment markers in the format `<!--$id name props-->...<!--/$id-->`
80
- * and extracts the component data from the marker content.
81
- *
82
- * @param component - The component metadata containing ID, import function, and other metadata
83
- * needed to locate and load the component
84
- *
85
- * @returns A Promise that resolves to a `PreparedComponent` object containing the DOM node,
86
- * parsed props, and the loaded React component function
87
- *
88
- * @throws {Error} If the component's comment markers cannot be found in the DOM. This typically
89
- * happens if the component ID doesn't match any marker, the script runs before
90
- * the HTML is loaded, or there's a mismatch between server and client metadata
91
- *
92
- * @example
93
- * ```tsx
94
- * // Basic usage with React:
95
- * import { createRoot } from "react-dom/client";
96
- * import { prepareComponent } from "ziex";
97
- * import { components } from "@ziex/components";
98
- *
99
- * for (const component of components) {
100
- * prepareComponent(component).then(({ domNode, Component, props }) => {
101
- * createRoot(domNode).render(<Component {...props} />);
102
- * }).catch(console.error);
103
- * }
104
- * ```
105
- *
106
- * @example
107
- * ```tsx
108
- * // Server-rendered HTML with comment markers:
109
- * // <!--$zx-abc123-0 CounterComponent {"max_count":10}-->
110
- * // <button>0</button>
111
- * // <!--/$zx-abc123-0-->
112
- * ```
113
- */
114
11
  export declare function prepareComponent(component: ComponentMetadata): Promise<PreparedComponent>;
115
12
  export declare function filterComponents(components: ComponentMetadata[]): ComponentMetadata[];
116
- /**
117
- * Discovered component from DOM traversal.
118
- * Contains all metadata needed to hydrate the component.
119
- */
120
13
  export type DiscoveredComponent = {
121
14
  id: string;
122
15
  name: string;
123
16
  props: Record<string, any>;
124
17
  container: HTMLElement;
125
18
  };
126
- /**
127
- * Finds all React component markers in the DOM and returns their metadata.
128
- *
129
- * This is a DOM-first approach that:
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
- * 3. Creates containers for React to render into
133
- *
134
- * @returns Array of discovered components with their containers and props
135
- */
136
19
  export declare function discoverComponents(): DiscoveredComponent[];
137
- /**
138
- * Component registry mapping component names to their import functions.
139
- */
140
20
  export type ComponentRegistry = Record<string, () => Promise<(props: any) => React.ReactElement>>;
141
- /**
142
- * Hydrates all React components found in the DOM.
143
- *
144
- * This is the simplest way to hydrate React islands - it automatically:
145
- * 1. Discovers all component markers in the DOM
146
- * 2. Looks up components by name in the registry
147
- * 3. Renders each component into its container
148
- *
149
- * @param registry - Map of component names to import functions
150
- * @param render - Function to render a component (e.g., `(el, Component, props) => createRoot(el).render(<Component {...props} />)`)
151
- *
152
- * @example
153
- * ```ts
154
- * import { hydrateAll } from "ziex/react";
155
- *
156
- * hydrateAll({
157
- * CounterComponent: () => import("./Counter"),
158
- * ToggleComponent: () => import("./Toggle"),
159
- * });
160
- * ```
161
- */
162
21
  export declare function hydrateAll(registry: ComponentRegistry, render: (container: HTMLElement, Component: (props: any) => React.ReactElement, props: Record<string, any>) => void): Promise<void>;
package/react/index.js CHANGED
@@ -72,6 +72,9 @@ function discoverComponents() {
72
72
  if (spaceIdx !== -1) {
73
73
  const id = text.slice(1, spaceIdx);
74
74
  const content = text.slice(spaceIdx + 1);
75
+ if (content.startsWith("[")) {
76
+ continue;
77
+ }
75
78
  const jsonStart = content.indexOf("{");
76
79
  let name = "";
77
80
  let props = {};
package/wasm/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ZigJS } from "../../../../vendor/jsz/js/src";
2
2
  /**
3
3
  * ZX Client Bridge - Unified JS↔WASM communication layer
4
- * Handles events, fetch, timers, and other async callbacks using jsz
4
+ * Handles events, fetch, WebSocket, timers, and other async callbacks using jsz
5
5
  */
6
6
  export declare const CallbackType: {
7
7
  readonly Event: 0;
@@ -9,6 +9,10 @@ export declare const CallbackType: {
9
9
  readonly FetchError: 2;
10
10
  readonly Timeout: 3;
11
11
  readonly Interval: 4;
12
+ readonly WebSocketOpen: 5;
13
+ readonly WebSocketMessage: 6;
14
+ readonly WebSocketError: 7;
15
+ readonly WebSocketClose: 8;
12
16
  };
13
17
  export type CallbackTypeValue = typeof CallbackType[keyof typeof CallbackType];
14
18
  export declare const jsz: ZigJS;
@@ -29,6 +33,15 @@ export declare class ZxBridge {
29
33
  setInterval(callbackId: bigint, intervalMs: number): void;
30
34
  /** Clear an interval */
31
35
  clearInterval(callbackId: bigint): void;
36
+ /**
37
+ * Create and connect a WebSocket.
38
+ * Calls __zx_ws_onopen, __zx_ws_onmessage, __zx_ws_onerror, __zx_ws_onclose.
39
+ */
40
+ wsConnect(wsId: bigint, urlPtr: number, urlLen: number, protocolsPtr: number, protocolsLen: number): void;
41
+ /** Send data over WebSocket */
42
+ wsSend(wsId: bigint, dataPtr: number, dataLen: number, isBinary: number): void;
43
+ /** Close WebSocket connection */
44
+ wsClose(wsId: bigint, code: number, reasonPtr: number, reasonLen: number): void;
32
45
  /** Handle a DOM event (called by event delegation) */
33
46
  eventbridge(velementId: bigint, eventTypeId: number, event: Event): void;
34
47
  /** Create the import object for WASM instantiation */
package/wasm/index.js CHANGED
@@ -173,7 +173,11 @@ var CallbackType = {
173
173
  FetchSuccess: 1,
174
174
  FetchError: 2,
175
175
  Timeout: 3,
176
- Interval: 4
176
+ Interval: 4,
177
+ WebSocketOpen: 5,
178
+ WebSocketMessage: 6,
179
+ WebSocketError: 7,
180
+ WebSocketClose: 8
177
181
  };
178
182
  var jsz = new ZigJS;
179
183
  var tempRefBuffer = new ArrayBuffer(8);
@@ -197,6 +201,7 @@ function writeBytes(ptr, data) {
197
201
  class ZxBridge {
198
202
  #exports;
199
203
  #intervals = new Map;
204
+ #websockets = new Map;
200
205
  constructor(exports) {
201
206
  this.#exports = exports;
202
207
  }
@@ -206,6 +211,18 @@ class ZxBridge {
206
211
  get #fetchCompleteHandler() {
207
212
  return this.#exports.__zx_fetch_complete;
208
213
  }
214
+ get #wsOnOpenHandler() {
215
+ return this.#exports.__zx_ws_onopen;
216
+ }
217
+ get #wsOnMessageHandler() {
218
+ return this.#exports.__zx_ws_onmessage;
219
+ }
220
+ get #wsOnErrorHandler() {
221
+ return this.#exports.__zx_ws_onerror;
222
+ }
223
+ get #wsOnCloseHandler() {
224
+ return this.#exports.__zx_ws_onclose;
225
+ }
209
226
  #invoke(type, id, data) {
210
227
  const handler = this.#handler;
211
228
  if (!handler) {
@@ -289,6 +306,105 @@ class ZxBridge {
289
306
  this.#intervals.delete(callbackId);
290
307
  }
291
308
  }
309
+ wsConnect(wsId, urlPtr, urlLen, protocolsPtr, protocolsLen) {
310
+ const url = readString(urlPtr, urlLen);
311
+ const protocolsStr = protocolsLen > 0 ? readString(protocolsPtr, protocolsLen) : "";
312
+ const protocols = protocolsStr ? protocolsStr.split(",").map((p) => p.trim()).filter(Boolean) : undefined;
313
+ try {
314
+ const ws = protocols && protocols.length > 0 ? new WebSocket(url, protocols) : new WebSocket(url);
315
+ ws.binaryType = "arraybuffer";
316
+ ws.onopen = () => {
317
+ const handler = this.#wsOnOpenHandler;
318
+ if (!handler)
319
+ return;
320
+ const protocol = ws.protocol || "";
321
+ const { ptr, len } = this.#writeStringToWasm(protocol);
322
+ handler(wsId, ptr, len);
323
+ };
324
+ ws.onmessage = (event) => {
325
+ const handler = this.#wsOnMessageHandler;
326
+ if (!handler)
327
+ return;
328
+ const isBinary = event.data instanceof ArrayBuffer;
329
+ let data;
330
+ if (isBinary) {
331
+ data = new Uint8Array(event.data);
332
+ } else {
333
+ data = new TextEncoder().encode(event.data);
334
+ }
335
+ const { ptr, len } = this.#writeBytesToWasm(data);
336
+ handler(wsId, ptr, len, isBinary ? 1 : 0);
337
+ };
338
+ ws.onerror = (event) => {
339
+ const handler = this.#wsOnErrorHandler;
340
+ if (!handler)
341
+ return;
342
+ const msg = "WebSocket error";
343
+ const { ptr, len } = this.#writeStringToWasm(msg);
344
+ handler(wsId, ptr, len);
345
+ };
346
+ ws.onclose = (event) => {
347
+ const handler = this.#wsOnCloseHandler;
348
+ if (!handler)
349
+ return;
350
+ const reason = event.reason || "";
351
+ const { ptr, len } = this.#writeStringToWasm(reason);
352
+ handler(wsId, event.code, ptr, len, event.wasClean ? 1 : 0);
353
+ this.#websockets.delete(wsId);
354
+ };
355
+ this.#websockets.set(wsId, ws);
356
+ } catch (error) {
357
+ const handler = this.#wsOnErrorHandler;
358
+ if (handler) {
359
+ const msg = error instanceof Error ? error.message : "WebSocket connection failed";
360
+ const { ptr, len } = this.#writeStringToWasm(msg);
361
+ handler(wsId, ptr, len);
362
+ }
363
+ }
364
+ }
365
+ wsSend(wsId, dataPtr, dataLen, isBinary) {
366
+ const ws = this.#websockets.get(wsId);
367
+ if (!ws || ws.readyState !== WebSocket.OPEN)
368
+ return;
369
+ const memory = new Uint8Array(jsz.memory.buffer);
370
+ const data = memory.slice(dataPtr, dataPtr + dataLen);
371
+ if (isBinary) {
372
+ ws.send(data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength));
373
+ } else {
374
+ ws.send(new TextDecoder().decode(data));
375
+ }
376
+ }
377
+ wsClose(wsId, code, reasonPtr, reasonLen) {
378
+ const ws = this.#websockets.get(wsId);
379
+ if (!ws)
380
+ return;
381
+ const reason = reasonLen > 0 ? readString(reasonPtr, reasonLen) : undefined;
382
+ try {
383
+ if (reason) {
384
+ ws.close(code, reason);
385
+ } else {
386
+ ws.close(code);
387
+ }
388
+ } catch {
389
+ ws.close();
390
+ }
391
+ }
392
+ #writeStringToWasm(str) {
393
+ const encoded = new TextEncoder().encode(str);
394
+ return this.#writeBytesToWasm(encoded);
395
+ }
396
+ #writeBytesToWasm(data) {
397
+ const allocFn = this.#exports.__zx_alloc;
398
+ let ptr = 0;
399
+ if (allocFn) {
400
+ ptr = allocFn(data.length);
401
+ } else {
402
+ const heapBase = this.#exports.__heap_base?.value ?? 65536;
403
+ ptr = heapBase + Date.now() % 256 * 4096;
404
+ }
405
+ writeBytes(ptr, data);
406
+ return { ptr, len: data.length };
407
+ }
292
408
  eventbridge(velementId, eventTypeId, event) {
293
409
  const eventRef = storeValueGetRef(event);
294
410
  const eventbridge = this.#exports.__zx_eventbridge;
@@ -310,6 +426,15 @@ class ZxBridge {
310
426
  },
311
427
  _clearInterval: (callbackId) => {
312
428
  bridgeRef.current?.clearInterval(callbackId);
429
+ },
430
+ _wsConnect: (wsId, urlPtr, urlLen, protocolsPtr, protocolsLen) => {
431
+ bridgeRef.current?.wsConnect(wsId, urlPtr, urlLen, protocolsPtr, protocolsLen);
432
+ },
433
+ _wsSend: (wsId, dataPtr, dataLen, isBinary) => {
434
+ bridgeRef.current?.wsSend(wsId, dataPtr, dataLen, isBinary);
435
+ },
436
+ _wsClose: (wsId, code, reasonPtr, reasonLen) => {
437
+ bridgeRef.current?.wsClose(wsId, code, reasonPtr, reasonLen);
313
438
  }
314
439
  }
315
440
  };
package/wasm/init.js CHANGED
@@ -173,7 +173,11 @@ var CallbackType = {
173
173
  FetchSuccess: 1,
174
174
  FetchError: 2,
175
175
  Timeout: 3,
176
- Interval: 4
176
+ Interval: 4,
177
+ WebSocketOpen: 5,
178
+ WebSocketMessage: 6,
179
+ WebSocketError: 7,
180
+ WebSocketClose: 8
177
181
  };
178
182
  var jsz = new ZigJS;
179
183
  var tempRefBuffer = new ArrayBuffer(8);
@@ -197,6 +201,7 @@ function writeBytes(ptr, data) {
197
201
  class ZxBridge {
198
202
  #exports;
199
203
  #intervals = new Map;
204
+ #websockets = new Map;
200
205
  constructor(exports) {
201
206
  this.#exports = exports;
202
207
  }
@@ -206,6 +211,18 @@ class ZxBridge {
206
211
  get #fetchCompleteHandler() {
207
212
  return this.#exports.__zx_fetch_complete;
208
213
  }
214
+ get #wsOnOpenHandler() {
215
+ return this.#exports.__zx_ws_onopen;
216
+ }
217
+ get #wsOnMessageHandler() {
218
+ return this.#exports.__zx_ws_onmessage;
219
+ }
220
+ get #wsOnErrorHandler() {
221
+ return this.#exports.__zx_ws_onerror;
222
+ }
223
+ get #wsOnCloseHandler() {
224
+ return this.#exports.__zx_ws_onclose;
225
+ }
209
226
  #invoke(type, id, data) {
210
227
  const handler = this.#handler;
211
228
  if (!handler) {
@@ -289,6 +306,105 @@ class ZxBridge {
289
306
  this.#intervals.delete(callbackId);
290
307
  }
291
308
  }
309
+ wsConnect(wsId, urlPtr, urlLen, protocolsPtr, protocolsLen) {
310
+ const url = readString(urlPtr, urlLen);
311
+ const protocolsStr = protocolsLen > 0 ? readString(protocolsPtr, protocolsLen) : "";
312
+ const protocols = protocolsStr ? protocolsStr.split(",").map((p) => p.trim()).filter(Boolean) : undefined;
313
+ try {
314
+ const ws = protocols && protocols.length > 0 ? new WebSocket(url, protocols) : new WebSocket(url);
315
+ ws.binaryType = "arraybuffer";
316
+ ws.onopen = () => {
317
+ const handler = this.#wsOnOpenHandler;
318
+ if (!handler)
319
+ return;
320
+ const protocol = ws.protocol || "";
321
+ const { ptr, len } = this.#writeStringToWasm(protocol);
322
+ handler(wsId, ptr, len);
323
+ };
324
+ ws.onmessage = (event) => {
325
+ const handler = this.#wsOnMessageHandler;
326
+ if (!handler)
327
+ return;
328
+ const isBinary = event.data instanceof ArrayBuffer;
329
+ let data;
330
+ if (isBinary) {
331
+ data = new Uint8Array(event.data);
332
+ } else {
333
+ data = new TextEncoder().encode(event.data);
334
+ }
335
+ const { ptr, len } = this.#writeBytesToWasm(data);
336
+ handler(wsId, ptr, len, isBinary ? 1 : 0);
337
+ };
338
+ ws.onerror = (event) => {
339
+ const handler = this.#wsOnErrorHandler;
340
+ if (!handler)
341
+ return;
342
+ const msg = "WebSocket error";
343
+ const { ptr, len } = this.#writeStringToWasm(msg);
344
+ handler(wsId, ptr, len);
345
+ };
346
+ ws.onclose = (event) => {
347
+ const handler = this.#wsOnCloseHandler;
348
+ if (!handler)
349
+ return;
350
+ const reason = event.reason || "";
351
+ const { ptr, len } = this.#writeStringToWasm(reason);
352
+ handler(wsId, event.code, ptr, len, event.wasClean ? 1 : 0);
353
+ this.#websockets.delete(wsId);
354
+ };
355
+ this.#websockets.set(wsId, ws);
356
+ } catch (error) {
357
+ const handler = this.#wsOnErrorHandler;
358
+ if (handler) {
359
+ const msg = error instanceof Error ? error.message : "WebSocket connection failed";
360
+ const { ptr, len } = this.#writeStringToWasm(msg);
361
+ handler(wsId, ptr, len);
362
+ }
363
+ }
364
+ }
365
+ wsSend(wsId, dataPtr, dataLen, isBinary) {
366
+ const ws = this.#websockets.get(wsId);
367
+ if (!ws || ws.readyState !== WebSocket.OPEN)
368
+ return;
369
+ const memory = new Uint8Array(jsz.memory.buffer);
370
+ const data = memory.slice(dataPtr, dataPtr + dataLen);
371
+ if (isBinary) {
372
+ ws.send(data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength));
373
+ } else {
374
+ ws.send(new TextDecoder().decode(data));
375
+ }
376
+ }
377
+ wsClose(wsId, code, reasonPtr, reasonLen) {
378
+ const ws = this.#websockets.get(wsId);
379
+ if (!ws)
380
+ return;
381
+ const reason = reasonLen > 0 ? readString(reasonPtr, reasonLen) : undefined;
382
+ try {
383
+ if (reason) {
384
+ ws.close(code, reason);
385
+ } else {
386
+ ws.close(code);
387
+ }
388
+ } catch {
389
+ ws.close();
390
+ }
391
+ }
392
+ #writeStringToWasm(str) {
393
+ const encoded = new TextEncoder().encode(str);
394
+ return this.#writeBytesToWasm(encoded);
395
+ }
396
+ #writeBytesToWasm(data) {
397
+ const allocFn = this.#exports.__zx_alloc;
398
+ let ptr = 0;
399
+ if (allocFn) {
400
+ ptr = allocFn(data.length);
401
+ } else {
402
+ const heapBase = this.#exports.__heap_base?.value ?? 65536;
403
+ ptr = heapBase + Date.now() % 256 * 4096;
404
+ }
405
+ writeBytes(ptr, data);
406
+ return { ptr, len: data.length };
407
+ }
292
408
  eventbridge(velementId, eventTypeId, event) {
293
409
  const eventRef = storeValueGetRef(event);
294
410
  const eventbridge = this.#exports.__zx_eventbridge;
@@ -310,6 +426,15 @@ class ZxBridge {
310
426
  },
311
427
  _clearInterval: (callbackId) => {
312
428
  bridgeRef.current?.clearInterval(callbackId);
429
+ },
430
+ _wsConnect: (wsId, urlPtr, urlLen, protocolsPtr, protocolsLen) => {
431
+ bridgeRef.current?.wsConnect(wsId, urlPtr, urlLen, protocolsPtr, protocolsLen);
432
+ },
433
+ _wsSend: (wsId, dataPtr, dataLen, isBinary) => {
434
+ bridgeRef.current?.wsSend(wsId, dataPtr, dataLen, isBinary);
435
+ },
436
+ _wsClose: (wsId, code, reasonPtr, reasonLen) => {
437
+ bridgeRef.current?.wsClose(wsId, code, reasonPtr, reasonLen);
313
438
  }
314
439
  }
315
440
  };