mirror of
https://github.com/ziex-dev/ziex.git
synced 2026-07-20 02:29:36 -06:00
refactor: move private fields to _internal struct
This commit is contained in:
parent
6cd0901d4c
commit
08c462ba35
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -369,7 +369,7 @@ jobs:
|
||||
gh release create "$TAG" \
|
||||
--repo "$OWNER/$REPO_NAME" \
|
||||
--title "$TAG" \
|
||||
--notes "Release ${TAG} — synced from [ziex-dev/ziex@${TAG}](https://github.com/ziex-dev/ziex/releases/tag/${TAG})" \
|
||||
--notes "Release ${TAG} - synced from [ziex-dev/ziex@${TAG}](https://github.com/ziex-dev/ziex/releases/tag/${TAG})" \
|
||||
|| echo "Release $TAG may already exist for $REPO_NAME, skipping..."
|
||||
|
||||
cd -
|
||||
|
||||
@ -341,7 +341,7 @@ echo -e "\033[2mResults written to: $RESULTS_FILE\033[0m"
|
||||
# Generate bench.zon from results
|
||||
ZON_FILE="../site/app/pages/bench.zon"
|
||||
{
|
||||
echo "// Auto-generated by bench/bench.sh — do not edit"
|
||||
echo "// Auto-generated by bench/bench.sh - do not edit"
|
||||
echo ".{"
|
||||
tail -n +2 "$RESULTS_FILE" | while IFS=',' read -r fw idle peak build_time img_mb bin_mb cold_ms cpu_avg cpu_peak rps p50 p99; do
|
||||
label=$(get_label "$fw")
|
||||
|
||||
@ -18,7 +18,7 @@ pub fn build(b: *std.Build) !void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
const exclude_lsp = b.option(bool, "exclude-lsp", "Exclude the LSP server to speed up builds") orelse false;
|
||||
const exclude_core_lang = b.option(bool, "exclude-core-lang", "Exclude core language tools (Ast/Parse/sourcemap) — only needed by CLI") orelse false;
|
||||
const exclude_core_lang = b.option(bool, "exclude-core-lang", "Exclude core language tools (Ast/Parse/sourcemap) - only needed by CLI") orelse false;
|
||||
const exclude_db = b.option(bool, "exclude-db", "Exclude database adapter to speed up builds") orelse false;
|
||||
const is_client = b.option(bool, "is-client", "Building for the browser (client)") orelse false;
|
||||
const is_edge = b.option(bool, "is-edge", "Building for a WASI-based edge runtime") orelse false;
|
||||
@ -48,12 +48,8 @@ pub fn build(b: *std.Build) !void {
|
||||
const cachez_dep = b.dependency("cachez", .{ .target = target, .optimize = optimize });
|
||||
const adapters_dep = b.dependency("adapters", .{ .target = target, .optimize = optimize });
|
||||
|
||||
// --- Sub-modules (cached independently) --- //
|
||||
|
||||
// Style module (35K lines, zero internal deps — cached after first compile)
|
||||
// --- Features Module --- //
|
||||
const zx_style_mod = b.addModule("zx_style", .{ .root_source_file = b.path("src/style/root.zig"), .target = target, .optimize = optimize });
|
||||
|
||||
// Core language module (Ast, Parse, sourcemap — only compiled when referenced)
|
||||
const zx_core_lang_mod = b.addModule("zx_core_lang", .{ .root_source_file = b.path("src/core/root.zig"), .target = target, .optimize = optimize });
|
||||
zx_core_lang_mod.addImport("tree_sitter", tree_sitter_dep.module("tree_sitter"));
|
||||
zx_core_lang_mod.addImport("tree_sitter_zx", tree_sitter_zx_dep.module("tree_sitter_zx"));
|
||||
|
||||
@ -73,7 +73,7 @@ export function activateMultilineStringDecorator(
|
||||
),
|
||||
);
|
||||
|
||||
// -- Copy command — strip \\ prefixes when copying from multiline string -- //
|
||||
// -- Copy command - strip \\ prefixes when copying from multiline string -- //
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand(
|
||||
"zx.copyFromMultilineString",
|
||||
@ -81,7 +81,7 @@ export function activateMultilineStringDecorator(
|
||||
),
|
||||
);
|
||||
|
||||
// -- Paste command — auto-prefix pasted text with \\ -- //
|
||||
// -- Paste command - auto-prefix pasted text with \\ -- //
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand(
|
||||
"zx.pasteInMultilineString",
|
||||
@ -89,7 +89,7 @@ export function activateMultilineStringDecorator(
|
||||
),
|
||||
);
|
||||
|
||||
// -- Folding provider — collapse multiline string blocks -- //
|
||||
// -- Folding provider - collapse multiline string blocks -- //
|
||||
context.subscriptions.push(
|
||||
vscode.languages.registerFoldingRangeProvider(
|
||||
[{ language: "zx" }, { language: "zig" }],
|
||||
|
||||
@ -267,7 +267,7 @@
|
||||
]
|
||||
},
|
||||
"zig-multiline-string": {
|
||||
"comment": "Zig multiline string literal lines (\\\\...) — must match before HTML tags",
|
||||
"comment": "Zig multiline string literal lines (\\\\...) - must match before HTML tags",
|
||||
"name": "string.quoted.other.multiline.zig",
|
||||
"match": "^\\s*\\\\\\\\.*$"
|
||||
},
|
||||
|
||||
@ -57,7 +57,7 @@ echo "==> Publishing @ziex/cli* to local registry..."
|
||||
cd "$VERDACCIO_DIR"
|
||||
npm publish --workspaces --access public --tag dev --registry "$REGISTRY" 2>&1
|
||||
|
||||
# Build and publish ziex to local registry (skip on Windows — bun build crashes)
|
||||
# Build and publish ziex to local registry (skip on Windows - bun build crashes)
|
||||
if [ "$IS_WINDOWS" = false ]; then
|
||||
echo "==> Building and publishing ziex to local registry..."
|
||||
cd "$SCRIPT_DIR/ziex"
|
||||
@ -77,7 +77,7 @@ mkdir -p "$SCRIPT_DIR/_check_tmp"
|
||||
cd "$SCRIPT_DIR/_check_tmp"
|
||||
export npm_config_cache="$SCRIPT_DIR/_check_tmp/.npm-cache"
|
||||
|
||||
# Test runner — writes PASS/FAIL to $RESULTS_DIR so tests can run in parallel
|
||||
# Test runner - writes PASS/FAIL to $RESULTS_DIR so tests can run in parallel
|
||||
check() {
|
||||
local name="$1" cmd="$2" expected="$3"
|
||||
local safe_name="${name//[\/[:space:]]/_}"
|
||||
@ -88,7 +88,7 @@ check() {
|
||||
echo " PASS: $name"
|
||||
else
|
||||
echo "FAIL" > "$RESULTS_DIR/$safe_name"
|
||||
echo " FAIL: $name — expected '$expected', got: $output"
|
||||
echo " FAIL: $name - expected '$expected', got: $output"
|
||||
fi
|
||||
}
|
||||
|
||||
@ -106,7 +106,7 @@ check "npx @ziex/cli version" \
|
||||
"$VERSION" &
|
||||
pids+=($!)
|
||||
|
||||
# ziex via npx (skip on Windows — not built)
|
||||
# ziex via npx (skip on Windows - not built)
|
||||
if [ "$IS_WINDOWS" = false ]; then
|
||||
check "npx ziex version" \
|
||||
"npx --yes --registry '$REGISTRY' ziex@dev version" \
|
||||
|
||||
@ -64,7 +64,7 @@ async function main() {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// By now the user has been typing — give the fetch a short grace period
|
||||
// By now the user has been typing - give the fetch a short grace period
|
||||
// in case it hasn't resolved yet, then fall back to the static list.
|
||||
const dynamicTemplates = await Promise.race([
|
||||
templatesFetch,
|
||||
@ -113,7 +113,7 @@ async function main() {
|
||||
s.stop('Replacement failed', 1);
|
||||
}
|
||||
|
||||
// 4. Fix Zig fingerprint — the fingerprint is a u64 packed struct:
|
||||
// 4. Fix Zig fingerprint - the fingerprint is a u64 packed struct:
|
||||
// lower 32 bits = random id, upper 32 bits = CRC-32 of the package name.
|
||||
s.start('Updating package fingerprint...');
|
||||
try {
|
||||
|
||||
@ -25,7 +25,7 @@ describe("fingerprint", () => {
|
||||
const zigFp = zigGenerate(name);
|
||||
const nodeFp = generateZigFingerprint(name);
|
||||
|
||||
// Extract checksum (upper 32 bits) from both — the random id will differ
|
||||
// Extract checksum (upper 32 bits) from both - the random id will differ
|
||||
const zigChecksum = BigInt(zigFp) >> 32n;
|
||||
const nodeChecksum = BigInt(nodeFp) >> 32n;
|
||||
expect(nodeChecksum).toBe(zigChecksum);
|
||||
|
||||
@ -63,7 +63,7 @@ async function main() {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// By now the user has been typing — give the fetch a short grace period
|
||||
// By now the user has been typing - give the fetch a short grace period
|
||||
// in case it hasn't resolved yet, then fall back to the static list.
|
||||
const dynamicTemplates = await Promise.race([
|
||||
templatesFetch,
|
||||
@ -112,7 +112,7 @@ async function main() {
|
||||
s.stop('Replacement failed', 1);
|
||||
}
|
||||
|
||||
// 4. Fix Zig fingerprint — Zig reports the correct value when the package
|
||||
// 4. Fix Zig fingerprint - Zig reports the correct value when the package
|
||||
// name changes; we capture it and patch build.zig.zon automatically.
|
||||
s.start('Updating package fingerprint...');
|
||||
try {
|
||||
|
||||
@ -36,7 +36,7 @@ fn innerInitSingle(b: *std.Build, build_item: Build) !Output {
|
||||
var arena = std.heap.ArenaAllocator.init(b.allocator);
|
||||
const alloc = arena.allocator();
|
||||
|
||||
// Create config for single build (outdir is NOT in config — injected by exe from CLI arg)
|
||||
// Create config for single build (outdir is NOT in config - injected by exe from CLI arg)
|
||||
var obj = std.json.ObjectMap.init(alloc);
|
||||
try obj.put("name", .{ .string = build_item.name orelse "bun build" });
|
||||
const config_val = try build_item.config.toJsonValue(b, alloc);
|
||||
|
||||
@ -23,7 +23,7 @@ sourcemap: ?Sourcemap = null,
|
||||
/// Minify the output (all sub-options at once)
|
||||
minify: ?bool = null,
|
||||
|
||||
/// External packages — not bundled
|
||||
/// External packages - not bundled
|
||||
external: []const []const u8 = &.{},
|
||||
|
||||
/// Public path prefix for asset URLs
|
||||
@ -40,7 +40,7 @@ splitting: ?bool = null,
|
||||
pub fn toJsonValue(self: BunBuildConfig, b: *std.Build, arena: std.mem.Allocator) !std.json.Value {
|
||||
var obj = std.json.ObjectMap.init(arena);
|
||||
|
||||
// entrypoints — required array
|
||||
// entrypoints - required array
|
||||
var eps = std.json.Array.init(arena);
|
||||
for (self.entrypoints) |lp| {
|
||||
try eps.append(.{ .string = lp.getPath(b) });
|
||||
|
||||
@ -41,7 +41,7 @@ fn innerInitSingle(b: *std.Build, build_item: Build) !Output {
|
||||
var arena = std.heap.ArenaAllocator.init(b.allocator);
|
||||
const alloc = arena.allocator();
|
||||
|
||||
// Create config for single build (output is NOT in config — injected by exe from CLI arg)
|
||||
// Create config for single build (output is NOT in config - injected by exe from CLI arg)
|
||||
var obj = std.json.ObjectMap.init(alloc);
|
||||
try obj.put("name", .{ .string = build_item.name orelse "tailwindcss" });
|
||||
const config_val = try build_item.config.toJsonValue(b, alloc);
|
||||
|
||||
@ -6,11 +6,11 @@ import type { WASI } from "./wasi";
|
||||
|
||||
/**
|
||||
* Anything that can be resolved to a `WebAssembly.Module`:
|
||||
* - `WebAssembly.Module` — already compiled (Cloudflare Workers, wrangler)
|
||||
* - `ArrayBuffer` / `ArrayBufferView` — raw WASM bytes
|
||||
* - `Response` — a fetch() response whose body is the WASM binary
|
||||
* - `string` — an HTTP(S) URL or an absolute file path (Bun)
|
||||
* - `URL` — a URL object
|
||||
* - `WebAssembly.Module` - already compiled (Cloudflare Workers, wrangler)
|
||||
* - `ArrayBuffer` / `ArrayBufferView` - raw WASM bytes
|
||||
* - `Response` - a fetch() response whose body is the WASM binary
|
||||
* - `string` - an HTTP(S) URL or an absolute file path (Bun)
|
||||
* - `URL` - a URL object
|
||||
*/
|
||||
export type WasmInput =
|
||||
| WebAssembly.Module
|
||||
@ -22,7 +22,7 @@ export type WasmInput =
|
||||
|
||||
/**
|
||||
* Resolve any supported WASM input to a compiled `WebAssembly.Module`.
|
||||
* The result is NOT cached here — cache it at the call site if needed.
|
||||
* The result is NOT cached here - cache it at the call site if needed.
|
||||
*/
|
||||
export async function resolveModule(input: WasmInput): Promise<WebAssembly.Module> {
|
||||
if (typeof input === 'string') {
|
||||
@ -49,7 +49,7 @@ export async function resolveModule(input: WasmInput): Promise<WebAssembly.Modul
|
||||
}
|
||||
|
||||
// Anything else is assumed to be an already-compiled WebAssembly.Module.
|
||||
// Intentionally avoid `instanceof WebAssembly.Module` — it fails across
|
||||
// Intentionally avoid `instanceof WebAssembly.Module` - it fails across
|
||||
// different VM contexts (e.g. Vercel's edge runtime simulator).
|
||||
return input as unknown as WebAssembly.Module;
|
||||
}
|
||||
@ -62,7 +62,7 @@ type DOKey<Env> = { [K in keyof Env]: Env[K] extends DurableObjectNamespace ? K
|
||||
type DBKey<Env> = { [K in keyof Env]: Env[K] extends D1Database ? K : never }[keyof Env];
|
||||
|
||||
type ZiexOptions<Env> = {
|
||||
/** WASM module — accepts any {@link WasmInput}. Resolved and cached on first request. */
|
||||
/** WASM module - accepts any {@link WasmInput}. Resolved and cached on first request. */
|
||||
module: WasmInput;
|
||||
/** Optional pre-configured WASI instance. */
|
||||
wasi?: WASI;
|
||||
@ -71,8 +71,8 @@ type ZiexOptions<Env> = {
|
||||
/**
|
||||
* KV namespace bindings. Two forms are supported:
|
||||
*
|
||||
* - **Env key**: a single key from `Env` whose value is a `KVNamespace` — used as the `"default"` binding.
|
||||
* - **Name map**: `{ [bindingName]: envKey }` — map namespace names to env keys.
|
||||
* - **Env key**: a single key from `Env` whose value is a `KVNamespace` - used as the `"default"` binding.
|
||||
* - **Name map**: `{ [bindingName]: envKey }` - map namespace names to env keys.
|
||||
*
|
||||
* @example Single env key (becomes the "default" binding)
|
||||
* ```ts
|
||||
@ -104,7 +104,7 @@ type ZiexOptions<Env> = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Main Ziex application class. Mirrors the Hono API style — construct once,
|
||||
* Main Ziex application class. Mirrors the Hono API style - construct once,
|
||||
* export as default, and the runtime calls `fetch` for you.
|
||||
*
|
||||
* Works on Cloudflare Workers, Bun, and Vercel Edge out of the box.
|
||||
@ -156,14 +156,14 @@ export class Ziex<Env = Record<string, unknown>> {
|
||||
const { kv } = this.options;
|
||||
if (kv === undefined) return undefined;
|
||||
if (typeof kv === 'object' && kv !== null) {
|
||||
// { [name]: envKey } — map of namespace names to env keys
|
||||
// { [name]: envKey } - map of namespace names to env keys
|
||||
const result: Record<string, KVNamespace> = {};
|
||||
for (const [name, key] of Object.entries(kv)) {
|
||||
result[name] = env[key as keyof Env] as unknown as KVNamespace;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// Single env key — becomes the "default" binding
|
||||
// Single env key - becomes the "default" binding
|
||||
return { default: env[kv as keyof Env] as unknown as KVNamespace };
|
||||
}
|
||||
|
||||
|
||||
@ -68,7 +68,7 @@ export function buildWsImports(
|
||||
ws.recvResolve = (bytes) => resolve(deliver(bytes));
|
||||
});
|
||||
}) : (_buf_ptr: number, _buf_max: number): number => -1,
|
||||
// Pub/sub — delegates to optional callbacks (real in DO, no-ops otherwise)
|
||||
// Pub/sub - delegates to optional callbacks (real in DO, no-ops otherwise)
|
||||
ws_subscribe: (ptr: number, len: number): void => { ws.subscribe?.(readStr(ptr, len)); },
|
||||
ws_unsubscribe: (ptr: number, len: number): void => { ws.unsubscribe?.(readStr(ptr, len)); },
|
||||
ws_publish: (topic_ptr: number, topic_len: number, data_ptr: number, data_len: number): number => {
|
||||
@ -147,7 +147,7 @@ function executeWasm(
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// NOTE: no await — start() runs synchronously until the first Suspending
|
||||
// NOTE: no await - start() runs synchronously until the first Suspending
|
||||
// call, writing __EDGE_META__ to stderr and the HTML shell to stdout.
|
||||
// The runtime streams stdout to the client while WASM is suspended.
|
||||
const start = (WebAssembly as any).promising(instance.exports._start as Function);
|
||||
@ -218,9 +218,9 @@ export async function run({
|
||||
env?: unknown;
|
||||
ctx?: { waitUntil(promise: Promise<unknown>): void };
|
||||
module: WebAssembly.Module;
|
||||
/** KV namespace bindings — `{ default: env.KV, otherName: env.OTHER_KV }` */
|
||||
/** KV namespace bindings - `{ default: env.KV, otherName: env.OTHER_KV }` */
|
||||
kv?: Record<string, KVNamespace>;
|
||||
/** D1 bindings — `{ default: env.DB, analytics: env.ANALYTICS_DB }` */
|
||||
/** D1 bindings - `{ default: env.DB, analytics: env.ANALYTICS_DB }` */
|
||||
db?: Record<string, D1Database>;
|
||||
imports?: (mem: () => WebAssembly.Memory) => Record<string, Record<string, unknown>>;
|
||||
wasi?: WASI;
|
||||
@ -304,7 +304,7 @@ export async function run({
|
||||
const earlyMeta = parseEdgeMeta(earlyStderrText);
|
||||
|
||||
if (earlyMeta.streaming) {
|
||||
// Page opted into streaming (zx.PageOptions{ .streaming = true }) —
|
||||
// Page opted into streaming (zx.PageOptions{ .streaming = true }) -
|
||||
// pipe stdout incrementally to the client as WASM yields via sleep_ms.
|
||||
const { readable, writable } = new TransformStream<Uint8Array, Uint8Array>();
|
||||
streamWriter = writable.getWriter();
|
||||
|
||||
@ -20,7 +20,7 @@ type FetchApp = { fetch(req: Request, env?: unknown, ctx?: unknown): Promise<Res
|
||||
* Wrap a Ziex app as a Vercel Edge Function handler.
|
||||
*
|
||||
* Returns a standard `(req: Request) => Promise<Response>` function.
|
||||
* Vercel's edge runtime calls it directly — just export it as default.
|
||||
* Vercel's edge runtime calls it directly - just export it as default.
|
||||
*/
|
||||
export function handle(app: FetchApp): (req: Request) => Promise<Response> {
|
||||
return (req) => app.fetch(req);
|
||||
|
||||
@ -43,7 +43,7 @@ export function createWasiImports({
|
||||
const encodedArgs = argStrings.map((a) => encoder.encode(a + "\0"));
|
||||
const argBufSize = encodedArgs.reduce((s, a) => s + a.length, 0);
|
||||
|
||||
// WASM memory — set after instantiation
|
||||
// WASM memory - set after instantiation
|
||||
let wasmMemory: WebAssembly.Memory = null!;
|
||||
const setMemory = (m: WebAssembly.Memory) => { wasmMemory = m; };
|
||||
|
||||
@ -142,7 +142,7 @@ export function createWasiImports({
|
||||
return 0;
|
||||
},
|
||||
fd_prestat_get(_fd: number, _bufptr: number): number {
|
||||
return 8; // WASI_EBADF — no preopened directories
|
||||
return 8; // WASI_EBADF - no preopened directories
|
||||
},
|
||||
fd_prestat_dir_name(_fd: number, _path: number, _path_len: number): number {
|
||||
return 8; // WASI_EBADF
|
||||
@ -205,7 +205,7 @@ export function createWasiImports({
|
||||
v().setUint32(bufused_ptr, 0, true);
|
||||
return 76;
|
||||
},
|
||||
// Stub for poll_oneoff — no blocking I/O in edge runtimes.
|
||||
// Stub for poll_oneoff - no blocking I/O in edge runtimes.
|
||||
poll_oneoff(_in: number, _out: number, _nsubscriptions: number, nevents_ptr: number): number {
|
||||
v().setUint32(nevents_ptr, 0, true);
|
||||
return 0;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { ZigJS } from "../../../../vendor/jsz/js/src";
|
||||
|
||||
/**
|
||||
* Core WASM bridge — environment-agnostic (browser + edge).
|
||||
* Core WASM bridge - environment-agnostic (browser + edge).
|
||||
* Contains no references to browser globals (document, window, WebSocket, HTMLFormElement).
|
||||
*/
|
||||
export const CallbackType = {
|
||||
@ -41,7 +41,7 @@ export function storeValueGetRef(val: any): bigint {
|
||||
return tempRefView.getBigUint64(0, true);
|
||||
}
|
||||
|
||||
/** Shared encoder/decoder — avoids allocating new instances on every call. */
|
||||
/** Shared encoder/decoder - avoids allocating new instances on every call. */
|
||||
export const textDecoder = new TextDecoder();
|
||||
export const textEncoder = new TextEncoder();
|
||||
|
||||
@ -103,7 +103,7 @@ export function invokeWasmExport<F extends (...args: any[]) => any>(
|
||||
}
|
||||
|
||||
/**
|
||||
* Core ZX Bridge — works in both browser and edge runtimes.
|
||||
* Core ZX Bridge - works in both browser and edge runtimes.
|
||||
* Contains fetch, timers, and logging. No DOM or browser-WebSocket references.
|
||||
*
|
||||
* Extend this class in browser environments to add DOM and WebSocket support.
|
||||
|
||||
@ -33,7 +33,7 @@ import type {
|
||||
} from "./core";
|
||||
|
||||
/**
|
||||
* Browser ZX Bridge — extends ZxBridgeCore with DOM, WebSocket, and form-action support.
|
||||
* Browser ZX Bridge - extends ZxBridgeCore with DOM, WebSocket, and form-action support.
|
||||
* Import this from environments that have access to browser globals.
|
||||
* For edge runtimes, import ZxBridgeCore from ./core instead.
|
||||
*/
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
/**
|
||||
* WASI/edge WASM bridge — zero dependencies on ZigJS (jsz) or browser globals.
|
||||
* WASI/edge WASM bridge - zero dependencies on ZigJS (jsz) or browser globals.
|
||||
*
|
||||
* Import this (and only this) from edge/server runtimes. It provides the
|
||||
* minimal __zx import namespace needed by server-side WASM: log, fetch, and
|
||||
* timers. The jsz importObject is intentionally omitted — the server binary
|
||||
* timers. The jsz importObject is intentionally omitted - the server binary
|
||||
* does not use jsz value-passing.
|
||||
*/
|
||||
|
||||
@ -19,7 +19,7 @@ export class ZxWasiBridge {
|
||||
readonly #cb: ((type: number, id: bigint, data: bigint) => void) | undefined;
|
||||
readonly #intervals: Map<bigint, ReturnType<typeof setInterval>> = new Map();
|
||||
|
||||
// Cached memory view — invalidated when the WASM buffer grows.
|
||||
// Cached memory view - invalidated when the WASM buffer grows.
|
||||
#memView: Uint8Array | null = null;
|
||||
#memBuf: ArrayBufferLike | null = null;
|
||||
|
||||
@ -128,7 +128,7 @@ export class ZxWasiBridge {
|
||||
* Create the WASI import object for WASM instantiation.
|
||||
*
|
||||
* Returns only the `__zx` namespace (log, fetch, timers).
|
||||
* Does NOT include jsz.importObject() — the server binary does not use jsz.
|
||||
* Does NOT include jsz.importObject() - the server binary does not use jsz.
|
||||
*/
|
||||
static createImportObject(bridgeRef: { current: ZxWasiBridge | null }): { __zx: Record<string, unknown> } {
|
||||
return {
|
||||
|
||||
@ -62,7 +62,7 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Playground IDE Styles — re-uses home.css design tokens */
|
||||
/* Playground IDE Styles - re-uses home.css design tokens */
|
||||
|
||||
:root {
|
||||
--zx-primary: #4ade80;
|
||||
@ -914,7 +914,7 @@ body {
|
||||
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolygon points='5 3 19 12 5 21 5 3'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* Done / error / cached / prefetched — override icon */
|
||||
/* Done / error / cached / prefetched - override icon */
|
||||
.pg-status-step--done .pg-status-step-icon {
|
||||
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E") !important;
|
||||
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E") !important;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Auto-generated by bench/bench.sh — do not edit
|
||||
// Auto-generated by bench/bench.sh - do not edit
|
||||
.{
|
||||
.{
|
||||
.id = "ziex",
|
||||
|
||||
@ -101,7 +101,7 @@ fn SiteStats(allocator: zx.Allocator, props: struct { loading: bool }) zx.Compon
|
||||
/// Pause execution for `duration_ms` milliseconds.
|
||||
/// On native (dev server): uses std.Thread.sleep.
|
||||
/// On WASM (edge/Cloudflare): calls a Suspending JS import so the Worker can
|
||||
/// flush partial response chunks to the client before resuming — enabling
|
||||
/// flush partial response chunks to the client before resuming - enabling
|
||||
/// true streaming with JSPI.
|
||||
fn wait(duration_ms: u64) void {
|
||||
if (comptime builtin.cpu.arch == .wasm32) {
|
||||
@ -111,7 +111,7 @@ fn wait(duration_ms: u64) void {
|
||||
}
|
||||
}
|
||||
|
||||
/// Suspending WASM import — implemented as `setTimeout` on the JS side.
|
||||
/// Suspending WASM import - implemented as `setTimeout` on the JS side.
|
||||
/// Only linked in WASM builds; dead-code-eliminated in native builds.
|
||||
extern "__zx_sys" fn sleep_ms(ms: u32) void;
|
||||
|
||||
|
||||
@ -391,11 +391,11 @@ fn DisplayingData(allocator: zx.Allocator, props: DisplayingDataProps) zx.Compon
|
||||
<h2>Displaying data</h2>
|
||||
<p>ZX lets you embed dynamic content using curly braces <code>{"{expression}"}</code>. Expressions are automatically formatted based on their type:</p>
|
||||
<ul>
|
||||
<li><strong>Strings</strong> — displayed as text (HTML-escaped for safety)</li>
|
||||
<li><strong>Numbers</strong> — formatted as decimal</li>
|
||||
<li><strong>Booleans</strong> — displayed as "true" or "false"</li>
|
||||
<li><strong>Enums</strong> — displayed as the tag name</li>
|
||||
<li><strong>Optionals</strong> — unwrapped if present, otherwise renders nothing</li>
|
||||
<li><strong>Strings</strong> - displayed as text (HTML-escaped for safety)</li>
|
||||
<li><strong>Numbers</strong> - formatted as decimal</li>
|
||||
<li><strong>Booleans</strong> - displayed as "true" or "false"</li>
|
||||
<li><strong>Enums</strong> - displayed as the tag name</li>
|
||||
<li><strong>Optionals</strong> - unwrapped if present, otherwise renders nothing</li>
|
||||
</ul>
|
||||
<ExampleBlock id="text-expr" zx_code={props.zx_text} zig_code={props.zig_text} html_code={props.html_text} filename="user_greeting.zx" />
|
||||
<p>Different types are automatically handled:</p>
|
||||
@ -558,7 +558,7 @@ fn ChildrenProps(allocator: zx.Allocator, props: ChildrenPropsProps) zx.Componen
|
||||
return (
|
||||
<section id="children" class="section" @allocator={allocator}>
|
||||
<h2>Passing children to components</h2>
|
||||
<p>Components can accept <em>children</em> — content passed between opening and closing tags. Add a <code>children: zx.Component</code> field to your props:</p>
|
||||
<p>Components can accept <em>children</em> - content passed between opening and closing tags. Add a <code>children: zx.Component</code> field to your props:</p>
|
||||
<ExampleBlock id="children" zx_code={props.zx_code} zig_code={props.zig_code} html_code={props.html_code} filename="card_demo.zx" />
|
||||
<p>This pattern is useful for creating wrapper components like cards, modals, or layout containers. See the <a href="/reference#children-props">Children Props documentation</a> for more details.</p>
|
||||
</section>
|
||||
@ -603,10 +603,10 @@ fn PagesAndLayouts(allocator: zx.Allocator, props: PagesAndLayoutsProps) zx.Comp
|
||||
<h3>PageContext and LayoutContext</h3>
|
||||
<p>Both contexts provide:</p>
|
||||
<ul>
|
||||
<li><code>request</code> — HTTP request with headers, query params, body</li>
|
||||
<li><code>response</code> — HTTP response for setting headers</li>
|
||||
<li><code>arena</code> — Request-scoped allocator (recommended)</li>
|
||||
<li><code>allocator</code> — Global allocator for persistent allocations</li>
|
||||
<li><code>request</code> - HTTP request with headers, query params, body</li>
|
||||
<li><code>response</code> - HTTP response for setting headers</li>
|
||||
<li><code>arena</code> - Request-scoped allocator (recommended)</li>
|
||||
<li><code>allocator</code> - Global allocator for persistent allocations</li>
|
||||
</ul>
|
||||
<div class="callout tip">
|
||||
<strong>Best practice:</strong>
|
||||
@ -651,8 +651,8 @@ fn Plugins(allocator: zx.Allocator) zx.Component {
|
||||
<h3>Builtin Plugins</h3>
|
||||
<p>ZX ships with the following builtin plugins available via <code>zx.plugins</code>:</p>
|
||||
<ul>
|
||||
<li><strong><code>zx.plugins.tailwind</code></strong> — Compile Tailwind CSS styles</li>
|
||||
<li><strong><code>zx.plugins.esbuild</code></strong> — Bundle TypeScript/JavaScript for client-side code</li>
|
||||
<li><strong><code>zx.plugins.tailwind</code></strong> - Compile Tailwind CSS styles</li>
|
||||
<li><strong><code>zx.plugins.esbuild</code></strong> - Bundle TypeScript/JavaScript for client-side code</li>
|
||||
</ul>
|
||||
|
||||
<h3>Tailwind CSS Plugin</h3>
|
||||
|
||||
@ -835,7 +835,7 @@ outputsRun.addEventListener('click', async () => {
|
||||
ph.appendChild(phLabel);
|
||||
viewport.appendChild(ph);
|
||||
|
||||
// If a background prefetch is in flight, wait — result will be in cache
|
||||
// If a background prefetch is in flight, wait - result will be in cache
|
||||
if (prefetchPromise) await prefetchPromise;
|
||||
|
||||
const compiled = await runTranspileAndBuild(true);
|
||||
|
||||
@ -451,8 +451,8 @@ fn PluginsSection(allocator: zx.Allocator) zx.Component {
|
||||
<h3 id="builtin-plugins">Builtin Plugins</h3>
|
||||
<p>ZX ships with the following builtin plugins available via <code>zx.plugins</code>:</p>
|
||||
<ul>
|
||||
<li><strong><code>zx.plugins.tailwind</code></strong> — Compile Tailwind CSS styles</li>
|
||||
<li><strong><code>zx.plugins.esbuild</code></strong> — Bundle TypeScript/JavaScript for client-side code</li>
|
||||
<li><strong><code>zx.plugins.tailwind</code></strong> - Compile Tailwind CSS styles</li>
|
||||
<li><strong><code>zx.plugins.esbuild</code></strong> - Bundle TypeScript/JavaScript for client-side code</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="tailwind-plugin">Tailwind CSS Plugin</h3>
|
||||
@ -619,8 +619,8 @@ fn PluginsSection(allocator: zx.Allocator) zx.Component {
|
||||
|
||||
<h4>Plugin Lifecycle</h4>
|
||||
<ul>
|
||||
<li><strong><code>before_transpile</code></strong> — Runs before ZX transpiles <code>.zx</code> files to Zig. Useful for preprocessing source files.</li>
|
||||
<li><strong><code>after_transpile</code></strong> — Runs after ZX transpilation. Useful for post-processing like CSS compilation, JS bundling, or asset optimization.</li>
|
||||
<li><strong><code>before_transpile</code></strong> - Runs before ZX transpiles <code>.zx</code> files to Zig. Useful for preprocessing source files.</li>
|
||||
<li><strong><code>after_transpile</code></strong> - Runs after ZX transpilation. Useful for post-processing like CSS compilation, JS bundling, or asset optimization.</li>
|
||||
</ul>
|
||||
|
||||
<h4>Using <code>{"{outdir}"}</code></h4>
|
||||
@ -650,9 +650,9 @@ fn CachingSection(allocator: zx.Allocator) zx.Component {
|
||||
<h3 id="caching-overview">Overview</h3>
|
||||
<p>Caching can be applied at three levels:</p>
|
||||
<ul>
|
||||
<li><strong>Component caching</strong> — Cache individual components using the <code>@caching</code> attribute</li>
|
||||
<li><strong>Page caching</strong> — Cache entire pages using <code>PageOptions.caching</code></li>
|
||||
<li><strong>Layout caching</strong> — Cache layouts using <code>LayoutOptions.caching</code></li>
|
||||
<li><strong>Component caching</strong> - Cache individual components using the <code>@caching</code> attribute</li>
|
||||
<li><strong>Page caching</strong> - Cache entire pages using <code>PageOptions.caching</code></li>
|
||||
<li><strong>Layout caching</strong> - Cache layouts using <code>LayoutOptions.caching</code></li>
|
||||
</ul>
|
||||
|
||||
<h3 id="caching-components">Component Caching</h3>
|
||||
@ -716,18 +716,18 @@ fn CachingSection(allocator: zx.Allocator) zx.Component {
|
||||
<CodeBlock code={cache_invalidation_example} />
|
||||
<p><strong>Available functions:</strong></p>
|
||||
<ul>
|
||||
<li><code>zx.cache.del(key)</code> — Delete a specific cache entry by exact key. Returns <code>true</code> if deleted.</li>
|
||||
<li><code>zx.cache.delPrefix(prefix)</code> — Delete all entries matching a prefix. Returns the count of deleted entries.</li>
|
||||
<li><code>zx.cache.get(key)</code> — Get cached HTML by key. Returns <code>?[]const u8</code>.</li>
|
||||
<li><code>zx.cache.del(key)</code> - Delete a specific cache entry by exact key. Returns <code>true</code> if deleted.</li>
|
||||
<li><code>zx.cache.delPrefix(prefix)</code> - Delete all entries matching a prefix. Returns the count of deleted entries.</li>
|
||||
<li><code>zx.cache.get(key)</code> - Get cached HTML by key. Returns <code>?[]const u8</code>.</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="caching-best-practices">Best Practices</h3>
|
||||
<ul>
|
||||
<li><strong>Cache static content aggressively</strong> — Headers, footers, navigation, and other rarely-changing content benefit most from caching.</li>
|
||||
<li><strong>Use custom keys for related content</strong> — Group related cache entries with a common prefix for easy bulk invalidation.</li>
|
||||
<li><strong>Be careful with user-specific content</strong> — Don't cache components that display user-specific data without including user identity in the cache key.</li>
|
||||
<li><strong>Start with short TTLs</strong> — Begin with shorter cache durations and increase as you verify correctness.</li>
|
||||
<li><strong>Invalidate on data changes</strong> — Call <code>zx.cache.del()</code> or <code>zx.cache.delPrefix()</code> when underlying data changes.</li>
|
||||
<li><strong>Cache static content aggressively</strong> - Headers, footers, navigation, and other rarely-changing content benefit most from caching.</li>
|
||||
<li><strong>Use custom keys for related content</strong> - Group related cache entries with a common prefix for easy bulk invalidation.</li>
|
||||
<li><strong>Be careful with user-specific content</strong> - Don't cache components that display user-specific data without including user identity in the cache key.</li>
|
||||
<li><strong>Start with short TTLs</strong> - Begin with shorter cache durations and increase as you verify correctness.</li>
|
||||
<li><strong>Invalidate on data changes</strong> - Call <code>zx.cache.del()</code> or <code>zx.cache.delPrefix()</code> when underlying data changes.</li>
|
||||
</ul>
|
||||
|
||||
<div class="callout tip">
|
||||
@ -926,7 +926,7 @@ fn CLICommand(allocator: zx.Allocator, props: struct { command: CLICommandInfo }
|
||||
<h4>Arguments</h4>
|
||||
<ul>
|
||||
{for (args) |arg| (
|
||||
<li><code>{arg.name}</code> — {arg.description}</li>
|
||||
<li><code>{arg.name}</code> - {arg.description}</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
@ -937,7 +937,7 @@ fn CLICommand(allocator: zx.Allocator, props: struct { command: CLICommandInfo }
|
||||
<h4>Flags</h4>
|
||||
<ul>
|
||||
{for (flags) |flag| (
|
||||
<li><code>{flag.name}</code> — {flag.description}</li>
|
||||
<li><code>{flag.name}</code> - {flag.description}</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@ -85,9 +85,7 @@ pub const Component = union(enum) {
|
||||
// allocator.create() does NOT run field default initializers, so explicitly
|
||||
// set all fields that ComponentCtx defines with defaults.
|
||||
ctx.allocator = allocator;
|
||||
if (@hasField(CtxType, "_component_id")) ctx._component_id = "";
|
||||
if (@hasField(CtxType, "_id")) ctx._id = 0;
|
||||
if (@hasField(CtxType, "_state_index")) ctx._state_index = 0;
|
||||
if (@hasField(CtxType, "_internal")) ctx._internal = .{};
|
||||
ctx.children = if (@hasField(@TypeOf(props), "children")) props.children else null;
|
||||
if (@hasField(CtxType, "props")) {
|
||||
const PropsFieldType = @FieldType(CtxType, "props");
|
||||
@ -132,8 +130,10 @@ pub const Component = union(enum) {
|
||||
const CtxType = @typeInfo(FirstPropType).pointer.child;
|
||||
const ctx_ptr: *CtxType = @ptrCast(@alignCast(@constCast(propsPtr orelse @panic("ctx is null"))));
|
||||
// Reset slot counters on every call so hooks run in stable order.
|
||||
if (@hasField(CtxType, "_state_index")) ctx_ptr._state_index = 0;
|
||||
if (@hasField(CtxType, "_handler_index")) ctx_ptr._handler_index = 0;
|
||||
if (@hasField(CtxType, "_internal")) {
|
||||
ctx_ptr._internal.state_idx = 0;
|
||||
ctx_ptr._internal.handler_idx = 0;
|
||||
}
|
||||
return normalize(func(ctx_ptr));
|
||||
}
|
||||
if (first_is_allocator and param_count == 1) {
|
||||
@ -152,8 +152,10 @@ pub const Component = union(enum) {
|
||||
if (!first_is_ctx_ptr) return;
|
||||
const CtxType = @typeInfo(FirstPropType).pointer.child;
|
||||
const ctx_ptr: *CtxType = @ptrCast(@alignCast(@constCast(propsPtr orelse return)));
|
||||
if (@hasField(CtxType, "_component_id")) ctx_ptr._component_id = component_id;
|
||||
if (@hasField(CtxType, "_id")) ctx_ptr._id = instance_id;
|
||||
if (@hasField(CtxType, "_internal")) {
|
||||
ctx_ptr._internal.component_id = component_id;
|
||||
ctx_ptr._internal.instance_id = instance_id;
|
||||
}
|
||||
}
|
||||
|
||||
fn deinit(propsPtr: ?*const anyopaque, alloc: Allocator) void {
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
//! Structured zig dbuidld output parser for watch mode.
|
||||
//!
|
||||
//! zig --watch stderr output order per build cycle:
|
||||
//! 1. [optional] `zig build-exe ...` — compilation started (change detected)
|
||||
//! 2. `Build Summary: N/M steps succeeded; X failed` — outcome
|
||||
//! 1. [optional] `zig build-exe ...` - compilation started (change detected)
|
||||
//! 2. `Build Summary: N/M steps succeeded; X failed` - outcome
|
||||
//! 3. Build tree (+/- lines per step)
|
||||
//! 4. Blank line
|
||||
//! 5. Error diagnostics (if failed): `file:line:col: error: msg` blocks
|
||||
//! 6. `error: the following command failed with N compilation errors:` — end of errors
|
||||
//! 6. `error: the following command failed with N compilation errors:` - end of errors
|
||||
//!
|
||||
//! Events returned from processLine:
|
||||
//! change_detected — a `zig build-exe/lib/obj` line seen → recompilation started
|
||||
//! should_restart — binary mtime changed after a successful Build Summary
|
||||
//! errors — structured diagnostics, emitted when the error block ends
|
||||
//! resolved — previous build had errors, current succeeded
|
||||
//! change_detected - a `zig build-exe/lib/obj` line seen → recompilation started
|
||||
//! should_restart - binary mtime changed after a successful Build Summary
|
||||
//! errors - structured diagnostics, emitted when the error block ends
|
||||
//! resolved - previous build had errors, current succeeded
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const log = std.log.scoped(.builder);
|
||||
@ -62,7 +62,7 @@ pub const Event = union(enum) {
|
||||
should_restart: u64, // build_duration_ms
|
||||
errors: BuildResult,
|
||||
resolved,
|
||||
build_complete_no_change: u64, // build_duration_ms — successful build but binary unchanged
|
||||
build_complete_no_change: u64, // build_duration_ms - successful build but binary unchanged
|
||||
assets_installed: AssetChange, // asset-only change (public/assets files copied, no recompilation)
|
||||
};
|
||||
|
||||
@ -418,7 +418,7 @@ pub fn parseDiagnostic(allocator: std.mem.Allocator, line: []const u8) ?Diagnost
|
||||
const message = line[msg_start..];
|
||||
if (location.len == 0 or message.len == 0) return null;
|
||||
|
||||
// Parse "file:LINE:COL" — find the last two colons.
|
||||
// Parse "file:LINE:COL" - find the last two colons.
|
||||
var last_colon: usize = 0;
|
||||
var prev_colon: usize = 0;
|
||||
var j: usize = 0;
|
||||
@ -853,7 +853,7 @@ pub fn main() !void {
|
||||
try child.spawn();
|
||||
defer _ = child.kill() catch {};
|
||||
|
||||
// Use a dummy binary path — we just want to see events
|
||||
// Use a dummy binary path - we just want to see events
|
||||
var state = BuildState.init(allocator, "zig-out/bin/app", 0);
|
||||
state.first_build_done = true;
|
||||
defer state.deinit();
|
||||
|
||||
@ -314,7 +314,7 @@ fn serveWebSocket(ds: *DevServer, sock: *http.Server.WebSocket) !noreturn {
|
||||
};
|
||||
log.debug("ws: connected message sent", .{});
|
||||
|
||||
// Drain incoming frames on a dedicated thread — mirrors WebServer.zig's
|
||||
// Drain incoming frames on a dedicated thread - mirrors WebServer.zig's
|
||||
// recvWebSocketMessages pattern. Without this, the connection stalls because
|
||||
// unread frames (pings, close frames, etc.) block the underlying TCP stream.
|
||||
const recv_thread = std.Thread.spawn(.{}, recvWebSocketFrames, .{sock}) catch |err| {
|
||||
@ -341,7 +341,7 @@ fn serveWebSocket(ds: *DevServer, sock: *http.Server.WebSocket) !noreturn {
|
||||
while (true) {
|
||||
const cur = ds.update_id.load(.acquire);
|
||||
if (cur == last_id) {
|
||||
// No pending event — wait up to 30 s then ping to keep the connection alive.
|
||||
// No pending event - wait up to 30 s then ping to keep the connection alive.
|
||||
std.Thread.Futex.timedWait(&ds.update_id, last_id, 30 * std.time.ns_per_s) catch {
|
||||
try sock.writeMessage("", .ping);
|
||||
continue;
|
||||
@ -396,8 +396,8 @@ fn serializeNotification(gpa: Allocator, notification: Notification) ![]u8 {
|
||||
}
|
||||
|
||||
/// Proxy a request to the inner app binary.
|
||||
/// `head_buffer` — raw HTTP request head (including terminating \r\n\r\n).
|
||||
/// `buffered_extra` — body bytes already consumed by the http.Server reader.
|
||||
/// `head_buffer` - raw HTTP request head (including terminating \r\n\r\n).
|
||||
/// `buffered_extra` - body bytes already consumed by the http.Server reader.
|
||||
fn proxyToInner(
|
||||
ds: *DevServer,
|
||||
client: std.net.Stream,
|
||||
@ -406,7 +406,7 @@ fn proxyToInner(
|
||||
) !void {
|
||||
const inner_addr = try std.net.Address.parseIp("127.0.0.1", ds.inner_port);
|
||||
|
||||
// Retry while the inner server is (re)starting — up to 2 s.
|
||||
// Retry while the inner server is (re)starting - up to 2 s.
|
||||
const inner: std.net.Stream = for (0..200) |_| {
|
||||
if (std.net.tcpConnectToAddress(inner_addr)) |s| break s else |_| std.Thread.sleep(10 * std.time.ns_per_ms);
|
||||
} else return error.ConnectionRefused;
|
||||
|
||||
@ -83,7 +83,7 @@ fn remapSingle(allocator: std.mem.Allocator, d: *Builder.Diagnostic) !void {
|
||||
defer allocator.free(decoded);
|
||||
base64.Decoder.decode(decoded, b64_data) catch return;
|
||||
|
||||
// Parse JSON sourcemap — extract "sources" and "mappings" fields
|
||||
// Parse JSON sourcemap - extract "sources" and "mappings" fields
|
||||
const source_file = extractJsonStringField(decoded, "sources") orelse return;
|
||||
const mappings_str = extractJsonStringField(decoded, "mappings") orelse return;
|
||||
|
||||
|
||||
@ -62,7 +62,7 @@
|
||||
if (document.hidden) {
|
||||
// Defer actions while tab is hidden; keep only the latest pending message
|
||||
// so we apply the final state when the user switches back.
|
||||
// "connected" and "building" are status-only — always store them but
|
||||
// "connected" and "building" are status-only - always store them but
|
||||
// don't discard a more important pending action (reload/error/asset_update).
|
||||
if (msg.type === "connected" || msg.type === "building") {
|
||||
if (!state.pendingMessage) state.pendingMessage = msg;
|
||||
@ -188,7 +188,7 @@
|
||||
}
|
||||
|
||||
if (!cssReloaded && files.length > 0) {
|
||||
// Non-CSS assets changed (images, fonts, etc.) — bust cache on matching elements
|
||||
// Non-CSS assets changed (images, fonts, etc.) - bust cache on matching elements
|
||||
const selectors = files
|
||||
.filter((f) => !f.endsWith(".css"))
|
||||
.map((f) => `img[src*="${f}"],source[srcset*="${f}"]`)
|
||||
|
||||
@ -75,15 +75,19 @@ pub fn ComponentCtx(comptime PropsType: type) type {
|
||||
allocator: Allocator,
|
||||
children: ?zx.Component = null,
|
||||
|
||||
_id: u16 = 0,
|
||||
_component_id: []const u8 = "",
|
||||
_state_index: u32 = 0,
|
||||
_handler_index: u32 = 0,
|
||||
_internal: Internal = .{},
|
||||
|
||||
pub const Internal = struct {
|
||||
instance_id: u16 = 0,
|
||||
component_id: []const u8 = "",
|
||||
state_idx: u32 = 0,
|
||||
handler_idx: u32 = 0,
|
||||
};
|
||||
|
||||
pub fn state(self: *Self, comptime T: type, initial: T) reactivity.StateInstance(T) {
|
||||
const slot = (1 << 20) + self._state_index;
|
||||
self._state_index += 1;
|
||||
return reactivity.State(T).getOrCreate(self.allocator, self._component_id, slot, initial) catch @panic("State(T).getOrCreate");
|
||||
const slot = (1 << 20) + self._internal.state_idx;
|
||||
self._internal.state_idx += 1;
|
||||
return reactivity.State(T).getOrCreate(self.allocator, self._internal.component_id, slot, initial) catch @panic("State(T).getOrCreate");
|
||||
}
|
||||
|
||||
pub fn sbind(self: *Self, comptime handler: anytype, states: anytype) zx.EventHandler {
|
||||
@ -99,7 +103,7 @@ pub fn ComponentCtx(comptime PropsType: type) type {
|
||||
|
||||
const alloc = if (platform.role == .client) client_allocator else self.allocator;
|
||||
const bound_states = zx.EventHandler.buildStates(alloc, states);
|
||||
return zx.EventHandler.serverSS(handler, alloc, &self._handler_index, bound_states);
|
||||
return zx.EventHandler.serverSS(handler, alloc, &self._internal.handler_idx, bound_states);
|
||||
}
|
||||
|
||||
pub fn bind(self: *Self, comptime handler: anytype) zx.EventHandler {
|
||||
@ -116,20 +120,20 @@ pub fn ComponentCtx(comptime PropsType: type) type {
|
||||
return switch (FnType) {
|
||||
// Client
|
||||
fn (*ClientEvent) void => zx.EventHandler.client(handler),
|
||||
fn (*ClientEvent.Stateful) void => zx.EventHandler.clientS(handler, alloc, self._component_id),
|
||||
fn (*ClientEvent.Stateful) void => zx.EventHandler.clientS(handler, alloc, self._internal.component_id),
|
||||
|
||||
// Server
|
||||
fn (*ServerEvent.Stateful) void => zx.EventHandler.serverS(handler, alloc, self._component_id, self._state_index, &self._handler_index),
|
||||
fn (*ServerEvent) void => zx.EventHandler.server(handler, alloc, &self._handler_index),
|
||||
fn (*ServerEvent.Stateful) void => zx.EventHandler.serverS(handler, alloc, self._internal.component_id, self._internal.state_idx, &self._internal.handler_idx),
|
||||
fn (*ServerEvent) void => zx.EventHandler.server(handler, alloc, &self._internal.handler_idx),
|
||||
|
||||
// Server Actions
|
||||
fn (*ActionContext.Stateful) void => zx.EventHandler.actionStateful(handler, alloc, self._component_id, self._state_index, &self._handler_index),
|
||||
fn (*ActionContext.Stateful) void => zx.EventHandler.actionStateful(handler, alloc, self._internal.component_id, self._internal.state_idx, &self._internal.handler_idx),
|
||||
fn (ActionContext, *StateContext) void => actionBind(handler, alloc, self),
|
||||
fn (*ActionContext) void => actionBind(handler, alloc, self),
|
||||
|
||||
else => blk: {
|
||||
if (comptime params.len == 1 and params[0].type.? == *ServerEvent) {
|
||||
break :blk zx.EventHandler.server(handler, alloc, &self._handler_index);
|
||||
break :blk zx.EventHandler.server(handler, alloc, &self._internal.handler_idx);
|
||||
}
|
||||
if (comptime params.len == 2 and
|
||||
@typeInfo(params[0].type.?) == .@"struct" and
|
||||
@ -164,9 +168,9 @@ fn actionBind(comptime handler: anytype, alloc: Allocator, ctx: anytype) zx.Even
|
||||
}
|
||||
}
|
||||
};
|
||||
const bound = reactivity.collectStateBoundEntries(alloc, ctx._component_id, ctx._state_index);
|
||||
ctx._handler_index += 1;
|
||||
const h_id = ctx._handler_index;
|
||||
const bound = reactivity.collectStateBoundEntries(alloc, ctx._internal.component_id, ctx._internal.state_idx);
|
||||
ctx._internal.handler_idx += 1;
|
||||
const h_id = ctx._internal.handler_idx;
|
||||
const ec = alloc.create(zx.EventHandler.Context) catch @panic("OOM");
|
||||
ec.* = .{ .handler_id = h_id, .bound_states = bound };
|
||||
return zx.EventHandler{
|
||||
|
||||
@ -500,7 +500,7 @@ pub fn transpileReturn(self: *Ast, node: ts.Node, ctx: *TranspileContext) !void
|
||||
// Check if we need to initialize _zx with allocator
|
||||
const allocator_value = try getAllocatorAttribute(self, child);
|
||||
|
||||
// Synthesized _zx init — no source mapping (it's boilerplate)
|
||||
// Synthesized _zx init - no source mapping (it's boilerplate)
|
||||
try ctx.write("var _zx = @import(\"zx\").");
|
||||
if (allocator_value) |alloc| {
|
||||
try ctx.write("allocInit(");
|
||||
@ -1361,7 +1361,7 @@ fn writeChildrenValue(self: *Ast, children: []const ts.Node, ctx: *TranspileCont
|
||||
/// When preserve_whitespace is true (e.g. for <pre>), text nodes won't be trimmed
|
||||
fn writeHtmlElement(self: *Ast, node: ts.Node, tag: []const u8, tag_name_byte: u32, end_tag_start_byte: u32, end_tag_end_byte: u32, attributes: []const ZxAttribute, children: []const ts.Node, ctx: *TranspileContext, preserve_whitespace: bool) !void {
|
||||
_ = node;
|
||||
// _zx.ele( is synthesized — no source mapping
|
||||
// _zx.ele( is synthesized - no source mapping
|
||||
try ctx.write("_zx.ele");
|
||||
if (ctx.paren_byte) |p| {
|
||||
try ctx.writeM("(", p, self);
|
||||
|
||||
@ -240,7 +240,7 @@ pub const Handler = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Open a .zx file in ZLS if not already tracked — reads from disk, transpiles to .zig, sends didOpen.
|
||||
/// Open a .zx file in ZLS if not already tracked - reads from disk, transpiles to .zig, sends didOpen.
|
||||
/// For imported files we send transpiled .zig (not raw .zx) so ZLS can resolve exported types.
|
||||
fn ensureZxFileOpenInZls(handler: *Handler, arena: std.mem.Allocator, doc_dir: []const u8, rel_path: []const u8) void {
|
||||
const joined = std.fs.path.join(handler.allocator, &.{ doc_dir, rel_path }) catch return;
|
||||
|
||||
@ -16,7 +16,7 @@ const opts = @import("options.zig");
|
||||
const ctxs = @import("contexts.zig");
|
||||
const reactivity = @import("runtime/client/reactivity.zig");
|
||||
|
||||
// -- Core Language (separate cached module — excluded by default for user builds) --//
|
||||
// -- Core Language --//
|
||||
pub const Ast = if (!module_options.exclude_core_lang) @import("zx_core_lang").Ast else @compileError("core_lang is excluded. Set exclude-core-lang=false to enable.");
|
||||
pub const Parse = if (!module_options.exclude_core_lang) @import("zx_core_lang").Parse else @compileError("core_lang is excluded. Set exclude-core-lang=false to enable.");
|
||||
pub const Validate = if (!module_options.exclude_core_lang) @import("zx_core_lang").Validate else @compileError("core_lang is excluded. Set exclude-core-lang=false to enable.");
|
||||
@ -63,7 +63,6 @@ pub const EventHandler = @import("runtime/core/EventHandler.zig");
|
||||
pub const State = reactivity.State;
|
||||
|
||||
// --- Options --- //
|
||||
pub const AppOptions = app_module.ServerConfig;
|
||||
pub const PageOptions = opts.PageOptions;
|
||||
pub const LayoutOptions = opts.LayoutOptions;
|
||||
pub const NotFoundOptions = opts.NotFoundOptions;
|
||||
|
||||
@ -64,6 +64,7 @@ pub const ComponentMeta = struct {
|
||||
}
|
||||
|
||||
fn wrapper(allocator: std.mem.Allocator, cmp_name: []const u8, props_json: ?[]const u8) zx.Component {
|
||||
_ = cmp_name;
|
||||
// Case 1: Component takes only allocator - fn Component(allocator) Component
|
||||
if (first_is_allocator and param_count == 1) {
|
||||
return normalizeResult(func(allocator));
|
||||
@ -83,15 +84,17 @@ pub const ComponentMeta = struct {
|
||||
ctx.allocator = allocator;
|
||||
ctx.children = null;
|
||||
|
||||
ctx._id = instance_counter;
|
||||
// Reset slot counters so hooks run in stable order every render.
|
||||
if (@hasField(CtxType, "_internal")) {
|
||||
ctx._internal = .{
|
||||
.instance_id = instance_counter,
|
||||
.component_id = current_render_id,
|
||||
.state_idx = 0,
|
||||
.handler_idx = 0,
|
||||
};
|
||||
}
|
||||
instance_counter +%= 1; // Wrap around on overflow
|
||||
|
||||
// Reset both slot counters so hooks run in stable order every render.
|
||||
ctx._component_id = current_render_id;
|
||||
ctx._state_index = 0;
|
||||
if (@hasField(CtxType, "_handler_index")) ctx._handler_index = 0;
|
||||
if (@hasField(CtxType, "_component_name")) ctx._component_name = cmp_name;
|
||||
|
||||
// Parse props if the context has a props field
|
||||
if (@hasField(CtxType, "props")) {
|
||||
const PropsFieldType = @FieldType(CtxType, "props");
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
//! Client-side Event — wraps a browser JS event object.
|
||||
//! Client-side Event - wraps a browser JS event object.
|
||||
//!
|
||||
//! Provides DOM event methods: `value()`, `key()`, `preventDefault()`.
|
||||
//! For state access, use `Event.Stateful` via `ctx.bind()`.
|
||||
@ -52,10 +52,9 @@ pub fn key(self: Event) ?[]const u8 {
|
||||
return event.ref.getAlloc(real_js.String, gpa, "key") catch null;
|
||||
}
|
||||
|
||||
|
||||
// --- Stateful --- //
|
||||
|
||||
/// Stateful client event — provides `state()` access to bound component state.
|
||||
/// Stateful client event - provides `state()` access to bound component state.
|
||||
/// Use `fn(*zx.client.Event.Stateful) void` with `ctx.bind()` to get this type.
|
||||
pub const Stateful = struct {
|
||||
_inner: *Event,
|
||||
@ -89,5 +88,4 @@ pub const Stateful = struct {
|
||||
pub fn key(self: Stateful) ?[]const u8 {
|
||||
return self._inner.key();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@ -82,7 +82,7 @@ pub fn State(comptime T: type) type {
|
||||
pub const ValueType = T;
|
||||
|
||||
value: T,
|
||||
/// The owning component ID — used to call scheduleRender on mutation.
|
||||
/// The owning component ID - used to call scheduleRender on mutation.
|
||||
component_id: []const u8,
|
||||
|
||||
pub fn init(value: T, component_id: []const u8) Self {
|
||||
@ -165,7 +165,7 @@ pub fn State(comptime T: type) type {
|
||||
if (state_store.get(key)) |entry| {
|
||||
return @ptrCast(@alignCast(entry.ptr));
|
||||
}
|
||||
@panic("State not found — ensure sc.state() is called in the same order as ctx.state()");
|
||||
@panic("State not found - ensure sc.state() is called in the same order as ctx.state()");
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -220,11 +220,10 @@ pub fn scheduleRender(component_id: []const u8) void {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// component_id not registered — nested component inside a CSR parent.
|
||||
// component_id not registered - nested component inside a CSR parent.
|
||||
// Re-render all so the parent picks up the state change.
|
||||
client.renderAll();
|
||||
}
|
||||
}
|
||||
|
||||
pub const EventHandler = zx.EventHandler;
|
||||
|
||||
|
||||
@ -25,17 +25,17 @@ pub extern "__zx" fn _snv(vnode_id: u64, ptr: [*]const u8, len: usize) void;
|
||||
|
||||
// DOM tree mutation
|
||||
|
||||
/// parent.appendChild(child) — both nodes looked up by vnode_id.
|
||||
/// parent.appendChild(child) - both nodes looked up by vnode_id.
|
||||
pub extern "__zx" fn _ac(parent_id: u64, child_id: u64) void;
|
||||
|
||||
/// parent.insertBefore(child, ref) — all nodes looked up by vnode_id.
|
||||
/// parent.insertBefore(child, ref) - all nodes looked up by vnode_id.
|
||||
pub extern "__zx" fn _ib(parent_id: u64, child_id: u64, ref_id: u64) void;
|
||||
|
||||
/// parent.removeChild(child) — looked up by vnode_id.
|
||||
/// parent.removeChild(child) - looked up by vnode_id.
|
||||
/// Also recursively removes all descendants from the JS domNodes registry.
|
||||
pub extern "__zx" fn _rc(parent_id: u64, child_id: u64) void;
|
||||
|
||||
/// parent.replaceChild(new_child, old_child) — looked up by vnode_id.
|
||||
/// parent.replaceChild(new_child, old_child) - looked up by vnode_id.
|
||||
/// Also removes old_child subtree from the JS domNodes registry.
|
||||
pub extern "__zx" fn _rpc(parent_id: u64, new_id: u64, old_id: u64) void;
|
||||
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
//! Shared Event types for both client-side and server-side event handlers.
|
||||
//!
|
||||
//! Both `zx.client.Event` and `zx.server.Event` share this state interface:
|
||||
//! - `Event.Stateful` — wrapper with `state()` for handlers bound via `ctx.bind()`
|
||||
//! - `StateHandle(T)` — returned by `state()`, provides `.get()` / `.set()`
|
||||
//! - `StateContext` — positional state accessor used internally by the bind wrappers
|
||||
//! - `Event.Stateful` - wrapper with `state()` for handlers bound via `ctx.bind()`
|
||||
//! - `StateHandle(T)` - returned by `state()`, provides `.get()` / `.set()`
|
||||
//! - `StateContext` - positional state accessor used internally by the bind wrappers
|
||||
|
||||
const std = @import("std");
|
||||
const zx = @import("../../root.zig");
|
||||
|
||||
/// A handle to a single state value.
|
||||
/// Returned by `e.state(T)` or `sc.state(T)` — call `.get()` to read, `.set(val)` to write back.
|
||||
/// Returned by `e.state(T)` or `sc.state(T)` - call `.get()` to read, `.set(val)` to write back.
|
||||
pub fn StateHandle(comptime T: type) type {
|
||||
return struct {
|
||||
_ctx: *StateContext,
|
||||
@ -32,7 +32,7 @@ pub fn StateHandle(comptime T: type) type {
|
||||
/// Server-side accessor for component states round-tripped through a server event.
|
||||
///
|
||||
/// Call `sc.state(T)` in the same order as `ctx.state(T)` in the render function
|
||||
/// to access each bound state — no index needed.
|
||||
/// to access each bound state - no index needed.
|
||||
pub const StateContext = struct {
|
||||
arena: std.mem.Allocator,
|
||||
_allocator: std.mem.Allocator,
|
||||
@ -61,7 +61,7 @@ pub const StateContext = struct {
|
||||
}
|
||||
|
||||
/// Access the next bound state in call order, deserializing it to type `T`.
|
||||
/// Returns a StateHandle with `.get()` / `.set()` — same ergonomics as Event.Stateful.state().
|
||||
/// Returns a StateHandle with `.get()` / `.set()` - same ergonomics as Event.Stateful.state().
|
||||
pub fn state(self: *StateContext, comptime T: type) StateHandle(T) {
|
||||
const i = self._index;
|
||||
self._index += 1;
|
||||
|
||||
@ -281,7 +281,7 @@ pub fn resolveRouteHandler(handlers: ServerMeta.RouteHandlers, method: zx.server
|
||||
}
|
||||
|
||||
/// Execute cascading Proxy() handlers from root "/" down to the target path.
|
||||
/// Does NOT execute local page_proxy/route_proxy — those are handled by the caller.
|
||||
/// Does NOT execute local page_proxy/route_proxy - those are handled by the caller.
|
||||
pub fn executeCascadingProxies(
|
||||
path: []const u8,
|
||||
req: zx.server.Request,
|
||||
|
||||
@ -144,7 +144,7 @@ fn storedTypeHash(raw: []const u8) !u64 {
|
||||
return std.fmt.parseUnsigned(u64, raw[start..i], 10);
|
||||
}
|
||||
|
||||
// -- Impl: Noop (WASM default — replaced by edge adapter at startup) -- //
|
||||
// -- Impl: Noop (WASM default - replaced by edge adapter at startup) -- //
|
||||
|
||||
fn noopGet(_: *anyopaque, _: []const u8, _: std.mem.Allocator, _: []const u8) anyerror!?[]u8 {
|
||||
return null;
|
||||
@ -162,7 +162,7 @@ const noop_vtable = VTable{
|
||||
.list = &noopList,
|
||||
};
|
||||
|
||||
// -- Impl: Filesystem (native default — persists to datadir/kv/<ns>/) -- //
|
||||
// -- Impl: Filesystem (native default - persists to datadir/kv/<ns>/) -- //
|
||||
const kv_store_base = zx_options.datadir ++ std.fs.path.sep_str ++ "kv";
|
||||
|
||||
fn keyPath(ns: []const u8, key: []const u8, buf: *[1024]u8) ?[]u8 {
|
||||
|
||||
@ -75,7 +75,7 @@ pub const VNode = struct {
|
||||
|
||||
if (element.children) |children| {
|
||||
// Flatten fragments: lift fragment children into this node's
|
||||
// children list (React-style — fragments produce no DOM node).
|
||||
// children list (React-style - fragments produce no DOM node).
|
||||
const flat = try flattenComponents(allocator, children);
|
||||
try self.children.ensureTotalCapacity(allocator, flat.len);
|
||||
for (flat, 0..) |child, child_index| {
|
||||
@ -425,7 +425,7 @@ fn flattenInto(children: []const zx.Component, result: []zx.Component, idx: *usi
|
||||
/// reused node whose old_index >= lastPlacedIndex → stays, update tracker.
|
||||
/// no match → PLACEMENT.
|
||||
/// Delete every old child not reused.
|
||||
/// Backward pass (our addition for reference_id) — emit PLACEMENT/MOVE patches
|
||||
/// Backward pass (our addition for reference_id) - emit PLACEMENT/MOVE patches
|
||||
/// back-to-front so each patch's reference_id points to an already-resolved node.
|
||||
fn reconcileChildren(
|
||||
allocator: zx.Allocator,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
//! Server-side Action — context for server form action handlers.
|
||||
//! Server-side Action - context for server form action handlers.
|
||||
//!
|
||||
//! Provides form data parsing via `data()`.
|
||||
//! For state access, use `Action.Stateful` via `ctx.bind()` or `fn(*zx.server.Action.Stateful) void`.
|
||||
@ -60,7 +60,7 @@ pub fn data(self: Action, comptime T: type) T {
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Stateful server action — provides `state()` access to bound component state.
|
||||
/// Stateful server action - provides `state()` access to bound component state.
|
||||
/// Use `fn(*zx.server.Action.Stateful) void` with `ctx.bind()` to get this type.
|
||||
pub const Stateful = struct {
|
||||
_inner: *Action,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
//! Server-side Event — context for server event handlers.
|
||||
//! Server-side Event - context for server event handlers.
|
||||
//!
|
||||
//! Provides the event payload value via `value()`.
|
||||
//! For state access, use `Event.Stateful` via `ctx.bind()`.
|
||||
@ -26,7 +26,7 @@ pub fn value(self: Event) ?[]const u8 {
|
||||
return self.payload.value;
|
||||
}
|
||||
|
||||
/// Stateful server event — provides `state()` access to bound component state.
|
||||
/// Stateful server event - provides `state()` access to bound component state.
|
||||
/// Use `fn(*zx.server.Event.Stateful) void` with `ctx.bind()` to get this type.
|
||||
pub const Stateful = struct {
|
||||
_inner: *Event,
|
||||
@ -42,4 +42,3 @@ pub const Stateful = struct {
|
||||
return self._inner.value();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -700,7 +700,7 @@ pub const ServerMeta = struct {
|
||||
}
|
||||
|
||||
/// Wrapper to allow routes to return void or !void.
|
||||
/// Route function shape: `(ctx: zx.RouteContext, app?: App, state?: State)` — positional.
|
||||
/// Route function shape: `(ctx: zx.RouteContext, app?: App, state?: State)` - positional.
|
||||
fn wrapRoute(comptime routeFn: anytype) RouteHandler {
|
||||
const FnInfo = @typeInfo(@TypeOf(routeFn)).@"fn";
|
||||
const R = FnInfo.return_type.?;
|
||||
|
||||
@ -11,7 +11,7 @@ const render = @import("render.zig");
|
||||
pub const PageFn = *const fn (zx.PageContext, ?*const anyopaque, ?*const anyopaque) anyerror!zx.Component;
|
||||
|
||||
pub const DispatchResult = union(enum) {
|
||||
/// Request did not match this handler type — continue normal handling.
|
||||
/// Request did not match this handler type - continue normal handling.
|
||||
not_triggered,
|
||||
/// Handler was invoked successfully. `body` is serialized JSON state, or null if no state.
|
||||
ok: struct { body: ?[]u8 = null },
|
||||
|
||||
@ -195,10 +195,10 @@ pub fn run() !void {
|
||||
// Action was invoked natively, continue to render the page below
|
||||
},
|
||||
.not_found => {
|
||||
// No page handler — fall through to API route dispatch below
|
||||
// No page handler - fall through to API route dispatch below
|
||||
},
|
||||
.component => |cmp| {
|
||||
// Page rendered successfully — write it out
|
||||
// Page rendered successfully - write it out
|
||||
wasi_res.setContentTypeStr("text/html");
|
||||
|
||||
const streaming_enabled = core_handler.isStreamingEnabled(route);
|
||||
@ -290,7 +290,7 @@ pub fn run() !void {
|
||||
}
|
||||
|
||||
if (wasi_socket.upgraded) {
|
||||
// WebSocket message loop — do not send HTTP response
|
||||
// WebSocket message loop - do not send HTTP response
|
||||
const upgrade_data = wasi_socket.upgradeData();
|
||||
if (handlers.socket_open) |open_fn| {
|
||||
open_fn(socket, upgrade_data, allocator, allocator) catch {};
|
||||
|
||||
@ -87,7 +87,7 @@ pub extern "__zx_db" fn db_values(
|
||||
) i32;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// WebSocket WASI bridge — Cloudflare Worker binding
|
||||
// WebSocket WASI bridge - Cloudflare Worker binding
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Extern imports provided by worker.ts via the __zx_ws import namespace.
|
||||
@ -106,7 +106,7 @@ pub extern "__zx_ws" fn ws_publish(topic_ptr: [*]const u8, topic_len: usize, dat
|
||||
pub extern "__zx_ws" fn ws_is_subscribed(topic_ptr: [*]const u8, topic_len: usize) i32;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Logging bridge — forwards std.log calls to the JS console with level info
|
||||
// Logging bridge - forwards std.log calls to the JS console with level info
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Provided by the JS runtime via the __zx namespace (ZxBridge).
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
//! ZXON — ZX Object Notation (Ziex Exchange Object Notation)
|
||||
//! ZXON - ZX Object Notation (Ziex Exchange Object Notation)
|
||||
//!
|
||||
//! A compact positional serialization format for Zig types. Structs are encoded
|
||||
//! as ordered value arrays `[field1, field2, ...]` — field names are known at
|
||||
//! as ordered value arrays `[field1, field2, ...]` - field names are known at
|
||||
//! comptime on both ends, so only values are transmitted.
|
||||
//!
|
||||
//! Compared to `std.json`, this pulls in ~78 KB less code in WASM builds.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
/// Options for serialize/parse — reserved for future use.
|
||||
/// Options for serialize/parse - reserved for future use.
|
||||
pub const Options = struct {};
|
||||
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
|
||||
@ -546,7 +546,7 @@ pub const ZxContext = struct {
|
||||
|
||||
// If client option is set, return a client component (for @rendering={.client})
|
||||
// Render the component on the server for SSR, then hydrate on client.
|
||||
// On Browser (already on the client), skip the CSR wrapper — just render
|
||||
// On Browser (already on the client), skip the CSR wrapper - just render
|
||||
// the component directly as a component_fn.
|
||||
if (options.client != null and zx.platform.role == .client) {
|
||||
return .{ .component_fn = comp_fn };
|
||||
|
||||
@ -61,7 +61,7 @@ test "sm > sourceToGenerated exact match" {
|
||||
try testing.expectEqual(@as(i32, 5), result.generated_line);
|
||||
try testing.expectEqual(@as(i32, 8), result.generated_column);
|
||||
|
||||
// Position between two mappings on same source line — should use closest before
|
||||
// Position between two mappings on same source line - should use closest before
|
||||
const between = decoded.sourceToGenerated(2, 7).?;
|
||||
try testing.expectEqual(@as(i32, 5), between.generated_line);
|
||||
// col 7 is 3 past the mapping at col 4, so generated col = 8 + 3 = 11
|
||||
@ -108,7 +108,7 @@ test "sm > lookup returns null for unmapped position" {
|
||||
var decoded = try sm.decode(allocator);
|
||||
defer decoded.deinit();
|
||||
|
||||
// Line before any mapping — should return null
|
||||
// Line before any mapping - should return null
|
||||
const result = decoded.sourceToGenerated(0, 0);
|
||||
try testing.expectEqual(@as(?sourcemap.Mapping, null), result);
|
||||
}
|
||||
@ -233,7 +233,7 @@ test "sm > e2e expression in element" {
|
||||
}
|
||||
|
||||
// The expression {name} at source line 3 should map somewhere in generated that contains "name"
|
||||
// Find "name" in the source — it's at line 3, the expression is after "Hello "
|
||||
// Find "name" in the source - it's at line 3, the expression is after "Hello "
|
||||
// In .zx, line 3 is: " <p>Hello {name}</p>"
|
||||
// "name" starts at col 16 (after 8 spaces + "<p>Hello {")
|
||||
const expr_mapping = decoded.sourceToGenerated(3, 17).?;
|
||||
|
||||
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user