Skip to content

Commit 09eb940

Browse files
committed
docs: slim the top-level README to a high-level overview
Make the README read like a normal repo landing page: short intro, a features list, one quick example, an installation pointer, a brief "how it works", and links down into the docs. Move the full API reference out to docs/api.md and drop the project-layout block (the docs source map already covers it). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3vgA3Q6PR9VQn8X5pLcMR
1 parent 0aa234d commit 09eb940

3 files changed

Lines changed: 105 additions & 122 deletions

File tree

README.md

Lines changed: 65 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,164 +1,107 @@
11
# php-quickjs
22

3-
Run **untrusted JavaScript or TypeScript inside PHP**, safely and ergonomically.
4-
5-
PHP applications increasingly need to execute user-supplied logic — rules,
6-
formulas, templates, plugins, AI-generated snippets. Doing that in PHP itself
7-
means `eval()` (no isolation) or a separate service (operational weight).
8-
`php-quickjs` embeds a [QuickJS-NG](https://github.com/quickjs-ng/quickjs) engine
9-
directly in the process and gives you a **typed, bidirectional bridge**:
10-
11-
- The guest runs in an **isolated context** with memory, time, and stack limits.
12-
- PHP exposes a **controlled allowlist** of capabilities into JS as a frozen,
13-
namespaced `php.module.fn()` SDK — the guest can only reach what you grant.
14-
- JS can **call back into PHP** mid-execution, pass functions both ways, and
15-
hold opaque handles to live PHP objects.
16-
- Guest code may be **TypeScript**: it is transpiled to JS in-process with
17-
[`oxc`](https://github.com/oxc-project/oxc), and runtime errors are mapped back
18-
to the original TS line/column.
19-
20-
Written in Rust with [`ext-php-rs`](https://github.com/davidcole1340/ext-php-rs)
21-
(the Zend side) and [`rquickjs`](https://github.com/DelSkayn/rquickjs) (QuickJS-NG
22-
is bundled — no system library needed).
23-
24-
> **Scope.** This is an *embedder*, not a security boundary against hostile code
25-
> on its own. The capability model contains *what JS can reach*; the resource
26-
> limits contain *abuse* (infinite loops, alloc bombs). QuickJS C
27-
> memory-corruption bugs are **not** contained — for attacker-controlled code,
28-
> nest the whole extension inside an outer microVM/gVisor boundary.
29-
30-
## Getting started
31-
32-
**Requirements:** Rust 1.96+ (for oxc), clang, and PHP 8.4 dev headers
33-
(`php-config`). The extension is a plain cargo `cdylib` — no `phpize` step.
34-
35-
```sh
36-
git clone https://github.com/eddmann/php-quickjs && cd php-quickjs
37-
make build # -> target/debug/libphp_quickjs.so
38-
make test # Rust unit tests + PHP integration suite
39-
```
40-
41-
Load it and run your first guest:
3+
Run untrusted **JavaScript or TypeScript inside PHP** — safely, with a typed,
4+
bidirectional bridge.
5+
6+
`php-quickjs` embeds the [QuickJS-NG](https://github.com/quickjs-ng/quickjs) engine
7+
directly in your PHP process. Guest code runs in an isolated context with memory,
8+
time, and stack limits; PHP exposes a controlled allowlist of capabilities into JS;
9+
and values, functions, and errors cross the boundary both ways. Guest code may be
10+
TypeScript — it's transpiled in-process and runtime errors map back to the original
11+
TS source.
12+
13+
Built in Rust with [`ext-php-rs`](https://github.com/davidcole1340/ext-php-rs) and
14+
[`rquickjs`](https://github.com/DelSkayn/rquickjs). QuickJS-NG is bundled — no system
15+
library required.
16+
17+
## Features
18+
19+
- **Isolated guest** — memory, time, and stack limits contain runaway code.
20+
- **Capability allowlist** — JS only sees the PHP functions you expose, as a frozen
21+
`php.module.fn()` SDK.
22+
- **Bidirectional** — JS calls back into PHP, functions pass both ways, and opaque
23+
handles wrap live PHP objects.
24+
- **TypeScript built in** — transpiled with [`oxc`](https://github.com/oxc-project/oxc);
25+
errors map to the original TS line and column.
26+
- **Typed exceptions** — guest failures surface as `QuickJSEvalException` with a
27+
JS-like message and stack.
28+
29+
## Quick example
4230

4331
```php
4432
<?php
45-
// hello.php — run with:
46-
// php -d extension=$(pwd)/target/debug/libphp_quickjs.so hello.php
47-
4833
$js = new QuickJS(memoryLimit: 64 * 1024 * 1024, timeoutMs: 1000);
4934

5035
$js->register('log.info', fn(string $m) => error_log("[js] $m"));
5136
$js->register('fetchUser', fn(int $id) => ['name' => 'Ada', 'orders' => [1, 2, 3]]);
5237

5338
echo $js->eval(<<<'TS'
5439
php.log.info("starting");
55-
const u = php.fetchUser(42); // reenters PHP
40+
const u = php.fetchUser(42); // re-enters PHP
5641
`${u.name} has ${u.orders.length} orders`;
5742
TS);
5843
// => "Ada has 3 orders"
5944
```
6045

61-
More to copy from: [`examples/kitchen_sink.php`](examples/kitchen_sink.php) (every
62-
feature), [`examples/modes.php`](examples/modes.php), and
63-
[`examples/usage.php`](examples/usage.php).
64-
65-
## API
46+
Run it with `php -d extension=/path/to/libphp_quickjs.so hello.php`. More to copy from
47+
[`examples/`](examples): `kitchen_sink.php`, `modes.php`, `usage.php`.
6648

67-
### `new QuickJS(?int $memoryLimit = null, ?int $timeoutMs = null, ?int $maxStack = null, bool $isolated = false)`
68-
Limits default to unbounded; pass non-zero values to contain resource abuse.
69-
`isolated: true` runs each `eval()` in a fresh realm (see
70-
[execution modes](docs/execution-modes.md)).
49+
## Installation
7150

72-
### `register(string $name, callable $fn, ?string $types = null): void`
73-
Expose a PHP callable to JS under a flat, dotted name — it becomes
74-
`php.<dotted.name>(...)` in the guest. `$types` is an optional TypeScript
75-
signature surfaced by `dts()`. This flat registry is the **entire** trust
76-
boundary.
51+
Prebuilt binaries are attached to each
52+
[release](https://github.com/eddmann/php-quickjs/releases) for PHP 8.4 / 8.5 —
53+
self-hosted Linux, AWS Lambda (a ready Bref layer), and macOS (Apple Silicon). Enable
54+
the one matching your platform:
7755

78-
### `eval(string $code): mixed`
79-
Run TypeScript or JavaScript and marshal the result back to PHP. Errors raise a
80-
`QuickJSEvalException` located at the original TS line/column (see
81-
[errors](docs/errors.md)).
56+
```ini
57+
; php.ini
58+
extension=/path/to/php-quickjs-...so
59+
```
8260

83-
### `grant(mixed $resource): int` / `resolve(int $h): mixed` / `revoke(int $h): bool`
84-
Capability handles for live, stateful objects (DB connections, file handles).
85-
The object stays host-side; JS only ever sees an opaque integer it can pass back
86-
to a capability. The handle **is** the capability.
61+
Or build from source (Rust 1.96+, clang, PHP dev headers — a plain cargo `cdylib`, no
62+
`phpize`):
8763

88-
```php
89-
$pdo = new PDO('sqlite:app.db');
90-
$h = $js->grant($pdo);
91-
$js->register('db.query', fn(int $handle, string $sql) => $js->resolve($handle)->query($sql)->fetchAll());
64+
```sh
65+
git clone https://github.com/eddmann/php-quickjs && cd php-quickjs
66+
make build
9267
```
9368

94-
### `manifest(): array` / `dts(): string`
95-
The registration manifest and a generated TypeScript `.d.ts` for the `php`
96-
global, both from the same source of truth.
69+
→ Full platform matrix, Docker, and AWS Lambda / Bref instructions:
70+
**[docs/install.md](docs/install.md)**.
9771

98-
## How it works (in brief)
72+
## How it works
9973

10074
```
10175
PHP (trusted) ──ext-php-rs──► Rust bridge ──rquickjs──► QuickJS (untrusted)
10276
register() dispatch table php.module.fn()
10377
eval() __host(name, bytes) frozen php.* facade
10478
```
10579

106-
Everything the guest reaches goes through a **single** `__host(name, argsBytes)`
107-
import and a flat dispatch table — the namespaced `php.*` tree is cosmetic JS,
108-
built from the manifest and **frozen**. Values cross as MessagePack; functions
109-
cross both ways as references backed by registries; errors bridge both ways and
110-
remap to TS coordinates.
111-
112-
→ Full details in **[docs/architecture.md](docs/architecture.md)**.
113-
114-
### TypeScript
115-
116-
`eval()` accepts TS (the Bun model: transpile-and-go, no type-checking on the hot
117-
path). Types/`interface`/generics erase; the transpile result is cached by
118-
content hash; the source map stays host-side and is used only to remap errors.
119-
[docs/architecture.md#the-typescript-fast-path](docs/architecture.md#the-typescript-fast-path).
120-
121-
### Execution modes
80+
Everything the guest reaches goes through a single `__host` import and a flat dispatch
81+
table; the namespaced `php.*` tree is frozen JS built from your registrations. Values
82+
cross as MessagePack, functions as references backed by registries, and errors bridge
83+
both ways — remapping to TS coordinates on the way out.
12284

123-
By default, all `eval()` calls on an instance share one **persistent** global
124-
realm (a REPL-like session; state and callbacks carry over). Pass
125-
`isolated: true` to run **each `eval()` in its own fresh realm** (a stateless
126-
script runner). → [docs/execution-modes.md](docs/execution-modes.md).
85+
**[docs/architecture.md](docs/architecture.md)** for the full design.
12786

128-
### Sandbox & security
87+
## Scope
12988

130-
| Layer | Contains |
131-
|-------|----------|
132-
| frozen `php.*` + flat dispatch table | what JS can *name* / reach |
133-
| capability handles | which live objects JS can *use* |
134-
| `memoryLimit` / `timeoutMs` / `maxStack` | resource abuse (loops, alloc bombs) |
135-
| **outer microVM / gVisor** | QuickJS C memory-corruption → host RCE |
136-
137-
These are resource guards; the extension is the embedder, not a memory-safety
138-
boundary. For hostile code, add an outer VM.
89+
This is an *embedder*, not a standalone defence against hostile code. The capability
90+
model contains *what JS can reach*; the resource limits contain *abuse* (infinite
91+
loops, alloc bombs). QuickJS C memory-corruption bugs are **not** contained — for
92+
attacker-controlled code, nest the extension inside an outer microVM / gVisor boundary.
13993

14094
## Documentation
14195

142-
- [Installation](docs/install.md) — prebuilt binaries for self-hosted PHP, AWS
143-
Lambda (Bref), and macOS; or build from source.
144-
- [Architecture](docs/architecture.md) — the bridge, marshaling, function
145-
passing, security model.
146-
- [Execution modes](docs/execution-modes.md) — realms, shared vs. isolated,
96+
- [Installation](docs/install.md) — prebuilt binaries, Docker, AWS Lambda (Bref), and
97+
building from source.
98+
- [API reference](docs/api.md) — the `QuickJS` class and every method.
99+
- [Architecture](docs/architecture.md) — the bridge, marshaling, function passing, and
100+
security model.
101+
- [Execution modes](docs/execution-modes.md) — shared vs. isolated realms and the
147102
callback lifecycle.
148-
- [Errors](docs/errors.md) — typed exceptions, both-way bridging, TS remapping.
149-
150-
## Project layout
151-
152-
```
153-
src/lib.rs QuickJS class + module src/handles.rs capability handle table
154-
src/engine.rs runtime/realms, re-entrancy src/sandbox.rs memory/stack/timeout limits
155-
src/bridge.rs __host dispatch, frozen facade src/error.rs exception bridging + TS remap
156-
src/marshal.rs value <-> msgpack <-> zval src/exceptions.rs typed exception classes
157-
src/callback.rs Js\Callback (JS fn -> PHP) src/manifest.rs manifest + .d.ts generation
158-
src/transpile.rs oxc TS->JS + cache src/js/*.js in-sandbox codec + runtime
159-
docs/ implementation guide examples/ runnable demos
160-
tests/php/ integration suite
161-
```
103+
- [Errors](docs/errors.md) — typed exceptions, both-way bridging, and TypeScript
104+
remapping.
162105

163106
## License
164107

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ user-facing API and quick start, see the [project README](../README.md).
55

66
- **[Installation](install.md)** — prebuilt binaries for self-hosted PHP, AWS
77
Lambda (Bref), and macOS, plus building from source.
8+
- **[API reference](api.md)** — the `QuickJS` class and every method.
89
- **[Architecture](architecture.md)** — the three worlds (PHP / Rust / QuickJS),
910
the single `__host` bridge, how a call flows end to end, value marshaling, and
1011
bidirectional function passing.

docs/api.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# API reference
2+
3+
The extension exposes a single `QuickJS` class. For the bigger picture see
4+
[architecture](architecture.md); for realms and the callback lifecycle see
5+
[execution modes](execution-modes.md).
6+
7+
### `new QuickJS(?int $memoryLimit = null, ?int $timeoutMs = null, ?int $maxStack = null, bool $isolated = false)`
8+
9+
Limits default to unbounded; pass non-zero values to contain resource abuse.
10+
`isolated: true` runs each `eval()` in a fresh realm (see
11+
[execution modes](execution-modes.md)).
12+
13+
### `register(string $name, callable $fn, ?string $types = null): void`
14+
15+
Expose a PHP callable to JS under a flat, dotted name — it becomes
16+
`php.<dotted.name>(...)` in the guest. `$types` is an optional TypeScript signature
17+
surfaced by `dts()`. This flat registry is the **entire** trust boundary.
18+
19+
### `eval(string $code): mixed`
20+
21+
Run TypeScript or JavaScript and marshal the result back to PHP. Errors raise a
22+
`QuickJSEvalException` located at the original TS line/column (see [errors](errors.md)).
23+
24+
### `grant(mixed $resource): int` / `resolve(int $h): mixed` / `revoke(int $h): bool`
25+
26+
Capability handles for live, stateful objects (DB connections, file handles). The
27+
object stays host-side; JS only ever sees an opaque integer it can pass back to a
28+
capability. The handle **is** the capability.
29+
30+
```php
31+
$pdo = new PDO('sqlite:app.db');
32+
$h = $js->grant($pdo);
33+
$js->register('db.query', fn(int $handle, string $sql) => $js->resolve($handle)->query($sql)->fetchAll());
34+
```
35+
36+
### `manifest(): array` / `dts(): string`
37+
38+
The registration manifest and a generated TypeScript `.d.ts` for the `php` global,
39+
both from the same source of truth.

0 commit comments

Comments
 (0)