ziex 0.0.1-dev.7 → 0.1.0-dev.1000
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 +146 -104
- package/app.d.ts +124 -0
- package/aws-lambda/index.d.ts +96 -0
- package/aws-lambda/index.js +130 -0
- package/bin/ziex +25 -0
- package/browser/kv.d.ts +13 -0
- package/build.zig +5 -0
- package/build.zig.zon +7 -0
- package/cloudflare/app.d.ts +2 -0
- package/cloudflare/db.d.ts +2 -0
- package/cloudflare/do.d.ts +50 -0
- package/cloudflare/index.d.ts +4 -0
- package/cloudflare/index.js +2163 -0
- package/cloudflare/kv.d.ts +2 -0
- package/cloudflare/worker.d.ts +3 -0
- package/db.d.ts +22 -0
- package/index.d.ts +2 -9
- package/index.js +936 -28
- package/kv.d.ts +42 -0
- package/package.json +24 -5
- package/react/dom.d.ts +9 -123
- package/react/index.d.ts +1 -1
- package/react/index.js +135 -8
- package/react/types.d.ts +28 -31
- package/runtime.d.ts +73 -0
- package/vercel/index.d.ts +26 -0
- package/vercel/index.js +22 -0
- package/wasi.d.ts +61 -0
- package/wasm/core.d.ts +81 -0
- package/wasm/index.d.ts +61 -12
- package/wasm/index.js +1092 -40
- package/wasm/init.d.ts +1 -0
- package/wasm/init.js +1222 -0
- package/wasm/wasi.d.ts +28 -0
- package/zx.d.ts +1 -0
package/kv.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface KVNamespace {
|
|
2
|
+
get(key: string): Promise<string | null>;
|
|
3
|
+
put(key: string, value: string, options?: {
|
|
4
|
+
expiration?: number;
|
|
5
|
+
expirationTtl?: number;
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
delete(key: string): Promise<void>;
|
|
8
|
+
list(options?: {
|
|
9
|
+
prefix?: string;
|
|
10
|
+
}): Promise<{
|
|
11
|
+
keys: {
|
|
12
|
+
name: string;
|
|
13
|
+
}[];
|
|
14
|
+
}>;
|
|
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
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* In-memory KV namespace. Used as the default shim on platforms that don't
|
|
33
|
+
* provide a real KV binding (e.g. Vercel). Data lives only for the lifetime
|
|
34
|
+
* of the isolate instance.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createMemoryKV(): KVNamespace;
|
|
37
|
+
/**
|
|
38
|
+
* Create a `__zx_kv` import object for use with `run({ kv: ... })`.
|
|
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.
|
|
41
|
+
*/
|
|
42
|
+
export declare function createKVImports(bindings: Record<string, KVNamespace>, getMemory: () => WebAssembly.Memory): Record<string, unknown>;
|
package/package.json
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ziex",
|
|
3
|
+
"version": "0.1.0-dev.1000",
|
|
3
4
|
"description": "ZX is a framework for building web applications with Zig.",
|
|
4
5
|
"main": "index.js",
|
|
5
6
|
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ziex": "bin/ziex"
|
|
9
|
+
},
|
|
6
10
|
"exports": {
|
|
7
11
|
".": "./index.js",
|
|
8
12
|
"./react": "./react/index.js",
|
|
9
|
-
"./wasm": "./wasm/index.js"
|
|
13
|
+
"./wasm": "./wasm/index.js",
|
|
14
|
+
"./wasm/init": "./wasm/init.js",
|
|
15
|
+
"./cloudflare": "./cloudflare/index.js",
|
|
16
|
+
"./aws-lambda": "./aws-lambda/index.js",
|
|
17
|
+
"./vercel": "./vercel/index.js"
|
|
10
18
|
},
|
|
11
|
-
"homepage": "https://
|
|
19
|
+
"homepage": "https://ziex.dev",
|
|
12
20
|
"repository": {
|
|
13
21
|
"type": "git",
|
|
14
|
-
"url": "https://github.com/
|
|
22
|
+
"url": "git+https://github.com/ziex-dev/ziex.git"
|
|
15
23
|
},
|
|
16
24
|
"keywords": [
|
|
17
25
|
"zx",
|
|
@@ -27,7 +35,18 @@
|
|
|
27
35
|
],
|
|
28
36
|
"author": "Nurul Huda (Apon) <me@nurulhudaapon.com>",
|
|
29
37
|
"license": "MIT",
|
|
38
|
+
"scripts": {},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@ziex/cli": "0.1.0-dev.1000"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"react": {
|
|
44
|
+
"optional": true
|
|
45
|
+
},
|
|
46
|
+
"react-dom": {
|
|
47
|
+
"optional": true
|
|
48
|
+
}
|
|
49
|
+
},
|
|
30
50
|
"module": "index.js",
|
|
31
|
-
"types": "index.d.ts"
|
|
32
|
-
"version": "0.0.1-dev.7"
|
|
51
|
+
"types": "index.d.ts"
|
|
33
52
|
}
|
package/react/dom.d.ts
CHANGED
|
@@ -1,135 +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 the DOM node that was server-rendered by ZX with the component's unique ID.
|
|
12
|
-
* The element already exists in the DOM and contains the server-rendered fallback content.
|
|
13
|
-
* React will hydrate this element, replacing its contents with the interactive component.
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* ```tsx
|
|
17
|
-
* // The DOM node corresponds to HTML like:
|
|
18
|
-
* // <div id="zx-dcde04c415da9d1b15ca2690d8b497ae" data-props="...">...</div>
|
|
19
|
-
*
|
|
20
|
-
* const { domNode } = await prepareComponent(component);
|
|
21
|
-
* createRoot(domNode).render(<Component {...props} />);
|
|
22
|
-
* ```
|
|
23
|
-
*/
|
|
24
3
|
domNode: HTMLElement;
|
|
25
|
-
/**
|
|
26
|
-
* Component props parsed from the server-rendered HTML.
|
|
27
|
-
*
|
|
28
|
-
* Props are extracted from the `data-props` attribute (JSON-encoded) on the component's
|
|
29
|
-
* container element. If the component has children from server side then they are automatically converted to
|
|
30
|
-
* `dangerouslySetInnerHTML` for React compatibility.
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* ```tsx
|
|
34
|
-
* // Server-rendered HTML:
|
|
35
|
-
* // <div data-props='{"max_count":10,"label":"Counter"}' data-children="<span>0</span>">...</div>
|
|
36
|
-
*
|
|
37
|
-
* const { props } = await prepareComponent(component);
|
|
38
|
-
* // props = {
|
|
39
|
-
* // max_count: 10,
|
|
40
|
-
* // label: "Counter",
|
|
41
|
-
* // dangerouslySetInnerHTML: { __html: "<span>0</span>" }
|
|
42
|
-
* // }
|
|
43
|
-
* ```
|
|
44
|
-
*/
|
|
45
4
|
props: Record<string, any> & {
|
|
46
|
-
/**
|
|
47
|
-
* React's special prop for setting inner HTML directly.
|
|
48
|
-
*
|
|
49
|
-
* Automatically added when the component has children in the ZX file. The HTML string
|
|
50
|
-
* is extracted from the `data-children` attribute on the server-rendered element.
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* ```tsx
|
|
54
|
-
* // In ZX file:
|
|
55
|
-
* <MyComponent @rendering={.csr}>
|
|
56
|
-
* <p>Child content</p>
|
|
57
|
-
* </MyComponent>
|
|
58
|
-
*
|
|
59
|
-
* // Results in:
|
|
60
|
-
* // props.dangerouslySetInnerHTML = { __html: "<p>Child content</p>" }
|
|
61
|
-
* ```
|
|
62
|
-
*/
|
|
63
5
|
dangerouslySetInnerHTML?: {
|
|
64
6
|
__html: string;
|
|
65
7
|
};
|
|
66
8
|
};
|
|
67
|
-
/**
|
|
68
|
-
* The loaded React component function ready to render.
|
|
69
|
-
*
|
|
70
|
-
* This is the default export from the component module, lazy-loaded via the component's
|
|
71
|
-
* import function. The component is ready to be rendered with React's `createRoot().render()`.
|
|
72
|
-
*
|
|
73
|
-
* @example
|
|
74
|
-
* ```tsx
|
|
75
|
-
* const { Component, props, domNode } = await prepareComponent(component);
|
|
76
|
-
*
|
|
77
|
-
* // Component is the default export from the component file:
|
|
78
|
-
* // export default function CounterComponent({ max_count }: { max_count: number }) {
|
|
79
|
-
* // return <div>Count: {max_count}</div>;
|
|
80
|
-
* // }
|
|
81
|
-
*
|
|
82
|
-
* createRoot(domNode).render(<Component {...props} />);
|
|
83
|
-
* ```
|
|
84
|
-
*/
|
|
85
9
|
Component: (props: any) => React.ReactElement;
|
|
86
10
|
};
|
|
87
|
-
/**
|
|
88
|
-
* Prepares a client-side component for hydration by locating its DOM container, extracting
|
|
89
|
-
* props and children from server-rendered HTML attributes, and lazy-loading the component module.
|
|
90
|
-
*
|
|
91
|
-
* This function bridges server-rendered HTML (from ZX's Zig transpiler) and client-side React
|
|
92
|
-
* components. It reads data attributes (`data-props`, `data-children`) from the DOM element
|
|
93
|
-
* with the component's unique ID, then lazy-loads the component module for rendering.
|
|
94
|
-
*
|
|
95
|
-
* @param component - The component metadata containing ID, import function, and other metadata
|
|
96
|
-
* needed to locate and load the component
|
|
97
|
-
*
|
|
98
|
-
* @returns A Promise that resolves to a `PreparedComponent` object containing the DOM node,
|
|
99
|
-
* parsed props, and the loaded React component function
|
|
100
|
-
*
|
|
101
|
-
* @throws {Error} If the component's container element cannot be found in the DOM. This typically
|
|
102
|
-
* happens if the component ID doesn't match any element, the script runs before
|
|
103
|
-
* the HTML is loaded, or there's a mismatch between server and client metadata
|
|
104
|
-
*
|
|
105
|
-
* @example
|
|
106
|
-
* ```tsx
|
|
107
|
-
* // Basic usage with React:
|
|
108
|
-
* import { createRoot } from "react-dom/client";
|
|
109
|
-
* import { prepareComponent } from "ziex";
|
|
110
|
-
* import { components } from "@ziex/components";
|
|
111
|
-
*
|
|
112
|
-
* for (const component of components) {
|
|
113
|
-
* prepareComponent(component).then(({ domNode, Component, props }) => {
|
|
114
|
-
* createRoot(domNode).render(<Component {...props} />);
|
|
115
|
-
* }).catch(console.error);
|
|
116
|
-
* }
|
|
117
|
-
* ```
|
|
118
|
-
*
|
|
119
|
-
* @example
|
|
120
|
-
* ```tsx
|
|
121
|
-
* // With async/await:
|
|
122
|
-
* async function hydrateComponent(component: ComponentMetadata) {
|
|
123
|
-
* try {
|
|
124
|
-
* const { domNode, Component, props } = await prepareComponent(component);
|
|
125
|
-
* createRoot(domNode).render(<Component {...props} />);
|
|
126
|
-
* } catch (error) {
|
|
127
|
-
* console.error(`Failed to hydrate ${component.name}:`, error);
|
|
128
|
-
* }
|
|
129
|
-
* }
|
|
130
|
-
*
|
|
131
|
-
* Promise.all(components.map(hydrateComponent));
|
|
132
|
-
* ```
|
|
133
|
-
*/
|
|
134
11
|
export declare function prepareComponent(component: ComponentMetadata): Promise<PreparedComponent>;
|
|
135
12
|
export declare function filterComponents(components: ComponentMetadata[]): ComponentMetadata[];
|
|
13
|
+
export type DiscoveredComponent = {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
props: Record<string, any>;
|
|
17
|
+
container: HTMLElement;
|
|
18
|
+
};
|
|
19
|
+
export declare function discoverComponents(): DiscoveredComponent[];
|
|
20
|
+
export type ComponentRegistry = Record<string, () => Promise<(props: any) => React.ReactElement>>;
|
|
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.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { prepareComponent, filterComponents, type PreparedComponent } from "./dom";
|
|
1
|
+
export { prepareComponent, filterComponents, discoverComponents, hydrateAll, type PreparedComponent, type DiscoveredComponent, type ComponentRegistry, } from "./dom";
|
|
2
2
|
export type { ComponentMetadata } from "./types";
|
package/react/index.js
CHANGED
|
@@ -1,13 +1,73 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __returnValue = (v) => v;
|
|
3
|
+
function __exportSetter(name, newValue) {
|
|
4
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
5
|
+
}
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, {
|
|
9
|
+
get: all[name],
|
|
10
|
+
enumerable: true,
|
|
11
|
+
configurable: true,
|
|
12
|
+
set: __exportSetter.bind(all, name)
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
|
|
1
16
|
// src/react/dom.ts
|
|
17
|
+
function findCommentMarker(id) {
|
|
18
|
+
const startPrefix = `$${id} `;
|
|
19
|
+
const endMarker = `/$${id}`;
|
|
20
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT, null);
|
|
21
|
+
let startComment = null;
|
|
22
|
+
let endComment = null;
|
|
23
|
+
let name = "";
|
|
24
|
+
let props = {};
|
|
25
|
+
let node;
|
|
26
|
+
while (node = walker.nextNode()) {
|
|
27
|
+
const text = node.textContent?.trim() || "";
|
|
28
|
+
if (text.startsWith(startPrefix)) {
|
|
29
|
+
startComment = node;
|
|
30
|
+
const content = text.slice(startPrefix.length);
|
|
31
|
+
const jsonStart = content.indexOf("{");
|
|
32
|
+
if (jsonStart !== -1) {
|
|
33
|
+
name = content.slice(0, jsonStart).trim();
|
|
34
|
+
const jsonStr = content.slice(jsonStart);
|
|
35
|
+
try {
|
|
36
|
+
props = JSON.parse(jsonStr);
|
|
37
|
+
} catch {}
|
|
38
|
+
} else {
|
|
39
|
+
name = content.trim();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (text === endMarker) {
|
|
43
|
+
endComment = node;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (startComment && endComment) {
|
|
48
|
+
return { startComment, endComment, name, props };
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
function createContainerBetweenMarkers(startComment, endComment) {
|
|
53
|
+
const container = document.createElement("div");
|
|
54
|
+
container.style.display = "contents";
|
|
55
|
+
let current = startComment.nextSibling;
|
|
56
|
+
while (current && current !== endComment) {
|
|
57
|
+
const next = current.nextSibling;
|
|
58
|
+
container.appendChild(current);
|
|
59
|
+
current = next;
|
|
60
|
+
}
|
|
61
|
+
endComment.parentNode?.insertBefore(container, endComment);
|
|
62
|
+
return container;
|
|
63
|
+
}
|
|
2
64
|
async function prepareComponent(component) {
|
|
3
|
-
const
|
|
4
|
-
if (!
|
|
5
|
-
throw new Error(`
|
|
6
|
-
const props = JSON.parse(domNode.getAttribute("data-props") || "{}");
|
|
7
|
-
const htmlChildren = domNode.getAttribute("data-children") ?? undefined;
|
|
8
|
-
if (htmlChildren) {
|
|
9
|
-
props.dangerouslySetInnerHTML = { __html: htmlChildren };
|
|
65
|
+
const marker = findCommentMarker(component.id);
|
|
66
|
+
if (!marker) {
|
|
67
|
+
throw new Error(`Comment marker for ${component.id} not found`, { cause: component });
|
|
10
68
|
}
|
|
69
|
+
const props = marker.props;
|
|
70
|
+
const domNode = createContainerBetweenMarkers(marker.startComment, marker.endComment);
|
|
11
71
|
const Component = await component.import();
|
|
12
72
|
return { domNode, props, Component };
|
|
13
73
|
}
|
|
@@ -15,7 +75,74 @@ function filterComponents(components) {
|
|
|
15
75
|
const currentPath = window.location.pathname;
|
|
16
76
|
return components.filter((component) => component.route === currentPath || !component.route);
|
|
17
77
|
}
|
|
78
|
+
function discoverComponents() {
|
|
79
|
+
const components = [];
|
|
80
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT, null);
|
|
81
|
+
const markers = [];
|
|
82
|
+
let node;
|
|
83
|
+
while (node = walker.nextNode()) {
|
|
84
|
+
const text = node.textContent?.trim() || "";
|
|
85
|
+
if (text.startsWith("$") && !text.startsWith("/$")) {
|
|
86
|
+
const spaceIdx = text.indexOf(" ");
|
|
87
|
+
if (spaceIdx !== -1) {
|
|
88
|
+
const id = text.slice(1, spaceIdx);
|
|
89
|
+
const content = text.slice(spaceIdx + 1);
|
|
90
|
+
if (content.startsWith("[")) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const jsonStart = content.indexOf("{");
|
|
94
|
+
let name = "";
|
|
95
|
+
let props = {};
|
|
96
|
+
if (jsonStart !== -1) {
|
|
97
|
+
name = content.slice(0, jsonStart).trim();
|
|
98
|
+
try {
|
|
99
|
+
props = JSON.parse(content.slice(jsonStart));
|
|
100
|
+
} catch {}
|
|
101
|
+
} else {
|
|
102
|
+
name = content.trim();
|
|
103
|
+
}
|
|
104
|
+
markers.push({ id, name, props, startComment: node, endComment: null });
|
|
105
|
+
}
|
|
106
|
+
} else if (text.startsWith("/$")) {
|
|
107
|
+
const id = text.slice(2);
|
|
108
|
+
const marker = markers.find((m) => m.id === id && !m.endComment);
|
|
109
|
+
if (marker) {
|
|
110
|
+
marker.endComment = node;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
for (const marker of markers) {
|
|
115
|
+
if (!marker.endComment)
|
|
116
|
+
continue;
|
|
117
|
+
const container = createContainerBetweenMarkers(marker.startComment, marker.endComment);
|
|
118
|
+
components.push({
|
|
119
|
+
id: marker.id,
|
|
120
|
+
name: marker.name,
|
|
121
|
+
props: marker.props,
|
|
122
|
+
container
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return components;
|
|
126
|
+
}
|
|
127
|
+
async function hydrateAll(registry, render) {
|
|
128
|
+
const components = discoverComponents();
|
|
129
|
+
await Promise.all(components.map(async ({ name, props, container }) => {
|
|
130
|
+
const importer = registry[name];
|
|
131
|
+
if (!importer) {
|
|
132
|
+
console.warn(`Component "${name}" not found in registry`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const Component = await importer();
|
|
137
|
+
render(container, Component, props);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error(`Failed to hydrate "${name}":`, error);
|
|
140
|
+
}
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
18
143
|
export {
|
|
19
144
|
prepareComponent,
|
|
20
|
-
|
|
145
|
+
hydrateAll,
|
|
146
|
+
filterComponents,
|
|
147
|
+
discoverComponents
|
|
21
148
|
};
|
package/react/types.d.ts
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
* Metadata for a client-side component used within a ZX file.
|
|
3
3
|
*
|
|
4
4
|
* This type represents the metadata for components that are marked with the `@rendering` attribute
|
|
5
|
-
* in ZX files. When a component is declared with `@rendering={.
|
|
5
|
+
* in ZX files. When a component is declared with `@rendering={.react}` or `@rendering={.client}` in a
|
|
6
6
|
* `.zx` file, the ZX transpiler generates a `ComponentMetadata` entry that is included in the
|
|
7
7
|
* generated `components` array.
|
|
8
8
|
*
|
|
9
9
|
* @example
|
|
10
10
|
* ```tsx
|
|
11
11
|
* // In a ZX file (page.zx):
|
|
12
|
-
* <CounterComponent @rendering={.
|
|
12
|
+
* <CounterComponent @rendering={.react} max_count={10} />
|
|
13
13
|
*
|
|
14
14
|
* // Generated components array (components.ts):
|
|
15
15
|
* export const components: ComponentMetadata[] = [
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* name: "CounterComponent",
|
|
18
18
|
* path: "./components/CounterComponent.tsx",
|
|
19
19
|
* id: "zx-dcde04c415da9d1b15ca2690d8b497ae",
|
|
20
|
-
* type: "
|
|
20
|
+
* type: "react",
|
|
21
21
|
* import: () => import('./components/CounterComponent.tsx')
|
|
22
22
|
* }
|
|
23
23
|
* ];
|
|
@@ -44,7 +44,7 @@ export type ComponentMetadata = {
|
|
|
44
44
|
* @example
|
|
45
45
|
* ```tsx
|
|
46
46
|
* // In ZX file:
|
|
47
|
-
* <CounterComponent @rendering={.
|
|
47
|
+
* <CounterComponent @rendering={.react} />
|
|
48
48
|
*
|
|
49
49
|
* // name will be: "CounterComponent"
|
|
50
50
|
* ```
|
|
@@ -54,7 +54,7 @@ export type ComponentMetadata = {
|
|
|
54
54
|
* The file path to the component module.
|
|
55
55
|
*
|
|
56
56
|
* This is the relative or absolute path to the component file that will be dynamically imported
|
|
57
|
-
* at runtime. For CSR components, this typically points to a `.tsx` or `.jsx` file. For
|
|
57
|
+
* at runtime. For CSR components, this typically points to a `.tsx` or `.jsx` file. For Client
|
|
58
58
|
* components, this points to a Zig component file.
|
|
59
59
|
*
|
|
60
60
|
* The path is determined from the `@jsImport` directive in the ZX file, or defaults to
|
|
@@ -64,7 +64,7 @@ export type ComponentMetadata = {
|
|
|
64
64
|
* ```tsx
|
|
65
65
|
* // In ZX file:
|
|
66
66
|
* const CounterComponent = @jsImport("components/Counter.tsx");
|
|
67
|
-
* <CounterComponent @rendering={.
|
|
67
|
+
* <CounterComponent @rendering={.react} />
|
|
68
68
|
*
|
|
69
69
|
* // path will be: "components/Counter.tsx"
|
|
70
70
|
* ```
|
|
@@ -72,7 +72,7 @@ export type ComponentMetadata = {
|
|
|
72
72
|
* @example
|
|
73
73
|
* ```tsx
|
|
74
74
|
* // Without explicit @jsImport:
|
|
75
|
-
* <MyComponent @rendering={.
|
|
75
|
+
* <MyComponent @rendering={.react} />
|
|
76
76
|
*
|
|
77
77
|
* // path will default to: "./MyComponent.tsx"
|
|
78
78
|
* ```
|
|
@@ -85,17 +85,16 @@ export type ComponentMetadata = {
|
|
|
85
85
|
*/
|
|
86
86
|
route: string | null;
|
|
87
87
|
/**
|
|
88
|
-
* A unique
|
|
88
|
+
* A unique identifier for the component's hydration boundary.
|
|
89
89
|
*
|
|
90
90
|
* This ID is generated by hashing the component's path and name using MD5, then formatting it
|
|
91
|
-
* as a hex string with the "zx-" prefix. The ID is used to locate the
|
|
92
|
-
*
|
|
91
|
+
* as a hex string with the "zx-" prefix and a counter suffix. The ID is used to locate the
|
|
92
|
+
* component's comment markers in the DOM during client-side hydration.
|
|
93
93
|
*
|
|
94
|
-
* The ID format is: `zx-{32 hex characters}` (e.g., `zx-dcde04c415da9d1b15ca2690d8b497ae`)
|
|
94
|
+
* The ID format is: `zx-{32 hex characters}-{counter}` (e.g., `zx-dcde04c415da9d1b15ca2690d8b497ae-0`)
|
|
95
95
|
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* and `data-children` attributes to hydrate the component with the correct props and children.
|
|
96
|
+
* The ZX runtime renders components with comment markers in the format:
|
|
97
|
+
* `<!--$id name props-->...<!--/$id-->`
|
|
99
98
|
*
|
|
100
99
|
* @example
|
|
101
100
|
* ```tsx
|
|
@@ -103,36 +102,34 @@ export type ComponentMetadata = {
|
|
|
103
102
|
* {
|
|
104
103
|
* name: "CounterComponent",
|
|
105
104
|
* path: "./components/Counter.tsx",
|
|
106
|
-
* id: "zx-dcde04c415da9d1b15ca2690d8b497ae"
|
|
105
|
+
* id: "zx-dcde04c415da9d1b15ca2690d8b497ae-0"
|
|
107
106
|
* }
|
|
108
107
|
*
|
|
109
|
-
* // Generated HTML:
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* <!-- Server-rendered content -->
|
|
114
|
-
* </div>
|
|
108
|
+
* // Generated HTML with comment markers:
|
|
109
|
+
* <!--$zx-dcde04c415da9d1b15ca2690d8b497ae-0 CounterComponent {"max_count":10}-->
|
|
110
|
+
* <button>0</button>
|
|
111
|
+
* <!--/$zx-dcde04c415da9d1b15ca2690d8b497ae-0-->
|
|
115
112
|
* ```
|
|
116
113
|
*/
|
|
117
114
|
id: string;
|
|
118
115
|
/**
|
|
119
116
|
* The rendering type of the component, determining how it will be rendered on the client.
|
|
120
117
|
*
|
|
121
|
-
* - **"
|
|
118
|
+
* - **"react"** (Client Side React): The component is a React component that will be rendered
|
|
122
119
|
* using React's client-side rendering. The component file should export a default React
|
|
123
120
|
* component function. This is the most common type for interactive UI components.
|
|
124
121
|
*
|
|
125
|
-
* - **"
|
|
122
|
+
* - **"client"** (Client Side Zig): The component is a Zig component that will be compiled to
|
|
126
123
|
* WebAssembly and rendered on the client side. This allows you to use Zig's performance
|
|
127
124
|
* and type safety for client-side components.
|
|
128
125
|
*
|
|
129
|
-
* The type is determined by the `@rendering` attribute value in the ZX file (`.
|
|
126
|
+
* The type is determined by the `@rendering` attribute value in the ZX file (`.react` or `.client`).
|
|
130
127
|
*
|
|
131
128
|
* @example
|
|
132
129
|
* ```tsx
|
|
133
130
|
* // CSR component (React):
|
|
134
|
-
* <CounterComponent @rendering={.
|
|
135
|
-
* // type: "
|
|
131
|
+
* <CounterComponent @rendering={.react} max_count={10} />
|
|
132
|
+
* // type: "react"
|
|
136
133
|
*
|
|
137
134
|
* // Component file (CounterComponent.tsx):
|
|
138
135
|
* export default function CounterComponent({ max_count }: { max_count: number }) {
|
|
@@ -142,9 +139,9 @@ export type ComponentMetadata = {
|
|
|
142
139
|
*
|
|
143
140
|
* @example
|
|
144
141
|
* ```tsx
|
|
145
|
-
* //
|
|
146
|
-
* <CounterComponent @rendering={.
|
|
147
|
-
* // type: "
|
|
142
|
+
* // Client component (Zig/WASM):
|
|
143
|
+
* <CounterComponent @rendering={.client} />
|
|
144
|
+
* // type: "client"
|
|
148
145
|
*
|
|
149
146
|
* // Component file (CounterComponent.zig):
|
|
150
147
|
* pub fn CounterComponent(allocator: zx.Allocator) zx.Component {
|
|
@@ -152,7 +149,7 @@ export type ComponentMetadata = {
|
|
|
152
149
|
* }
|
|
153
150
|
* ```
|
|
154
151
|
*/
|
|
155
|
-
type: "
|
|
152
|
+
type: "react" | "client";
|
|
156
153
|
/**
|
|
157
154
|
* A lazy-loading function that dynamically imports the component module.
|
|
158
155
|
*
|
|
@@ -161,7 +158,7 @@ export type ComponentMetadata = {
|
|
|
161
158
|
* by only loading components when they are needed.
|
|
162
159
|
*
|
|
163
160
|
* For CSR components, the imported module should export a default React component.
|
|
164
|
-
* For
|
|
161
|
+
* For Client components, the import mechanism depends on the WASM module structure.
|
|
165
162
|
*
|
|
166
163
|
* The function is called during client-side hydration to load and render the component
|
|
167
164
|
* into its corresponding DOM container element.
|
package/runtime.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { WASI } from "./wasi";
|
|
2
|
+
import type { KVNamespace } from "./kv";
|
|
3
|
+
import type { D1Database } from "./db";
|
|
4
|
+
/** Minimal Durable Object namespace shape needed for WebSocket routing. */
|
|
5
|
+
export type DurableObjectNamespace = {
|
|
6
|
+
idFromName(name: string): unknown;
|
|
7
|
+
get(id: unknown): {
|
|
8
|
+
fetch(req: Request): Promise<Response>;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export type WsState = {
|
|
12
|
+
upgraded: boolean;
|
|
13
|
+
server: WebSocket | null;
|
|
14
|
+
pendingWrites: Uint8Array[];
|
|
15
|
+
messageQueue: Uint8Array[];
|
|
16
|
+
recvResolve: ((bytes: Uint8Array | null) => void) | null;
|
|
17
|
+
/** Resolved when ws_recv is called for the first time (WASM has entered the message loop). */
|
|
18
|
+
_resolveFirstSuspend?: () => void;
|
|
19
|
+
subscribe?: (topic: string) => void;
|
|
20
|
+
unsubscribe?: (topic: string) => void;
|
|
21
|
+
publish?: (topic: string, data: Uint8Array) => number;
|
|
22
|
+
isSubscribed?: (topic: string) => boolean;
|
|
23
|
+
};
|
|
24
|
+
/** Build the __zx_ws import object for a given connection state. */
|
|
25
|
+
export declare function buildWsImports(Suspending: any, mem: () => WebAssembly.Memory, decoder: TextDecoder, ws: WsState): {
|
|
26
|
+
ws_upgrade: () => void;
|
|
27
|
+
ws_write: (ptr: number, len: number) => void;
|
|
28
|
+
ws_close: (code: number, reason_ptr: number, reason_len: number) => void;
|
|
29
|
+
ws_recv: any;
|
|
30
|
+
ws_subscribe: (ptr: number, len: number) => void;
|
|
31
|
+
ws_unsubscribe: (ptr: number, len: number) => void;
|
|
32
|
+
ws_publish: (topic_ptr: number, topic_len: number, data_ptr: number, data_len: number) => number;
|
|
33
|
+
ws_is_subscribed: (ptr: number, len: number) => number;
|
|
34
|
+
};
|
|
35
|
+
/** Create WebSocketPair, wire message/close listeners, flush pending writes. */
|
|
36
|
+
export declare function attachWebSocket(ws: WsState): {
|
|
37
|
+
client: WebSocket;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Run a WASM module for a single request using JSPI.
|
|
41
|
+
*
|
|
42
|
+
* Pass `kv` as a map of binding names → KV namespaces. The Zig side selects
|
|
43
|
+
* a binding via `zx.kv.scope("name")`; the top-level `zx.kv.*` functions use
|
|
44
|
+
* `"default"`.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* return run({
|
|
49
|
+
* request, env, ctx, module,
|
|
50
|
+
* kv: { default: env.KV, users: env.USERS_KV },
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare function run({ request, env, ctx, module, kv: kvBindings, db: dbBindings, imports, wasi, websocket: doNamespace, }: {
|
|
55
|
+
request: Request;
|
|
56
|
+
env?: unknown;
|
|
57
|
+
ctx?: {
|
|
58
|
+
waitUntil(promise: Promise<unknown>): void;
|
|
59
|
+
};
|
|
60
|
+
module: WebAssembly.Module;
|
|
61
|
+
/** KV namespace bindings — `{ default: env.KV, otherName: env.OTHER_KV }` */
|
|
62
|
+
kv?: Record<string, KVNamespace>;
|
|
63
|
+
/** D1 bindings — `{ default: env.DB, analytics: env.ANALYTICS_DB }` */
|
|
64
|
+
db?: Record<string, D1Database>;
|
|
65
|
+
imports?: (mem: () => WebAssembly.Memory) => Record<string, Record<string, unknown>>;
|
|
66
|
+
wasi?: WASI;
|
|
67
|
+
/**
|
|
68
|
+
* Durable Object namespace to use for WebSocket connections.
|
|
69
|
+
* When provided, WebSocket upgrade requests are automatically forwarded to
|
|
70
|
+
* the DO so that pub/sub works across multiple connected clients.
|
|
71
|
+
*/
|
|
72
|
+
websocket?: DurableObjectNamespace;
|
|
73
|
+
}): Promise<Response>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ziex adapter for Vercel Edge Functions.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { Ziex } from "ziex/cloudflare";
|
|
7
|
+
* import { handle } from "ziex/vercel";
|
|
8
|
+
* import module from "./app.wasm"; // or fetch at runtime
|
|
9
|
+
*
|
|
10
|
+
* const app = new Ziex({ module });
|
|
11
|
+
*
|
|
12
|
+
* export const config = { runtime: "edge" };
|
|
13
|
+
* export default handle(app);
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
type FetchApp = {
|
|
17
|
+
fetch(req: Request, env?: unknown, ctx?: unknown): Promise<Response>;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Wrap a Ziex app as a Vercel Edge Function handler.
|
|
21
|
+
*
|
|
22
|
+
* Returns a standard `(req: Request) => Promise<Response>` function.
|
|
23
|
+
* Vercel's edge runtime calls it directly — just export it as default.
|
|
24
|
+
*/
|
|
25
|
+
export declare function handle(app: FetchApp): (req: Request) => Promise<Response>;
|
|
26
|
+
export {};
|
package/vercel/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __returnValue = (v) => v;
|
|
3
|
+
function __exportSetter(name, newValue) {
|
|
4
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
5
|
+
}
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, {
|
|
9
|
+
get: all[name],
|
|
10
|
+
enumerable: true,
|
|
11
|
+
configurable: true,
|
|
12
|
+
set: __exportSetter.bind(all, name)
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/vercel/index.ts
|
|
17
|
+
function handle(app) {
|
|
18
|
+
return (req) => app.fetch(req);
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
handle
|
|
22
|
+
};
|