Deviations from real JS
lamassu-js aims to match Node closely enough for differential testing over the shared subset — not full spec conformance. This page tracks every place that's known to differ, so you're not surprised by one in production.
Objects
Object.prototypeonly has three methods —hasOwnProperty,toString,valueOf. Real JS also hasisPrototypeOf,propertyIsEnumerable,toLocaleString, and the legacy__proto__accessor; none of those are implemented.toStringalways returns"[object Object]"— there's no per-kindSymbol.toStringTag-style internal class tag, though in practice this is only reachable for plain objects, sinceArray/Map/Set/Date/RegExpall define their owntoStringthat shadows it.Object(primitiveValue)returns the primitive unchanged rather than boxing it into a wrapper object — this engine has no boxed-primitive type at all.Object()/Object(null)/Object(undefined)(a new empty object) andObject(anObject)(identity) both match spec.- Key order follows hash order, not insertion order. Affects
Object.keys/values/entries,for...ofoverObject.entries(...), andJSON.stringifyof plain objects. (Planned to resolve once the engine's property storage moves to hidden-class "shapes," which store properties in insertion order — see the roadmap in the project'sdocs/plan.md.) - Integrity is whole-object, not per-property.
Object.freezeandObject.sealare enforced — on property assignment, indexed array writes,length,delete, the in-place array mutators (push/pop/shift/unshift/reverse/fill/sort), and the host'sjs_object_set— and because the engine is strict-mode throughout, a refused write throws aTypeErrorrather than failing quietly.Object.isFrozenandObject.isSealedreport the state. What's missing is anything finer: there are no per-property attributes here, so there is nowritable/configurabledistinction and noObject.definePropertyto set one. That covers everything guest code can observe of an ordinary frozen object; it only differs if you were reaching for a partially frozen one.
Arrays
- No holes. Array storage is a flat vector, not a sparse structure — every index up to
.lengthis a real, materialized slot.Array(n)andarr.length = nboth throwRangeErrorpast a fixed growth cap rather than eagerly allocating an attacker-chosen size; see Built-ins → Array. Array.prototype.sortis a stable O(n²) binary insertion sort — fine for templating-sized arrays; revisit if you're sorting something large.Array.fromsupports strings and arrays, not arbitrary array-likes or iterators.
Numbers
- Transcendental
Mathfunctions are ~1e-12 accurate, not correctly rounded — a custom freestanding kernel, no libm.sqrt/floor/ceil/trunc/roundare exact (native ops). - Number-to-string formatting is exact for safe integers and for magnitudes within ~1e±22; extreme magnitudes may differ from Node in the last digit (shortest-round-trip via float scaling, not a full big-integer
dtoa).
Strings
Case mapping (toUpperCase/toLowerCase) is ASCII-only — no full Unicode case folding.
Regular expressions
String.prototype.matchAll returns a plain array rather than a lazy iterator (for...of over the result behaves the same either way). String.prototype.split ignores its limit argument.
Promises & async
Promise(executor)is callable withoutnew— a deliberate accommodation, alongside realnew/constructor-function support and prototype chains for everything else.- Unhandled promise rejections are silent. The engine tracks a
handledflag internally but doesn't report unhandled rejections; a host hook could be added by an embedder that needs one. - A pending top-level-await module returns from the host's run call still pending — see Async & host calls.
Modules
- Cross-module cycles degrade to
undefinedfor a binding read before the exporting module has initialized it, rather than throwing a real-JS-style TDZ error. Hoistedexport functiondeclarations are available throughout, so mutually recursive function modules work fine — this only bites a cyclic read of alet/constbinding before its module finishes evaluating. - Named and
export *re-exports are snapshots, taken once the source module finishes evaluating (dependency order guarantees this has already happened). A later mutation of a re-exported mutable binding isn't observed through the re-export.export * as ns fromis live by reference instead (it aliases the source's namespace object). For a templating workload this is exact in practice, since re-exports are almost always functions or constants. - Module evaluation drains the whole microtask queue after each module body runs — there's no cross-module async evaluation ordering beyond plain dependency order.
Not a deviation, but easy to trip on
for...in, var, classes, eval, the Function constructor, Reflect, Proxy, Symbol, generators, typed arrays, and Intl aren't "different," they're absent — see Supported syntax for the full list of non-goals.