Build a Smaller Deno Runtime Boundary
Do not begin by embedding V8. First rebuild the architectural center with a small command language and a local event loop.
What are we preserving?
- a dynamic caller-facing command becomes a typed native operation;
- native services and permissions live in explicit runtime state;
- several operations can be pending concurrently;
- results return through correlation IDs on the event-loop owner;
- long-lived native objects live behind typed resource IDs;
- cancellation and close wake pending operations;
- a second worker owns separate state and communicates by messages.
Suggested construction sequence
1. Define one concrete operation
Accept Command::ReadText { path }. Validate it, check a simple
ReadPermissions value, and return Result<String, OpError>.
2. Separate submission from completion
Assign a PromiseId, put the typed read future into FuturesUnordered, and
store a callback or oneshot sender that knows how to deliver that ID’s result.
Drive the set from a poll_fn-based event loop.
3. Add a resource table
Implement:
type ResourceId = u32;
trait Resource: Any {
fn close(&self) {}
}
Store unlike resources behind Rc<dyn Resource>. Provide generic add<T> and
get<T> methods with checked downcasting. Add an in-memory duplex resource
before using a real socket.
4. Add cancellation
Create a cancellation resource for one read, race it against a delay, and prove that every success, error, abort, and dropped-caller path removes the temporary entry.
5. Make event-loop liveness explicit
Track refed pending operations separately from unrefed background work. Write tests showing when the event loop may exit and when it must stay alive.
6. Add a worker boundary
Run a second local runtime with independent state. Exchange owned messages over bounded channels. Do not share the resource table. This demonstrates why parallel language workers need a message boundary.
7. Compare with production Deno
Only now inspect what Deno adds: V8 handle safety, op2 code generation,
zero-copy buffers, many extension families, JavaScript error classes,
permission prompts, module loading, inspector integration, web compatibility,
platform-specific I/O, and carefully tuned event-loop phase semantics.
Tests that matter more than features
- Two ops complete out of submission order but reach the right callers.
- A permission failure performs no I/O.
- Looking up a resource as the wrong type returns an error.
- Closing a resource wakes a pending read.
- Dropping one result observer does not stop unrelated operations.
- Unrefed work does not keep an otherwise idle loop alive.
- Worker termination wakes a parked event loop.
Rebuild the op/resource/event-loop relationship, not a toy imitation of Deno’s public JavaScript syntax.