API reference

Object, Map & Set

32/32Coverage23/32Strict (zero caveats)

A two-tier object model. Statically-typed objects are fixed-shape heap structs — compile-time field offsets, real per-heap-identity Object.freeze, Object.hasOwn answered at compile time for a literal key. Dynamic objects (any-typed values, untyped JSON, js-mode literals) are real property bags with prototype chains (Object.create, __proto__), property descriptors (defineProperty, accessors, enumerability, descriptor-backed freeze/seal), and runtime add/delete. Map and Set carry the full core API; where JS says Iterable, this compiler narrows to a concrete array, and a missing Map key reads back as a real undefined (as in Node).

Exact Works, with caveats Not supported

Object literals { a: 1 }

Exact
let point: { x: number; y: number } = { x: 10, y: 20 }

Object literals build fixed-shape heap structs. The type may be annotated or inferred from the initializer; objects pass by reference (heap pointer), so a mutation through a function parameter is visible to the caller.

let point: { x: number; y: number } = { x: 10, y: 20 }
console.log(point.x)   // 10
See it in a full example →

Field access obj.field

Exact
obj.field; obj.field = value; obj.field += n

Reads and writes to a struct field by name, including compound assignment. Field access is a compile-time resolved struct offset.

point.x = 30
point.y += 5
console.log(point.y)   // 25
See it in a full example →

Object destructuring

Exact
const { x, y } = point

Binds fields of an object to local variables by name in a single declaration.

Object.keys(obj)

Works, with caveats
Object.keys(obj): string[]

Returns a string[] of the object's field names in ES own-property order. An omitted optional field (x?: T) is not a key: it is filtered out at runtime by its presence, as JSON.stringify already did. The result can be iterated directly without an intermediate variable.

Returns

string[] — Present field names in ES own-property order.

for (const k of Object.keys(person)) {
  console.log(k)
}
console.log(Object.keys(person).length)   // 3
See it in a full example →
⚠ Differences from standard
  • Object.values/entries/for...in still list an omitted optional field (as undefined); only keys filters it

Object.values(obj)

Exact
Object.values(obj): V[]

Returns each own field's value in declaration order: a typed V[] when every field has type V, an any[] for a mixed shape or an interface-typed value.

Returns

V[] — Field values.

for (const v of Object.values(person)) {
  console.log(v)
}
See it in a full example →

Object.entries(obj)

Exact
Object.entries(obj): [string, V][]

Returns real [string, V] tuples ([string, any] for a mixed shape or an interface-typed value), an array's [index, element] pairs; destructure with for (const [k, v] of Object.entries(obj)).

Returns

[string, V][] — Field name/value tuples.

for (const [key, value] of Object.entries(dims)) {
  console.log(key + ': ' + value)
}
See it in a full example →
⚠ Differences from standard
  • On a static array (Object.entries([7, 8])) it is a compile-time rejection

Object.groupBy(arr, fn)

Exact
Object.groupBy(items, fn): Partial<Record<K, T[]>>

Groups an array's elements by the key fn returns for each, producing a null-prototype object that maps each key, in first-seen order, to the array of its members. A non-string key is converted to a string. Access groups by key and enumerate them via Object.keys.

const byLetter = Object.groupBy(words, (w) => w.substring(0, 1))
const aWords = byLetter['a']
console.log(aWords.length)        // 3
See it in a full example →

Object.assign(target, ...src)

Works, with caveats
Object.assign(target, ...src): target

Copies each source's fields into target in argument order — a later source overwrites an earlier one on a shared field. Mutates and returns target (same heap object, no copy). An array target takes its sources' indices, growing as needed.

const applied = Object.assign(defaults, userPrefs)
console.log(defaults.theme)     // dark (same object)
See it in a full example →
⚠ Differences from standard
  • Every field a source contributes must already exist on target's struct type — a source field target's type doesn't have is a clean compile error (fixed-shape heap structs), not grafted on as in real JS

Object.defineProperty / getOwnPropertyDescriptor / accessors

implemented
Object.defineProperty(o, "k", { value: 1, enumerable: false }); const d = Object.getOwnPropertyDescriptor(o, "k"); const t = { get x() { return this._x; }, set x(v) { this._x = v; } };

