Skip to content

Latest commit

 

History

History
212 lines (159 loc) · 7.7 KB

File metadata and controls

212 lines (159 loc) · 7.7 KB

Overview

obj.mjs provides tools for manipulating JS objects in weird ways.

TOC

Usage

import * as o from 'https://cdn.jsdelivr.net/npm/@mitranim/js@0.1.85/obj.mjs'

API

function assign

Links: source; test/example.

Signature: (tar, src) => tar.

Similar to Object.assign. Differences:

  • Much faster.
  • Exactly two parameters, not variadic.
  • Sanity-checked:
    • Target must be a r.
    • Source must be nil or a r.
    • Throws on invalid inputs.

Similar to #patch but doesn't check for inherited and non-enumerable properties. Simpler, dumber, faster.

function patch

Links: source; test/example.

Signature: (tar, src) => tar.

Similar to Object.assign. Differences:

  • Much faster.
  • Takes only two args.
  • Sanity-checked:
    • Target must be a r.
    • Source must be nil or a r.
    • Throws on invalid inputs.
    • Does not override inherited properties.
    • Does not override own non-enumerable properties.

When overriding inherited and non-enumerable properties is desirable, use #assign.

function memGet

Links: source; test/example.

Takes a class and hacks its prototype, converting all non-inherited getters to lazy/memoizing versions of themselves that only execute once. The resulting value replaces the getter. Inherited getters are unaffected.

import * as o from 'https://cdn.jsdelivr.net/npm/@mitranim/js@0.1.85/obj.mjs'

class StructLax extends o.MixStruct(l.Emp) {}

class Bucket {
  static {o.memGet(this)}
  get one() {return new StructLax()}
  get two() {return new StructLax()}
}

const ref = new Bucket()
// Bucket {}

ref.one.three = 30
ref
// Bucket { one: Struct { three: 30 } }

ref.two.four = 40
ref
// Bucket { one: Struct { three: 30 }, two: Struct { four: 40 } }

function MixStruct

Links: source; test/example.

Mixin for classes representing a "struct" / "model" / "record". Also see #MixStructLax. Features:

  • Supports explicit specs with validation / transformation functions.
  • Can be instantiated or mutated from any r (any dict-like object); each field is validated by the user-defined spec.
  • Assigns and checks all declared fields when instantiating via new. Ignores undeclared fields.
  • Supports partial updates via the associated function structMut (not a method), which assigns and validates known fields provided in the input.
  • Supports deep mutation: when updating a struct, automatically detects sub-structs and mutates them, and invokes .mut on any object that implements this method.
  • Uses regular JS fields. Does not use getters / setters, proxies, private fields, non-enumerable fields, symbols, or anything else "strange". Declared fields are simply assigned via =.

Performance characteristics:

  • The cost of constructing or mutating depends only on declared fields, not on provided fields.
  • When the number of declared fields is similar to the number of provided fields, this tends to be slightly slower than Object.assign or #assign.
  • When the number of declared fields is significantly smaller than the number of provided fields, this tends to be faster than the aforementioned assignment functions.
import * as l from 'https://cdn.jsdelivr.net/npm/@mitranim/js@0.1.85/lang.mjs'
import * as o from 'https://cdn.jsdelivr.net/npm/@mitranim/js@0.1.85/obj.mjs'

class Person extends o.MixStruct(l.Emp) {
  static spec = {
    id: l.reqFin,
    name: l.reqStr,
  }
}

// Fails the type check.
new Person({id: 10})
/* Uncaught TypeError: invalid property "name" */

// Fails the type check.
new Person({name: `Mira`})
/* Uncaught TypeError: invalid property "id" */

// Satisfies the type check.
new Person({id: 10, name: `Mira`})
/* Person {id: 10, name: "Mira"} */

// Ignores undeclared fields.
new Person({id: 10, name: `Mira`, slug: `mira`, gender: `female`})
/* Person {id: 10, name: "Mira"} */

function MixStructLax

Links: source; test/example.

Mixin for classes representing a "struct" / "model" / "record". Similar to #MixStruct, with additional support for undeclared fields.

Differences from #MixStruct:

  • When instantiating via new or mutating via structMut, in addition to assigning and validating all declared fields, this also copies any undeclared fields present in the source data.
    • Behaves similarly to #patch, and differently from Object.assign or #assign. Avoids accidentally shadowing inherited or non-enumerable fields.
    • Just like for declared fields, supports deep mutation for undeclared fields.
  • Has slightly worse performance.
import * as l from 'https://cdn.jsdelivr.net/npm/@mitranim/js@0.1.85/lang.mjs'
import * as o from 'https://cdn.jsdelivr.net/npm/@mitranim/js@0.1.85/obj.mjs'

class Person extends o.MixStructLax(l.Emp) {
  static spec = {
    id: l.reqFin,
    name: l.reqStr,
  }
}

// Fails the type check.
new Person({id: 10})
/* Uncaught TypeError: invalid property "name" */

// Fails the type check.
new Person({name: `Mira`})
/* Uncaught TypeError: invalid property "id" */

// Satisfies the type check.
new Person({id: 10, name: `Mira`})
/* Person {id: 10, name: "Mira"} */

// Assigns undeclared fields in addition to declared fields.
new Person({id: 10, name: `Mira`, slug: `mira`, gender: `female`})
/* Person {id: 10, name: "Mira", slug: "mira", gender: "female"} */

Undocumented

The following APIs are exported but undocumented. Check obj.mjs.