Log inGet started
·
testsuite · drop-in viewer
asset⌬ testsuitetestSuiteprimary: init.luau·part ofpackage zerojs.package·originates fromworld f63c1fe3-4…

zerojs_async.testSuite

Exercises the per-VM asynchronous foundation: the event loop (microtask queue, timers, coroutine driver), the shared millisecond clock `rt.nowMs`, and the JS-visible `queueMicrotask` global.

byzero-proxy @ DESKTOP-DB3UJOJ·posted 3mo ago
What it does

zerojs_async tests

Exercises the per-VM asynchronous foundation: the event loop (microtask queue, timers, coroutine driver), the shared millisecond clock rt.nowMs, and the JS-visible queueMicrotask global.

Two layers of coverage:

  • JS-level, through the public zerojs.new():run path — queueMicrotask ordering (script code before microtasks, FIFO across calls, nested enqueues draining in the same turn), the non-callable TypeError, and typeof queueMicrotask === "function". Because vm:run marshals its completion value before draining microtasks, ordering that spans the sync/microtask boundary is proven through a host-bridged log the microtask mutates and a follow-up read, rather than through the run's return value alone.
  • Luau-level, probing the loop object directly via require("zerojs.event_loop") against a real realm from require("zerojs.runtime") — microtask FIFO and re-enqueue-during-drain termination, timer due-ordering with insertion-order tie-breaking, per-timer microtask drains, clearTimer, setInterval re-arming, hasPendingWork transitions, and the spawn/suspend/waker round-trip where resumption is enqueued as a microtask. One row covers vm:run refusing to return while asynchronous work is pending (message directs to runAsync), and one covers rt.nowMs returning a millisecond-resolution recent epoch.

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

conforms to

zero/source-extract/v2

run(src: ?) → void

Evaluate a source string on a fresh VM and return its marshaled completion.

argtypedescription
src?

runError(src: ?) → void

Evaluate a source string that must throw a JS error, returning its name and message read from the VM's structured lastError.

argtypedescription
src?

newLoop( ) → void

A fresh realm to probe the loop object directly. runtime.new() yields the same rt the api builds; event_loop.new(rt) is the same construction vm.new performs.

record(tag: ?) → void

argtypedescription
tag?

hit(tag: ?) → void

argtypedescription
tag?

hit(tag: ?) → void

argtypedescription
tag?

armTimer( ) → void

runLogged(src: ?) → void

Promise observation. `vm:run` marshals its completion before the final microtask drain, so a promise settlement cannot be witnessed through the run's return value. Each ordering/value test injects a host `log`/`capture` function the JS code calls from inside a reaction; after run() returns the final drain has completed, so the Luau-side log already carries the settled observations in their true interleaving.

argtypedescription
src?

log(tag: ?) → void

argtypedescription
tag?

runCaptured(src: ?) → void

Capture a single settled value into a one-cell Luau holder observable after the run's final drain.

argtypedescription
src?

capture(v: ?) → void

argtypedescription
v?

capture(v: ?) → void

argtypedescription
v?

await(handle: ?, maxFrames: ?) → void

These rows cross real engine frames: `vm:runAsync` returns a handle whose `task.spawn` driver pumps the VM loop once per frame. A test body is yieldable, so it drives the handle to completion with `handle:await()` (a `Test.waitUntil` over `handle.done`) and then reads the settled outcome. Per-test wall time stays small: timers in the 0–20ms band and short waitUntil budgets keep each row a handful of frames. Await one runAsync handle to a terminal state and return it. maxFrames is a generous per-row frame budget; a row that hangs fails on the expects below rather than blocking the suite.

argtypedescription
handle?
maxFrames?

log(tag: ?) → void

argtypedescription
tag?

log(tag: ?) → void

argtypedescription
tag?

capture(v: ?) → void

argtypedescription
v?

capture(v: ?) → void

argtypedescription
v?

mark( ) → void

mark( ) → void

tick( ) → void

log(tag: ?) → void

argtypedescription
tag?

mark( ) → void

env_add(a: ?, b: ?) → void

Env-injected host functions that YIELD (task.wait or any coroutine yield) surface to JS as promises: the call expression evaluates to a Promise immediately, the host fn continues on the engine scheduler, and the promise fulfills with the marshaled return value or rejects with the structured error. A host fn that returns WITHOUT yielding keeps the synchronous return path unchanged. A yielding host call in flight counts as pending work, so vm:run refuses it (directing to runAsync) and runAsync's pump does not declare completion while it runs. Host fns for the matrix. Each is a plain Luau closure; the yielding ones call task.wait, which parks them on the engine scheduler until the delay elapses.