Real property descriptors on dynamic (any-typed, or js-mode untyped-literal) objects: defineProperty with the spec's create defaults and non-configurable redefinition rules, getOwnPropertyDescriptor in both data and accessor forms, getOwnPropertyNames including non-enumerables, and object-literal get/set accessors invoked with the original receiver — inherited getters through the prototype chain included. Object.keys, for...in, and JSON.stringify honor enumerability (stringify and spread invoke getters), and rejected writes or deletes throw the module-strict JS TypeErrors. Object.freeze/seal/preventExtensions enforce through the same attributes, with truthful isFrozen/isSealed/isExtensible. Object.defineProperties defines each key of its second argument the same way, and functions carry descriptors too. getOwnPropertyDescriptor and getOwnPropertyNames also read a statically-typed object's own fields; defineProperty needs a dynamic object, since a struct has no descriptor table.

new Proxy(target, handler)

implemented
const p = new Proxy(target, { get(t, key) { return key in t ? t[key] : 42; }, set(t, key, v) { t[key] = v * 2; return true; } });

Proxies over dynamic objects with the get, set, has and deleteProperty traps dispatched inside the object runtime itself — property reads, writes, the in operator and delete on a proxy all consult the handler, and forward straight to the target when a trap is absent. JSON.stringify of a proxy serializes the target. Other traps (ownKeys, apply, construct, getOwnPropertyDescriptor) are not consulted, and statically-typed structs can't be proxied.

Reflect

Works, with caveats
Reflect.get(o, "a"); Reflect.set(o, "b", 2); Reflect.ownKeys(o); Reflect.defineProperty(o, "c", { value: 3 }); Reflect.defineMetadata("k", v, o, "prop"); Reflect.getMetadata("k", o, "prop");

The Reflect surface mapped 1:1 onto the dynamic-object runtime: get, set, has, deleteProperty, ownKeys, getPrototypeOf, setPrototypeOf, isExtensible, preventExtensions and defineProperty. The boolean-returning forms (set, deleteProperty, setPrototypeOf) return the success flag instead of throwing, per spec. A metadata API — defineMetadata/getMetadata/getOwnMetadata/hasMetadata/hasOwnMetadata — stores on a dynamic-object target (no prototype-chain walk, so get == getOwn); it also backs -emit-decorator-metadata's design:* metadata. Reflect.apply(f, thisArg, args) calls a function value with an argument array. Reflect.construct and Reflect.metadata-as-a-decorator-factory are missing; the object methods' targets must be dynamic objects.

Object.create() / getPrototypeOf / setPrototypeOf / __proto__

implemented
const child: any = Object.create(base); Object.getPrototypeOf(child) === base; // true child.__proto__ = other;

Real prototype chains on dynamic (any-typed) objects: Object.create(proto or null), get/setPrototypeOf, the __proto__ accessor (non-object assignments silently ignored, as in JS), and the { __proto__: base } literal form. Property reads and the in operator walk the chain — an own property shadows an inherited one, and delete re-exposes the prototype's value — while writes, Object.keys, for...in, and JSON.stringify stay own-only, matching JS. A prototype cycle throws the Node-matching TypeError. Statically-typed structs have no prototype link (the two-tier model), and Object.create's property-descriptors second argument is not yet supported.

Object.freeze(obj)

Exact
Object.freeze(obj): obj

Real runtime enforcement, not a no-op: tracks obj's heap pointer in a global frozen-object set, checked at every field-write site, so a blocked write throws a catchable Error even through a different alias or function parameter. Adding/deleting fields is already structurally impossible, so value-write is the only thing freeze needs to enforce. Arrays freeze too, every mutation checking the array's level.

Object.freeze(conf)
try {
  conf.port = 9090
} catch (e) {
  console.log('blocked: cannot write to a frozen object')
}
See it in a full example →

Object.seal(obj)

Exact
Object.seal(obj): obj

A static object's shape is fixed at compile time, so seal has nothing further to enforce — existing fields stay freely writable. It records the object's integrity level, so Object.isSealed/isFrozen/isExtensible answer as JS does (as they do after Object.freeze and Object.preventExtensions).

Object.seal(sealed)
sealed.port = 2
console.log(sealed.port)  // 2 (seal never blocked the value write)
See it in a full example →

