From bdcb3bba970857b3563a4b5cf3c9c6e737c1c9c8 Mon Sep 17 00:00:00 2001 From: dylan-sutton-chavez Date: Fri, 22 May 2026 09:53:12 -0600 Subject: [PATCH] docs: Clean repository documentation. --- README.md | 25 ++++++------------------ dom/README.md | 27 ++++++++++++-------------- network/README.md | 48 +++++++++++++++++++++++++---------------------- storage/README.md | 16 ++++++++-------- 4 files changed, 52 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index be3b1c0..dfbc886 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,17 @@ # Edge Python Host -Official JS modules for [Edge Python](https://edgepython.com) that expose host APIs (DOM, …) to Python scripts. Each capability is a plain ESM that registers with `createWorker` via `mainThreadModules` — no `.wasm`, no Rust, no custom embedder. +Official JS modules for [Edge Python](https://edgepython.com) exposing host APIs (DOM, network, storage) to Python scripts. Each capability is a plain ESM registered with `createWorker` via `mainThreadModules` — no `.wasm`, no Rust, no custom embedder. ## Layout ``` -edge-python-host/ -├── dom/ -│ ├── src/ -│ ├── web/ -│ ├── tests/ -│ └── README.md -├── network/ -│ ├── src/ -│ ├── web/ -│ ├── tests/ -│ └── README.md -├── storage/ -│ ├── src/ -│ ├── web/ -│ ├── tests/ -│ └── README.md -└── static/ +dom/ — src/, web/, tests/, README.md +network/ — src/, web/, tests/, README.md +storage/ — src/, web/, tests/, README.md +static/ ``` -Each top-level folder is one capability. +One folder per capability. ## Usage diff --git a/dom/README.md b/dom/README.md index 050c5db..2ac9a52 100644 --- a/dom/README.md +++ b/dom/README.md @@ -1,6 +1,6 @@ # Edge Python DOM -DOM access for Edge Python, shipped as a plain ESM module. Python scripts see `dom` as an ordinary module — full surface coverage: queries, mutation, events, forms, files, observers, animations, layout, media, SVG, and modern platform APIs (dialog, fullscreen, pointer lock). +DOM access shipped as a plain ESM module. Scripts see `dom` as ordinary — full surface: queries, mutation, events, forms, files, observers, animations, layout, media, SVG, dialog, fullscreen, pointer lock. ```python from dom import query, set_text, bind_event @@ -29,7 +29,7 @@ async def main(): ``` -The engine runs in a Web Worker; `dom` handlers run on the page's main thread (where `document` lives) via the runtime's deferred host-call mechanism. Python sees every call as synchronous. +Engine runs in a Web Worker; `dom` handlers run on the page's main thread (where `document` lives) via the runtime's deferred host-call mechanism. Python sees every call as synchronous. ## Quick start @@ -43,14 +43,11 @@ Open . No build step. ## Testing -A smoke test loads the demo in headless Chromium, clicks through a few interactions, and fails if any console error fires. +Headless Chromium smoke test — loads the demo, clicks through, fails on console error: ```bash -# Deno setup -curl -fsSL https://deno.land/install.sh | sh -source ~/.bashrc - -#Cache the browser binary +# Setup +curl -fsSL https://deno.land/install.sh | sh && source ~/.bashrc deno run -A npm:playwright install chromium # Run @@ -60,14 +57,14 @@ deno test --allow-all tests/dom.test.js ## API -**Conventions.** +**Conventions:** -- Handles are opaque integers — store them, pass them, never compute on them. +- Handles are opaque integers — store, pass, never compute on them. - Multi-result queries (`query_all`, `children`) return CSV strings of handles. - Structured returns (`rect`, `validity`, `form_data`, `bbox`, event payloads) are JSON strings. -- All async results (events, FileReader, animation finishes, observer entries) arrive through `receive()`. +- Async results (events, FileReader, animation finishes, observer entries) arrive via `receive()`. -**Parsing JSON returns.** Edge Python has no stdlib `json` — declare one in your `packages.json` (e.g. `{ "imports": { "json": "https://runtime.edgepython.com/lib/json.py" } }`) to get `json.loads`. For simple dispatch by event tag you can skip it and substring-match the raw payload (`'"msg":"click"' in ev`) as shown in `web/palette.py`. Examples below assume `json` is mapped where they use it. +**Parsing JSON returns.** The runtime auto-registers `json` — `from json import loads, dumps` works with zero setup. Examples below assume it. ### Selection and traversal @@ -245,19 +242,19 @@ append_child(query("svg"), circle) ## How it works -The module is a factory `(ctx) => handlers`. `src/state.js` opens a fresh closure per `createWorker` with the handle tables (`nodes`, `bindings`, `files`, observers, animations) and the `alloc` / `node` / `allocList` helpers. Eight handler slices (`tree`, `style`, `events`, `forms`, `observers`, `animations`, `media`, `platform`) each return an object literal of named handlers that close over the shared state; `src/index.js` composes them with `Object.assign`. Async callbacks (event listeners, `FileReader`, animation `finished`, observer entries) call `ctx.pushEvent(jsonDetail)` to wake a paused `receive()` in the script. +Factory `(ctx) => handlers`. `src/state.js` opens a fresh closure per `createWorker` with handle tables (`nodes`, `bindings`, `files`, observers, animations) and `alloc` / `node` / `allocList` helpers. Eight handler slices (`tree`, `style`, `events`, `forms`, `observers`, `animations`, `media`, `platform`) each return an object literal of named handlers closing over the shared state; `src/index.js` composes them with `Object.assign`. Async callbacks call `ctx.pushEvent(jsonDetail)` to wake a paused `receive()`. Adding a handler is one entry in one slice. Nothing else changes. ## Performance -Per-handler cost is one `postMessage` round-trip between the Worker (engine) and the main thread (handlers): ~0.1–0.4 ms in modern browsers. Plenty of headroom for UI-rate workloads — events, mutations, layout — at hundreds of ops per frame. +Per-handler cost is one `postMessage` round-trip (~0.1–0.4 ms in modern browsers) — plenty for UI-rate workloads (events, mutations, layout) at hundreds of ops/frame. Bad fit: tight per-frame loops with thousands of fine-grained ops, or pixel-precise renders. Pair with a `` capability for the framebuffer path. ## Distribution -This repo serves only the JS sources. `compiler_lib.wasm` and the Edge Python runtime both come from `runtime.edgepython.com` at page load — no vendored copy here, no build step. +JS sources only — `compiler_lib.wasm` and the runtime load from `runtime.edgepython.com` at page load. No vendored copy, no build step. ## License diff --git a/network/README.md b/network/README.md index d629e05..f3be01f 100644 --- a/network/README.md +++ b/network/README.md @@ -1,10 +1,12 @@ # Edge Python Network -HTTP, WebSocket, and Server-Sent Events for Edge Python, shipped as a plain ESM module. Python scripts see `network` as an ordinary module. +HTTP, WebSocket, and SSE shipped as a plain ESM module. Scripts see `network` as ordinary. ```python from network import fetch_json, ws_open, ws_send +import json + # HTTP — yields and resumes when the response arrives. Composes with gather / with_timeout. data = fetch_json("https://api.example.com/users") @@ -13,9 +15,9 @@ sock = ws_open("wss://example.com/socket", "msg") ws_send(sock, "hello") async def main(): while True: - ev = receive() - if '"type":"message"' in ev: - print(ev) + ev = json.loads(receive()) + if ev["type"] == "message": + print(ev["data"]) ``` ## Setup @@ -47,11 +49,11 @@ Open . No build step. ### Conventions -- **HTTP handlers are yielding host calls.** They return a Promise on the JS side and the runtime parks the coro in `WaitingHostCall` until it resolves — Python sees a sync-looking call that suspends. `gather()`, `with_timeout()`, and `run()` work over them automatically without `await`/`receive()` boilerplate. -- **WebSocket and SSE use the push-event pattern** (same as `dom`'s `bind_event`). Connections are opened with a `msg` tag; every wire event arrives in Python via `receive()` carrying that tag. -- **Handles are integer IDs** — store them, pass them, never compute on them. -- **Options are JSON strings** — `fetch(url, '{"method":"POST","body":"..."}')`. Mirrors `bind_event` and `animate` in `dom`. -- **All response bodies arrive as strings.** For JSON, parse with `json.loads` (declare the module in `packages.json` if you haven't yet — see [the dom README](../dom/README.md#api) for the import map snippet). +- **HTTP handlers yield.** They return a Promise on the JS side; the runtime parks the coro in `WaitingHostCall` until it resolves — Python sees a sync-looking call that suspends. `gather()`, `with_timeout()`, and `run()` work over them with no `await`/`receive()`. +- **WebSocket/SSE use push-events** (like `dom`'s `bind_event`). Connections open with a `msg` tag; every wire event arrives via `receive()` as JSON. +- **Handles are integer IDs.** +- **Options are JSON strings** — `fetch(url, '{"method":"POST","body":"..."}')`. +- **Response bodies are strings.** Parse JSON with `json.loads` — `json` is auto-registered by the runtime. ### HTTP @@ -98,18 +100,19 @@ except TimeoutError: ### WebSocket ```python +import json from network import ws_open, ws_send, ws_close, ws_state sock = ws_open("wss://example.com/socket", "ws") async def main(): while True: - ev = receive() - if '"type":"open"' in ev: + ev = json.loads(receive()) + if ev["type"] == "open": ws_send(sock, "hello") - elif '"type":"message"' in ev: - print(ev) # {"msg":"ws","type":"message","data":"..."} - elif '"type":"close"' in ev: + elif ev["type"] == "message": + print(ev["data"]) + elif ev["type"] == "close": return run(main()) @@ -122,16 +125,17 @@ Payload `type` values: `open`, `message`, `close`, `error`. `message` carries `d ### Server-Sent Events ```python +import json from network import sse_open, sse_close stream = sse_open("/events", "sse") async def main(): while True: - ev = receive() - if '"type":"message"' in ev: - print(ev) # {"msg":"sse","type":"message","data":"...","event_id":"..."} - elif '"type":"error"' in ev: + ev = json.loads(receive()) + if ev["type"] == "message": + print(ev["data"]) + elif ev["type"] == "error": sse_close(stream) return @@ -142,17 +146,17 @@ run(main()) ## How it works -`network/src/index.js` is a factory `(ctx) => handlers`, the same shape `dom` uses. Three handler slices (`http`, `ws`, `sse`) close over a shared `state` (handle tables for in-flight requests, sockets, SSE sources) and are merged with `Object.assign`. +`src/index.js` is a factory `(ctx) => handlers` (same shape as `dom`). Three slices (`http`, `ws`, `sse`) close over a shared `state` (handle tables for in-flight requests, sockets, SSE sources) and merge with `Object.assign`. -The HTTP slice returns **async handlers** (`async (url) => { ... return body; }`). The runtime detects the returned Promise and parks the calling coro in `WaitingHostCall` until it resolves — equivalent to how `sleep()` parks until a deadline. WS/SSE slices return synchronous handlers that wire DOM-style listeners into `ctx.pushEvent`, mirroring `bind_event` exactly. +HTTP slice returns async handlers (`async (url) => { ... return body; }`); the runtime detects the Promise and parks in `WaitingHostCall` until resolved — same shape as `sleep()`. WS/SSE slices return sync handlers wiring DOM-style listeners into `ctx.pushEvent`. ## Performance -Per-handler cost is one `postMessage` round-trip per call. HTTP handlers add the network latency on top (the dominant cost). For pipelines that do many small same-host requests, prefer one larger request over many small ones — same advice as in plain JS. +Per-handler cost is one `postMessage` round-trip per call; HTTP adds network latency on top. For many small same-host requests, prefer one larger request — same advice as plain JS. ## Distribution -This repo serves only the JS sources. `compiler_lib.wasm` and the Edge Python runtime both come from `runtime.edgepython.com` at page load — no vendored copy here, no build step. +JS sources only — `compiler_lib.wasm` and the runtime load from `runtime.edgepython.com`. No build step. ## License diff --git a/storage/README.md b/storage/README.md index 78891b0..cea2893 100644 --- a/storage/README.md +++ b/storage/README.md @@ -1,6 +1,6 @@ # Edge Python Storage -Persistent client-side storage for Edge Python — `localStorage`, `sessionStorage`, and `IndexedDB`. Shipped as a plain ESM module that registers with `createWorker`. +Persistent client-side storage — `localStorage`, `sessionStorage`, `IndexedDB`. Plain ESM module registered with `createWorker`. ```python from storage import local_set, local_get, idb_open, idb_put, idb_get @@ -44,11 +44,11 @@ Open . No build step. ### Conventions -- **Key-value handlers are sync.** `localStorage` and `sessionStorage` are blocking by spec; handlers return strings or `None`. No `await`, no `receive()`. -- **IndexedDB handlers are async (yielding host calls).** They return a Promise on the JS side; the runtime parks the calling coro in `WaitingHostCall` until it resolves. Python sees a sync-looking call that suspends, identical to `fetch()` in [`network/`](../network/README.md). -- **Values cross as JSON strings.** For `idb_put` / `idb_get`, encode/decode with `json.dumps` / `json.loads`. Storing structured objects directly would require crossing arbitrary types over the worker boundary — JSON is the same trade-off `dom`'s `animate` and `bind_event` make for options. -- **Key listings are JSON arrays, not CSV.** `local_keys()` / `session_keys()` / `idb_keys(...)` return a JSON-array string (because keys can contain commas). Parse with `json.loads`. -- **Handles are integer IDs** for IndexedDB; `local_*` / `session_*` need no handle (the global stores are addressed directly). +- **KV handlers are sync.** `localStorage` / `sessionStorage` are blocking by spec; handlers return strings or `None`. No `await`, no `receive()`. +- **IndexedDB handlers yield.** They return a Promise on the JS side; the runtime parks the coro in `WaitingHostCall` until resolved — same shape as `fetch()` in [`network/`](../network/README.md). +- **Values cross as JSON strings.** Encode with `json.dumps`, decode with `json.loads`. Same trade-off `dom`'s `animate` and `bind_event` make for options. +- **Key listings are JSON arrays** (keys can contain commas). Parse with `json.loads`. +- **Handles are integer IDs** for IndexedDB; `local_*` / `session_*` address global stores directly (no handle). ### localStorage / sessionStorage @@ -105,9 +105,9 @@ except TimeoutError: ## How it works -`storage/src/index.js` is a factory `() => handlers`, the same shape `dom` and `network` use. Two handler slices (`kv`, `idb`) close over a shared `state` (just a handle table for open IDBDatabase instances) and are merged with `Object.assign`. +`src/index.js` is a factory `() => handlers` (same shape as `dom`, `network`). Two slices (`kv`, `idb`) close over a shared `state` (a handle table for open `IDBDatabase` instances) and merge with `Object.assign`. -The KV slice returns **synchronous handlers** that call `localStorage` / `sessionStorage` directly. The IDB slice returns **async handlers** that promisify each native `IDBRequest`; the runtime detects the Promise return and parks the coro until it resolves. +KV slice returns sync handlers calling `localStorage` / `sessionStorage` directly. IDB slice returns async handlers that promisify native `IDBRequest`s; runtime detects the Promise and parks until resolved. ## License