argtypedescription
a?
b?

env_sleep_42( ) → void

env_sleep_a( ) → void

env_sleep_b( ) → void

env_late_fail( ) → void

env_early_fail( ) → void

env_table_after_wait( ) → void

env_echo_after_wait(s: ?, n: ?) → void

argtypedescription
s?
n?

bridgeEnv( ) → void

capture(v: ?) → void

argtypedescription
v?

capture(msg: ?, hasDetail: ?, detailEnumerable: ?) → void

argtypedescription
msg?
hasDetail?
detailEnumerable?

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

Showing up to 3 levels deep. Open a part to see what it holds further down.

16+items
❒
package · born here
❒asset
ZeroJS is a JavaScript interpreter for the Zero engine, written in Luau. It parses and executes ES2017-practical JavaScript entirely inside the Luau VM — no native code, no WebAssembly, no external dependency — and marshals values back to Luau transparently. ## Importing ```luau local zerojs = require("~zerojs.api") ``` `~zerojs` is this package's alias, registered from the `name` in its `package.yaml` wherever the package sits. The same spelling therefore works from the world that authors the package and from a world that installed it, at any depth. Every module is reachable the same way — `~zerojs.parser`, `~zerojs.stdlib.promise` — and the package's own modules reference each other with those ids. ## API surface ### Module-level | Symbol | Type | Description | |--------|------|-------------| | `zerojs.eval(src)` | `(string) -> any` | Parse and execute `src` in a fresh one-shot VM. Returns the marshaled JS completion value. Raises a Luau error on a JS exception. | | `zerojs.new(opts?)` | `(table?) -> VM` | Create a persistent VM. `opts.env` seeds additional JS globals from a Luau table. | | `zerojs.null` | sentinel | The JS `null` value (distinct from Luau `nil`). | | `zerojs.undefined` | sentinel | The JS `undefined` value, which marshals to Luau `nil` when returned. | | `zerojs.version` | `string` | Version string of this package. | | `zerojs.lastError(vm)` | `(VM) -> table?` | Returns the structured payload `{ name, message, stack }` from the most recent JS exception on `vm`. `nil` if no exception has occurred on that VM. | ### VM instance | Method | Description | |--------|-------------| | `vm:run(src, opts?)` | Execute `src` in this VM's persistent realm. `opts.sourceName` names the source for error messages and stack frames. Returns the marshaled completion value. Raises directing to `runAsync` when the script leaves a timer armed or a coroutine suspended across a frame. | | `vm:runAsync(src, opts?)` | Execute `src`, driving its event loop across engine frames until it settles. Returns a `RunHandle` immediately. `opts.timeoutMs` rejects the run if it outlasts that many milliseconds. Only one `runAsync` may be in flight per VM at a time; a second call while the first is unsettled raises. | | `vm:get(name)` | Read a global from the VM's realm. | | `vm:set(name, value)` | Write a marshaled Luau value as a JS global in the VM's realm. | ### `RunHandle` (returned by `vm:runAsync`) | Field | Description | |-------|-------------| | `done` | `false` until the run reaches a terminal state, then `true`. | | `ok` | Once `done`, `true` for a resolved run and `false` for a rejection or timeout. | | `value` | The marshaled completion value on a resolved run. | | `error` | The rendered message text on a rejected or timed-out run. | | `lastError` | The structured `{ name, message, stack }` payload for a failed run. | | `thread` | The engine `task.spawn` driver pumping the loop each frame — present only while the run is still pumping, and `nil` once the run settles on its arming frame (a synchronous or microtask-only completion needs no per-frame driver). | | `handle:await(maxFrames?)` | From a yieldable Luau context, poll `done` frame-by-frame until the handle settles, then return it. | A `runAsync` stays pending while the loop has work: an armed `setInterval` keeps it pending exactly as an un-`unref`'d handle keeps Node alive — clear the interval or bound the run with `timeoutMs`. ## Marshaling Luau values passed to `opts.env` or `vm:set` are converted to JS values. JS values returned from `run`/`eval` are converted back to Luau: numbers, strings, and booleans are transparent; arrays become 1-based Luau tables; objects become string-keyed Luau tables; JS functions become callable Luau closures; `undefined` becomes `nil`; `null` becomes `zerojs.null`. ## Step-budget sandbox Each VM runs with a default execution budget of 1,000,000 interpreter steps. A script that exceeds the budget raises a JS `RangeError: step budget exceeded`, which surfaces as a Luau error. The budget resets on each `run`/`eval` call. ## Language scope - Variable declarations: `var` (function-scoped hoisting), `let` and `const` with full TDZ enforcement - Functions: declarations, expressions, arrow functions, closures, rest parameters, default parameters - Classes: declarations and expressions, inheritance via `extends`, `super()` in constructors and methods, getter and setter accessors, instance fields, static fields and methods, computed method keys, named class expressions, TDZ before declaration - Destructuring: object and array patterns in declarations, assignment, and function parameters; nested patterns; rename, default, rest; computed keys in object patterns; elision in array patterns - Spread: array literals, call arguments, `new` arguments, object literals - Template literals (untagged) - Full coercion and equality semantics: `==`/`!=` abstract equality, `===`/`!==` strict equality, `typeof`, `instanceof` - `try`/`catch`/`finally`, `throw` - All loop forms: `for`, `while`, `do-while`, `for-in`, `for-of`; `switch` with fallthrough - `async`/`await` — real event-loop semantics: an async function returns a promise, runs synchronously up to the first `await`, and resumes from a microtask when its awaited operand settles - Property enumerability: `for-in` yields only own enumerable keys; object rest excludes prototype methods ### Asynchrony and the event loop - `Promise` with the full ES surface: `then`/`catch`/`finally`, `resolve`/`reject`, and the combinators `all`, `allSettled`, `race`, and `any` (with `AggregateError` when every input to `any` rejects) - `queueMicrotask` for scheduling a microtask directly - Timers: `setTimeout`/`clearTimeout` and `setInterval`/`clearInterval`, with extra arguments forwarded to the callback - `vm:runAsync(src, opts?)` drives a script's timers, microtasks, and promise settlements across engine frames and returns a `RunHandle` (see the VM table above) - A long synchronous loop under `runAsync` yields at each step-budget boundary and resumes on a later frame instead of raising `RangeError`; the same script under `vm:run` still raises ### Standard library - `Object`: callable coercion plus `keys`, `values`, `entries`, `assign`, `freeze`, `isFrozen`, `create`, `getPrototypeOf`, `defineProperty`, `fromEntries`, `hasOwn`, and the `Object.prototype` methods (`hasOwnProperty`, `toString`, `isPrototypeOf`) - `Function.prototype`: `call`, `apply`, `bind` - Global conversions and predicates: `parseInt`, `parseFloat`, `isNaN`, `isFinite`, `String`, `Number`, `Boolean` - `Array` and `Array.prototype`: construction, `isArray`, `from`, `of`, and the iteration/mutation/query methods (`map`, `filter`, `reduce`, `forEach`, `find`, `some`, `every`, `slice`, `splice`, `concat`, `join`, `indexOf`, `includes`, `sort`, `reverse`, `push`, `pop`, `shift`, `unshift`, `flat`, `fill`, `keys`, `values`, `entries`) - `String.prototype`: `slice`, `substring`, `substr`, `split`, `indexOf`, `includes`, `startsWith`, `endsWith`, `repeat`, `padStart`, `padEnd`, `trim`, `trimStart`, `trimEnd`, `toUpperCase`, `toLowerCase`, `charAt`, `charCodeAt`, `codePointAt`, `at`, `concat`, `replace`, `replaceAll`, `match`, `matchAll`, `search` - `Number`: `isInteger`, `isSafeInteger`, `isFinite`, `isNaN`, `parseFloat`, `parseInt`, `MAX_SAFE_INTEGER`, `MIN_SAFE_INTEGER`, `MAX_VALUE`, `MIN_VALUE`, `EPSILON`, `POSITIVE_INFINITY`, `NEGATIVE_INFINITY`, `NaN`, and `Number.prototype` (`toFixed`, `toString`, `toPrecision`) - `Math`: the constants and functions (`abs`, `floor`, `ceil`, `round`, `trunc`, `sign`, `min`, `max`, `pow`, `sqrt`, `cbrt`, `hypot`, `exp`, `log`, `log2`, `log10`, the trig family, `random`) - `Date`: construction as `new Date()` (now) and `new Date(ms)` (epoch milliseconds), `now`, `getTime`, `getFullYear`, `getMonth`, `getDate`, `getHours`, `getMinutes`, `getSeconds`, `getMilliseconds`, `getDay`, `toISOString`, `toJSON` - `JSON`: `parse` and `stringify` (indent argument and array replacer whitelist) - `Map` and `Set`: construction from iterables, `get`/`set`/`has`/`delete`/`clear`, `add`, `size`, `forEach`, `keys`, `values`, `entries`, insertion-order iteration - `RegExp`: literals and the constructor, `test`, `exec`, `lastIndex`, `source`, `flags`, `global`, `ignoreCase`, `multiline`, `dotAll`, and the `String` regex methods above - Errors: `Error`, `TypeError`, `RangeError`, `ReferenceError`, `SyntaxError` constructors and inheritance; `console.log`/`warn`/`error`; `globalThis`, `NaN`, `Infinity` ### Out of scope by design - Generators and iterators (`function*`, `yield`) - `Proxy` and `Reflect` - Labeled statements and `with` - Tagged template literals - Optional chaining (`?.`) and nullish coalescing (`??`) The full divergence list — where ZeroJS behavior differs from a browser engine — lives in the guide's divergence section. ## Test suites Seven suites ship with the package, run via `tools.tests.run`: ```luau tools.tests.run({ suite = "zerojs_lexer" }) -- tokenization tools.tests.run({ suite = "zerojs_parser" }) -- AST construction and error recovery tools.tests.run({ suite = "zerojs_semantics" }) -- runtime semantics end-to-end tools.tests.run({ suite = "zerojs_stdlib" }) -- the standard-library surface tools.tests.run({ suite = "zerojs_regex" }) -- the regex engine and RegExp methods tools.tests.run({ suite = "zerojs_async" }) -- the event loop, promises, and timers tools.tests.run({ suite = "zerojs_smoke" }) -- unmodified real-world JS programs ```
▲ 1↑ born
▣
module · born here
❒asset
# api The ZeroJS public API: zerojs.new/eval, VM run/get/set, sentinels, and version. The package root init.luau re-exports this module.
▲ 0↑ born
▣
module · born here
❒asset
# runtime The JavaScript object model and coercion engine: property get/set with prototype-chain walking, arrays, functions, key ordering, `instanceof`, and the `toNumber`/`toStringJs`/`toBoolean`/equality operations the interpreter evaluates against.
▲ 0↑ born
▣
module · born here
❒asset
# event_loop.module The per-VM asynchronous core for zerojs: a microtask queue, a timer heap, and a coroutine driver. One loop is built per VM as `event_loop.new(rt)`, where `rt` is the realm (used only for its `nowMs` clock). The loop is Promise-agnostic — `suspend(waker)` takes a callback-registering function, never a promise — so the promise layer can be built on top without the loop depending on it. ## Surface - `loop:enqueueMicrotask(fn)` — append a plain Luau thunk; jobs run FIFO. - `loop:drainMicrotasks()` — run queued jobs until the queue is empty; a job may enqueue more jobs, which run in the same drain. A job error propagates out of `drainMicrotasks` (the caller decides policy); remaining jobs stay queued. - `loop:setTimer(fn, delayMs) -> id` — one-shot timer due at `nowMs + max(0, delayMs)`. - `loop:setInterval(fn, delayMs) -> id` — repeating timer on the same clock; it re-arms after its callback runs. - `loop:clearTimer(id)` — cancels a one-shot or interval; an unknown id is a no-op. - `loop:runDueTimers(nowMs)` — fires every timer due at `<= nowMs` in due-time order (insertion order breaks ties), draining microtasks after each callback. - `loop:hasPendingWork() -> boolean` — true while any timer is armed or any spawned coroutine is suspended awaiting a wake. - `loop:spawn(fn, ...) -> co` — runs `fn` in a coroutine driven by the loop; an error inside propagates to the resumer with its traceback preserved. - `loop:suspend(waker)` — callable only from inside a loop-spawned coroutine. Registers the coroutine as suspended and yields. `waker(resume)` receives a `resume(ok, value)` callback; calling it enqueues the coroutine's resumption as a microtask, and `suspend` returns `(ok, value)` to the coroutine.
▲ 0↑ born
backing path · zerojs.package/tests/zerojs_async.testSuite

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported on this asset or on the parts this page shows. Parts it leaves out are not counted here.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.