Object.hasOwn() / .hasOwnProperty()

Exact
Object.hasOwn(obj, key): boolean; obj.hasOwnProperty(key): boolean

Own-key check. On a fixed-shape object a string-literal key is answered at compile time, except an optional field (x?: T), whose answer is its runtime presence ('age' in { name } is false, as in Node). A runtime string key is compared against the object's own keys, and a dictionary or any value is tested at run time. The `in` operator also finds what the prototype holds: Object.prototype's members and a class instance's methods and accessors.

Returns

boolean — Whether the field exists on the object.

console.log(Object.hasOwn(base, 'x'))     // 1
console.log(base.hasOwnProperty('q'))     // 0
console.log('x' in base)                  // 1
See it in a full example →

Object.fromEntries()

Works, with caveats
Object.fromEntries(entries: [string, V][]): dynamic object

Returns a dynamic object (a Map<string,V>-backed value); read via obj.field / obj[key]. Seeds via the same [K,V][] walker new Map(entries) uses.

⚠ Differences from standard
  • Keys must be strings (a [string, V][] array) — real JS stringifies any key and accepts symbol keys

Object spread { ...obj, key: val }

Exact
{ ...obj, key: val }

Copies a source object's fields, then applies later properties in source order — a property mentioned again simply overwrites the earlier value. Spread is a shallow copy: nested object fields are shared, not cloned. A spread may also add new fields not present on the source.

let moved = { ...base, y: 20 }
console.log(moved.y)   // 20
let withZ = { ...base, z: 3 }
console.log(withZ.z)   // 3
See it in a full example →

Computed property keys { [expr]: value }

Exact
{ [expr]: value }

Built as JavaScript builds it: a dynamic object whose keys exist at run time. Values of any types, methods (q[k](), q.name()), getters, spreads and a declared index-signature type all work, and the literal can be held in any as the same object. A well-known-symbol key (Symbol.asyncIterator / Symbol.iterator) desugars to a reserved static key, so the literal stays a static struct driving for...of / for await...of.

const k = 'b'
const obj = { a: 1, [k]: 2 }
console.log(obj[k])     // 2
obj['c'] = 99
console.log(obj.c)      // 99
See it in a full example →

Shorthand property { x }

Exact
{ x } // sugar for { x: x }

`{ x }` is sugar for `{ x: x }`, referencing the in-scope variable of the same name. Shorthand and explicit properties can be mixed freely.

let sx: number = 5
let sy: number = 6
let shorthandPoint = { sx, sy }
console.log(shorthandPoint.sx)   // 5
See it in a full example →

Method shorthand { foo() {...} }

Works, with caveats
{ foo() {...} } // sugar for { foo: function() {...} }

Desugars to { foo: function() {...} } — a plain anonymous function value, reusing the same closure machinery. A string-literal key can carry a method body too.

const calculator = {
  add(a: number, b: number): number {
    return a + b
  },
  label: "calc"
}
console.log(calculator.add(2, 3))   // 5
See it in a full example →
⚠ Differences from standard
  • this in a method-shorthand body is bound only when the literal's contextual type gives the method a this: T parameter; otherwise it is a clean compile-time rejection, where TypeScript types it as the object literal
  • No generator method shorthand (*g() {}): rejected like a generator expression used as a value

Map.set/get/has/delete/keys/values

Exact
new Map<K, V>(); m.set(k, v); m.get(k); m.has(k); m.delete(k); m.keys()

.get() returns V | undefined — a missing key reads as a real undefined (as in Node), distinguishable from a stored 0/false/"" via the presence-flagged representation; narrow, ?? d, or ! before use. A bare new Map() with heterogeneous keys widens to an any-keyed runtime with JS SameValueZero equality (string/number/NaN/boolean/object/null keys coexist, NaN matches NaN, objects key by reference). An explicitly-typed object- or array-keyed Map (Map<{…},V>, Map<T[],V>) also keys by reference identity: the same reference round-trips, a distinct reference of equal content is a different key, and keys()/entries()/forEach surface the live references.

const scores = new Map<string, number>();
scores.set("alice", 95);
console.log(scores.get("alice"));          // 95
console.log(scores.get("dave") === undefined); // true
See it in a full example →

Map.size

Exact
m.size: number

The number of entries currently in the map.

Returns

number — Entry count.

console.log(scores.size);         // 3
scores.delete("bob");
console.log(scores.size);         // 2
See it in a full example →

Map.entries()

Exact
m.entries(): MapIterator<[K, V]>

Returns a live Map Iterator of [K, V] tuples: next(), the iterator helpers (map, filter, take, drop, flatMap, reduce, toArray, forEach, some, every, find), for...of, spread and Array.from. An entry added before the iterator reaches it is visited, a deleted one is not; keys() and values() work the same way.

Returns

MapIterator<[K, V]> — A live iterator over the entries.

for (const [key, value] of lookup.entries()) {
  console.log(key + " = " + value);
}
See it in a full example →

Map.forEach()

Exact
m.forEach(fn: (value, key, map) => void): void

Calls fn(value, key, map), matching real JS's full callback signature; the 3rd argument is the map being iterated. A callback declaring fewer parameters receives only the leading ones.

scores.forEach((v, k) => {
  console.log(k + " -> " + v);
});
See it in a full example →

Map.clear()

Exact
m.clear(): void

Resets size to 0 in place — doesn't free/reallocate the backing arrays ("leak by design" memory model); immediately reusable afterward.

scores.clear();
console.log(scores.size); // 0
scores.set("dave", 100);
console.log(scores.size); // 1
See it in a full example →

new Map(entries)

Exact
new Map(iterable?: Iterable<[K, V]>): Map<K, V>

Takes any iterable of [key, value] pairs — an array, another Map, an iterator, a generator or an object with [Symbol.iterator]. K/V are inferred from the entries when no <K, V> is given; duplicate keys resolve last-wins.

const prices = new Map([["pen", 2], ["notebook", 5], ["eraser", 1]]);
console.log(prices.get("notebook")); // 5
See it in a full example →

new Set(iterable)

Exact
new Set(iterable?: Iterable<T>): Set<T>

Pre-populates a set from any iterable — an array, another Set, an iterator, a generator or an object with [Symbol.iterator] — collapsing duplicates. The element type is inferred from the elements, or given explicitly with <T>.

const fromLiteral = new Set([1, 2, 3, 2, 1]);
console.log(fromLiteral.size); // 3
See it in a full example →

Set.add/has/delete/values

Exact
new Set<T>(); s.add(v); s.has(v); s.delete(v); s.values()

Adds, tests membership, removes, and enumerates elements. Adding a value already present is a no-op.

const seen = new Set<string>();
seen.add("apple");
seen.add("apple");  // duplicate — no effect
console.log(seen.size);         // 2
console.log(seen.has("apple")); // 1
See it in a full example →

Set.size

Exact
s.size: number

The number of distinct elements currently in the set.

Returns

number — Element count.

console.log(ids.size);    // 2
console.log(ids.has(20)); // 1
See it in a full example →

Set.forEach()

Exact
s.forEach(fn: (value, value2, set) => void): void

Calls fn(value, value, set) — real JS's Set.prototype.forEach passes the value twice (Map/Set callback-shape parity) then the set itself; each argument is supplied per the callback's declared arity.

ids.forEach((id) => {
  console.log("id " + id);
});
See it in a full example →

Set.clear()

Exact
s.clear(): void

Removes every element, size drops to 0, set stays usable. Same in-place reset as Map.clear().

seen.clear();
console.log(seen.size); // 0
seen.add("date");
console.log(seen.size); // 1
See it in a full example →

WeakMap / WeakSet / WeakRef

Works, with caveats
new WeakMap(); new WeakSet(); new WeakRef(obj)

Object-identity-keyed weak collections and references. Under -mm=gc these are genuinely weak via Boehm disappearing links (a collected referent's .deref() / WeakMap.has observably drops); a WeakRef keeps its target to the end of the job that made or derefed it, as the spec does. gc() forces a full collection under gc, a no-op under manual.

⚠ Differences from standard
  • Object-identity keys only (a primitive key is a clean compile error); non-iterable (no size/iteration — matches spec)
  • Under -mm=manual (default) a weak reference is strong: nothing is ever collected, so .deref() never becomes undefined and keys persist ("leak by design"). Real weak semantics require -mm=gc
  • A new WeakMap() initial-entries argument is out of V1 scope