npm.io
5.6.0 • Published 1 week ago

@lowdefy/helpers

Licence
Apache-2.0
Version
5.6.0
Deps
2
Size
112 kB
Vulns
0
Weekly
7.9K
Stars
3.0K

@lowdefy/helpers

Lowdefy helper functions

Path syntax

get, set, unset and omit address values with dot-notation paths ('a.b.0.c'). A dot is always a separator unless it is escaped with a backslash, in which case it is a literal character in the segment.

get({ a: { b: 1 } }, 'a.b'); // returns 1        - two segments
get({ 'a.b': 1 }, 'a\\.b'); // returns 1        - one segment, literal key 'a.b'

An unescaped path can still reach a literal dotted key. At each level of the walk the strict segment wins if it is an own key of the target, whatever value it holds; only when it is absent is the segment joined with successive following segments, and the shortest joined key present on the target is used. A nested match therefore always takes precedence, and a present strict segment blocks the join even when it holds a value the walk cannot descend into:

get({ 'a.b': { c: 1 } }, 'a.b.c'); // returns 1 - joined to the literal key 'a.b'
get({ a: { b: { c: 1 } }, 'a.b': { c: 2 } }, 'a.b.c'); // returns 1 - the strict segment wins
get({ a: 1, 'a.b': 2 }, 'a.b'); // returns undefined - 'a' is present, the join is never tried
unset({ 'a.b': 1 }, 'a.b'); // deletes the key, returns true
set({ 'a.b': { c: 1 } }, 'a.b.c', 2); // returns { 'a.b': { c: 2 } } - no nested twin is created

Candidates are tried shortest-first, and there is no backtracking: once a joined key matches, the walk commits to it, and a later miss ends resolution at the committed key rather than retrying a longer join. get returns the default, unset is a no-op, and set writes into the committed key, autovivifying inside it:

get({ 'a.b': {}, 'a.b.c': 1 }, 'a.b.c'); // returns undefined - committed to 'a.b'
unset({ 'a.b': {}, 'a.b.c': 1 }, 'a.b.c'); // returns true, 'a.b.c' is left intact
set({ 'a.b': {}, 'a.b.c': 1 }, 'a.b.c', 2); // writes 2 to obj['a.b'].c, not to obj['a.b.c']
set({ 'a.b': {} }, 'a.b.c.d', 5); // obj['a.b'] becomes { c: { d: 5 } } - autovivified inside

Escaping remains the way to address a literal dotted key unambiguously, and is the only way to reach one that a nested match would otherwise shadow:

get({ 'a.b': { c: 1 } }, 'a\\.b.c'); // returns 1
get({ a: { b: { c: 1 } }, 'a.b': { c: 2 } }, 'a\\.b.c'); // returns 2 - past the nested match
unset({ 'a.b': 1 }, 'a\\.b'); // deletes the key, returns true

get, set and unset all apply this rule identically, and none of them takes a whole-path shortcut ahead of the walk — which is what makes the three of them resolve the same key for a given path. A path that is wholly an own key of the target still resolves, through the join rather than ahead of it, so the strict segment wins where both exist:

get({ 'a.b': 1 }, 'a.b'); // returns 1 - joined, no nested 'a' to descend
get({ a: { b: 2 }, 'a.b': 1 }, 'a.b'); // returns 2 - the strict segment wins

Resolving the same key is not the same as round-tripping. get returns the default for a path it cannot resolve and set then materialises that path, so set(obj, path, get(obj, path)) is not a no-op on an absent path:

const obj = { x: 1 };
set(obj, 'p.q', get(obj, 'p.q'));
// obj becomes { x: 1, p: { q: undefined } }

splitPath and joinPath are the escape-aware primitives behind this and are exported for consumers that need to manipulate paths without losing escape information.

Reserved keys

Seven keys are prototype-pollution vectors and are rejected wherever a path segment or map key is addressed:

__proto__
constructor
prototype
__defineGetter__
__defineSetter__
__lookupGetter__
__lookupSetter__

set, unset, get, omit (via unset), setKey, getKey and unsetKey throw ReservedKeyError when any segment or key matches. Rejecting is deliberate — silently filtering the segment would redirect set(obj, 'a.__proto__.b', 1) to a.b, writing to a different location than the caller asked for. The caller decides whether to catch, log, skip or propagate.

Use isReserved to check a key up front instead of catching ReservedKeyError.

Names that merely live on Object.prototype but are not pollution vectors (hasOwnProperty, toString, valueOf, …) are allowed.

Two helpers are exceptions and skip reserved keys instead of throwing: mergeObjects and urlQuery.parse. In both, the reserved name arrives as data — inside a merged value (JSON.parse('{"__proto__":{…}}')), or as a URL parameter name — not as a path a developer typed, so dropping it misroutes nothing, and throwing would abort an otherwise-valid config merge or query parse over a single poisoned field.

Keyed maps

For any map keyed by external or user-derived values — URL params, request body fields, action arguments, YAML ids — build it with Object.create(null) and write through setKey/getKey/ unsetKey rather than obj[key] = value:

const modules = Object.create(null);
setKey(modules, entry.id, entry);
getKey(modules, requestedId, null);

Traps worth knowing:

  • Keys must be strings. setKey(map, 1, v) throws TypeError — coerce numeric ids yourself.
  • Targets must be plain objects. Arrays and class instances throw TypeError; Object.create(null) is accepted (type.isObject reports true for it).
  • getKey returns its default only when the key is genuinely absent. A key holding undefined returns undefined, not the default.
  • Native Map needs none of this — it stores keys in an internal slot and is pollution-safe by construction. Use Map directly where it fits.

A site that skips or warns on a reserved key instead of throwing should guard with isReserved rather than catching ReservedKeyError.

Usage

applyArrayIndices
(arrayIndices: number[], name: string): string

Apply arrayIndices to a object id. Substitutes all instances of $ character in name with a index from arrayIndices, until there are no more indices or $'s.

applyArrayIndices([1, 2], 'array.$.subArr.
get
(
  target: any,
  path: string | number,
  options?: {
    default?: any,
    copy?: boolean,
  }
): any

Get a value from a target object, using path with dot-notation. Returns undefined or the optional default value if the value is not found. If options is not a plain object it is taken as the default value, so get(obj, 'a.b', 'fallback') works. With copy: true the result is deep-copied with serializer.copy.

get({ a: [{ b: 1 }] }, 'a.0.b'); // returns 1
get({ a: [{ b: 1 }] }, 'a.7.b', { default: 4 }); // returns 4

Paths are strings (numbers are coerced to strings). Array paths are not supported and return the default.

At each level of the walk the strict segment wins if it is present on the target. If it is absent, the segment is joined with successive following segments and the shortest joined key present on the target is used, so a literal dotted key is reachable at any depth without escaping. There is no backtracking: once a joined key matches, a later miss returns the default rather than retrying a longer join. Shortest-first applies at the root too — there is no whole-path shortcut ahead of the walk, which is what makes set, get and unset resolve the same key for a given path. That is not a round trip: set(obj, path, get(obj, path)) materialises an absent path with the default rather than leaving the target unchanged.

get({ attributes: { 'a.b': 'v' } }, 'attributes.a.b'); // returns 'v'
get({ a: { b: { c: 1 } }, 'a.b': { c: 2 } }, 'a.b.c'); // returns 1 - the strict segment wins
get({ 'a.b': {}, 'a.b.c': 1 }, 'a.b.c'); // returns undefined - no backtracking

Traversable values are plain objects, arrays and errors — and, because type.isObject cannot distinguish them from plain objects, class instances whose Object.prototype.toString tag is [object Object], so get({ i: new Instance() }, 'i.own') reads the instance's own property. Functions, Date, URL, Map, Set, RegExp, Promise, Buffer, typed arrays, null, undefined and primitives are not traversable, and yield the default when the path continues past them.

Traversability only governs stepping through a value on the way to a child. It says nothing about a value that is the endpoint of the path: get({ s: socket }, 's') returns the live socket, and with copy: true serializes its internals, without the predicate being consulted at all.

A lookup on an error reads the error's extractErrorProps form, so name resolves and an own key holding a class instance arrives as a '[Object: Name]' marker rather than a live object. An error that is the endpoint of the path is returned as-is — no lookup happens on it, so no conversion happens either.

get(error, 'cause.code'); // reads through an Error
get(error, 'name'); // returns 'Error'
get({ e: error }, 'e'); // returns the Error instance itself

Routing error lookups through extractErrorProps also inherits its depth limits, so error-borne data is not read at full fidelity. The cause chain resolves three levels deep (MAX_CAUSE_DEPTH); the fourth cause is the string '[Truncated]', and a lookup on it returns the default. Lowdefy's own wrap (ActionErrorRequestErrorServiceError → driver error) is only three cause links deep, so it resolves in full - get(actionError, 'cause.cause.cause.message') reads the driver error's message unchanged. Truncation needs a fifth link, such as a driver error that itself wraps a lower-level cause like a socket error. Objects held on an error are truncated at five levels (MAX_OBJECT_DEPTH). A miss returns a recognisable default, but reading the subtree itself returns a tree with '[Truncated]' baked in as a string literal — which will render if it reaches app config through something like _actions: someAction.error.data.

let error = new Error('l4');
for (const message of ['l3', 'l2', 'l1', 'l0']) error = new Error(message, { cause: error });
get(error, 'cause.cause.cause.message'); // returns 'l3'
get(error, 'cause.cause.cause.cause.message'); // returns undefined - past MAX_CAUSE_DEPTH

error.data = { a: { b: { c: { d: { e: { f: 'deep' } } } } } };
get(error, 'data.a.b.c.d.e.f'); // returns undefined - past MAX_OBJECT_DEPTH
get(error, 'data'); // returns { a: { b: { c: { d: { e: '[Truncated]' } } } } }

A third fidelity loss is not about depth: extractErrorProps enumerates an error's own keys with Object.keys, so a non-enumerable own property or an accessor never reaches the extracted form, and get returns the default for it rather than the value. AggregateError's own errors array is non-enumerable, so it is lost this way; a getter defined on an Error subclass is lost the same way. This is scoped narrowly - Node's own fs/ENOENT-style errors are unaffected, since errno, code, syscall and path are ordinary enumerable own properties, and no Lowdefy error class defines a property with Object.defineProperty - so the loss reaches third-party and built-in errors, not Lowdefy's own. AggregateError is the one an app author is realistically likely to meet.

get(new AggregateError([new Error('a')], 'agg'), 'errors.0.message'); // returns undefined - errors is non-enumerable

Lookups are own properties only, so a built-in prototype member is never returned as a value: get({}, 'toString') yields the default, and get([1], 'length') returns 1 because length is own on arrays.

Reserved segments (see Reserved keys) at any depth throw ReservedKeyError, even when a default is given — a reserved segment is illegal input, not a missing path. Wrap in try/catch to fall back to the default. The scan runs on the split segments before the walk, so a literal dotted key containing a reserved name throws rather than resolving: get({ 'a.constructor': 1 }, 'a.constructor') throws, matching set and unset.

getKey
(target: object, key: string, defaultValue?: any): any

Read a single key off a plain object. The key is literal — no dot-path splitting. Reads with Object.hasOwn, so inherited members never leak as values. Returns defaultValue when the key is absent. Throws TypeError if target is not a plain object or key is not a string, and ReservedKeyError if key is reserved.

getKey({ 'a.b': 1 }, 'a.b'); // returns 1
getKey({}, 'toString', null); // returns null, not Object.prototype.toString
isReserved
(key: string): boolean

True if key is one of the reserved keys. Use this to guard a call site that should skip, warn or otherwise degrade on a reserved key instead of catching ReservedKeyError — see Reserved keys and Keyed maps.

isReserved('__proto__'); // returns true
isReserved('toString'); // returns false
joinPath
(segments: string[]): string

Join segments into a dot-path, re-escaping literal backslashes (\\\\) and literal dots (\\.) inside a segment. Inverse of splitPath. Throws TypeError if segments is not an array. Non-string segments are coerced with String.

joinPath(['a.b', 'c']); // returns 'a\\.b.c'
joinPath(splitPath('a\\.b.c')); // round-trips to 'a\\.b.c'
mergeObjects
(objects: object[]): object

Deep-merge an array of plain objects, left to right. Non-plain-object entries in the array are ignored; a non-array argument is returned as-is.

mergeObjects([
  { a: 1, c: 4 },
  { a: 2, b: 3 },
]); // returns { a: 2, b: 3, c: 4 }
  • Non-mutating. Inputs are never modified; a fresh object is returned.

  • Arrays are atomic leaves. A later array replaces an earlier one rather than index-merging into it. Same for Date, RegExp, Map and any other non-plain value.

  • Reserved keys are skipped, not thrown on — see Reserved keys.

  • Plain objects and arrays are always copied. No plain object or array in the result is reference-identical to one in the inputs, at any depth reachable through plain objects and arrays. Every other value — Date, RegExp, Map, a function, and any other value whose type tag marks it as non-plain — is an opaque leaf and is shared by reference, along with whatever it holds: a plain object hanging off a shared Map or function is shared too. A plain user class is not exempt from the split: it has no such tag, so it is copied and flattened into a bare object, losing its prototype and methods.

    The copy carries own enumerable string keys only. Non-enumerable keys and symbol keys are dropped, a getter is flattened to its value at merge time, and a null-prototype object comes back with Object.prototype — so an object deliberately created with Object.create(null) loses that hardening on the way through a merge.

    const date = new Date();
    mergeObjects([{ at: date }, {}]).at === date; // true, shared by reference
    
    class Widget {}
    mergeObjects([{ w: new Widget() }, {}]).w instanceof Widget; // false, copied and flattened
  • A later undefined replaces an earlier value rather than being skipped. A caller that wants "no override" must omit the key rather than set it to undefined.

    mergeObjects([{ a: 1 }, { a: undefined }]); // returns { a: undefined }
    mergeObjects([{ a: 1 }, {}]); // returns { a: 1 }

    Every in-repo call site is defaults-first, overrides-last (e.g. [connection, request], [defaultTypesMap, customTypesMap]). A call site that inverts that order and passes a possibly-undefined value in the override position silently erases the value it meant to keep.

  • No identity pass-through. mergeObjects([x]) returns a copy, never x itself. If a caller needs a stable reference across renders, memoise at the call site.

omit
(object: object, list: string[]): object

Remove an array of keys from a object. Uses unset from this package, and inherits its behaviour — dot-paths, and ReservedKeyError on a reserved segment.

omit({ a: 1, b: 2, c: 3, d: 4 }, ['a', 'd']); // returns { b: 2, c: 3 }
ReservedKeyError
new ReservedKeyError(segment: string)

Thrown by the path and key helpers when a segment or key is one of the reserved keys. Extends Error with name = 'ReservedKeyError' and a segment property carrying the offending key. The message is always `Reserved key "${segment}"` — the fixed format is part of the contract, so the segment is surfaced in logs and serialized error payloads.

try {
  set(state, userPath, value);
} catch (error) {
  if (error instanceof ReservedKeyError) {
    throw new ConfigError(`Reserved key "${error.segment}" cannot be used in :set_state`, {
      cause: error,
    });
  }
  throw error;
}
serializer
serializer.copy
serializer.deserialize
serializer.deserializeFromString
serializer.serialize
serializer.serializeToString
set
(target: any, path: string, value: any): any

Sets a value in a object at a key given by path, and returns target. Intermediate objects are created as needed (autovivification); the next segment decides whether a missing intermediate becomes an array or an object. Returns target unchanged if target is not a plain object or path is not a string.

const obj = { a: 1 };
set(obj, 'b.c', 2);
// obj becomes { a: 1, b: { c: 2 } }

set(obj, 'd.0.e', 3);
// obj.d becomes [{ e: 3 }] - the integer segment creates an array

At each level of the walk the strict segment wins if it is present on the target. If it is absent, the segment is joined with successive following segments and the shortest joined key present on the target is used, so a write reaches a literal dotted key instead of creating a nested twin beside it. If nothing matches, the intermediate is created as usual.

const obj = { 'a.b': { c: 1 } };
set(obj, 'a.b.c', 2);
// obj becomes { 'a.b': { c: 2 } } - no obj.a is created

const both = { a: { b: {} }, 'a.b': {} };
set(both, 'a.b.c', 1);
// writes to both.a.b.c - the strict segment wins when both are present

Paths are strings only; array paths are not supported. The leaf value always replaces what is there; to merge instead, write set(obj, path, mergeObjects([get(obj, path), value])).

Reserved segments (see Reserved keys) at any depth throw ReservedKeyError before anything is written, so a rejected path leaves the target untouched.

setKey
(target: object, key: string, value: any): object

Set a single key on a plain object. The key is literal — no dot-path splitting, no autovivification. Throws TypeError if target is not a plain object or key is not a string, and ReservedKeyError if key is reserved (including on Object.create(null) targets, where the write would be safe — consistency beats per-target precision). Returns target.

const obj = {};
setKey(obj, 'a.b', 1); // sets the literal key 'a.b'
// obj becomes { 'a.b': 1 }

Use setKey rather than obj[userKey] = value wherever the key is derived from user input — see Keyed maps.

splitPath
(path: string): string[]

Split a dot-path into segments. \\. is a literal dot and \\\\ a literal backslash; any other backslash is an ordinary character, so a key such as a\\b needs no escaping. Throws TypeError if path is not a string.

splitPath('a.b.c'); // returns ['a', 'b', 'c']
splitPath('a\\.b.c'); // returns ['a.b', 'c']
stableStringify
(
  object: any
  options?: {
    cmp?: function,
    cycles?: boolean,
    space?: string | number,
    replacer?: function
  }
)

Derived from https://github.com/substack/json-stable-stringify

Returns a deterministic JSON stringified object.

swap
(
  arr: any[],
  from: number,
  to: number
)

Swaps the object at the from index with the object at the to index.

swap([0, 1, 2, 3, 4], 2, 3); // returns [0, 1, 3, 2, 4]
type

A collection of type predicates used across the monorepo. Prefer these over native checks so behaviour stays consistent.

type.typeOf(value): string

Returns the Lowdefy type name: 'undefined', 'null', 'boolean', 'number', 'bigint', 'string', 'symbol', 'array', 'date', 'error', 'regexp', 'map', 'set', 'weakmap', 'weakset', 'promise', 'function', a typed-array/'buffer' name, 'object' for plain objects, or the lowercased constructor name for anything else (new URL(...) is 'url').

Predicate True for
isArray Array.isArray
isObject plain objects only — Object.create(null) yes, new URL(...) no
isString typeof === 'string'
isRegExp instanceof RegExp
isFunction any callable, generator functions included
isBoolean typeof === 'boolean'
isNumber typeof === 'number' and finite — NaN and Infinity are false
isNumeric Number(value) is not NaN — note '', null and [] coerce to 0, so true
isInt Number.isInteger
isDate instanceof Date with a valid time — new Date('garbage') is false
isError instanceof Error
isSet a JS Set instance (not "is defined")
isNull null
isUndefined undefined
isNone null or undefined — the check to reach for
isPrimitive undefined, null, string, number, boolean and date
isEmptyObject a plain object with no own keys
isDateString an ISO-8601 date-time string
isName a valid Lowdefy id — [a-zA-Z0-9_.], no leading/trailing ., no numeric-leading segment, not lowdefy-prefixed
isOpRequest a plain object with a _request key holding a valid name

enforceType(typeName, value) returns the value when it matches typeName, and a safe fallback rather than throwing when it does not: null for 'string' (empty strings included), 'number', 'date', 'primitive' and 'object', false for 'boolean', [] for 'array', and for 'any' the value unless it is undefined. An unknown typeName returns null.

isPrimitive treating date as primitive is a deliberate Lowdefy convention, not JS semantics. The _type: primitive operator is app-developer-facing — do not "fix" this without coordinating with the operators-js, blocks-antd selector, and nunjucks consumers.

Type identification uses instanceof only; there is no cross-realm (vm/iframe/worker) duck-typing.

unset
(object: object, property: string): boolean

Unset a property on a object. Supports dot-notation. Returns true, including when the path does not exist or an intermediate is missing or primitive (a no-op).

const obj = { a: { b: [] } };
unset(obj, 'a.b'); // returns true
// obj becomes { a: {} }

At each level of the walk the strict segment wins if it is present. If it is absent, the segment is joined with successive following segments and the shortest joined key present on the target is used, so a literal dotted key is reachable at any depth without escaping. A present strict segment always wins, even when it holds a value the walk cannot descend into: unset({ a: 1, 'a.b': 2 }, 'a.b') is a no-op.

const obj = { 'a.b': 1 };
unset(obj, 'a.b'); // returns true
// obj becomes {}

const both = { a: { b: 1 }, 'a.b': 2 };
unset(both, 'a.b'); // deletes the nested b, the strict segment wins
// both becomes { a: {}, 'a.b': 2 }

Joined candidates are matched shortest-first and there is no backtracking: once a joined key matches, a later miss is a no-op rather than a retry with a longer join. Given { 'a.b': {}, 'a.b.c': 1 }, unset(obj, 'a.b.c') takes 'a.b', finds no c inside it, and leaves 'a.b.c' intact.

Throws TypeError('expected an object.') if object is not a plain object. Returns true without doing anything if property is not a string.

Reserved segments (see Reserved keys) at any depth throw ReservedKeyError. A joined candidate always contains a dot, so it can never be a reserved name.

unsetKey
(target: object, key: string): object

Delete a single key from a plain object. The key is literal — no dot-path splitting. Deletes only own properties, so an absent key never touches the prototype chain. Throws TypeError if target is not a plain object or key is not a string, and ReservedKeyError if key is reserved. Returns target.

const obj = { 'a.b': 1, c: 2 };
unsetKey(obj, 'a.b');
// obj becomes { c: 2 }
urlQuery
urlQuery.parse
(string: string): object

Parse a urlQuery serialized by urlQuery.stringify.

urlQuery.parse('a=%7B%22b%22%3A%221%22%7D'); // returns { a: { b: '1' } }

Entries are written with setKey, so URL keys matching a reserved key are silently skipped and parsing continues (?__proto__=1&a=2 parses to { a: 2 }). Values that do not deserialize are kept as the raw string.

urlQuery.stringify
(object: object): string

Serialize a urlQuery object to use as URL query parameters. Nested objects are serialized using serializer.serializeToString.

urlQuery.stringify({ a: { b: '1' } }); // returns 'a=%7B%22b%22%3A%221%22%7D'

Other exports

Export Signature Purpose
builtinMessages object Default key -> message map for translate.
cachedPromises ({ getter, cache }) => (key) => Promise Wraps an async getter so in-flight and resolved promises are served from a cache.
extractErrorProps (error) => object Serializable error props, following cause chains, with cycle and depth limits.
getLocaleDateFormat (locale, style?) => string | null Locale date/datetime/time/month pattern ('YYYY-MM-DD' style tokens).
getLocaleDecimalSeparator (locale) => string | null Locale decimal separator.
getLocaleGroupSeparator (locale) => string | null Locale thousands separator.
getOperatorType (value) => string | null Normalized operator name for an operator object, else null.
LRUCache new LRUCache({ maxSize }) Least-recently-used cache with get/set.
translate ({ key, values, locale, i18n }) => string Resolve and format an i18n message via intl-messageformat.
wait (ms) => Promise Promise resolving after ms.

More Lowdefy resources

Licence

Apache-2.0

); // returns 'array.1.subArr.2'
get
__CODE_BLOCK_10__

Get a value from a target object, using path with dot-notation. Returns __INLINE_CODE_58__ or the optional default value if the value is not found. If __INLINE_CODE_59__ is not a plain object it is taken as the default value, so __INLINE_CODE_60__ works. With __INLINE_CODE_61__ the result is deep-copied with __INLINE_CODE_62__.

__CODE_BLOCK_11__

Paths are strings (numbers are coerced to strings). Array paths are not supported and return the default.

At each level of the walk the strict segment wins if it is present on the target. If it is absent, the segment is joined with successive following segments and the shortest joined key present on the target is used, so a literal dotted key is reachable at any depth without escaping. There is no backtracking: once a joined key matches, a later miss returns the default rather than retrying a longer join. Shortest-first applies at the root too — there is no whole-path shortcut ahead of the walk, which is what makes __INLINE_CODE_63__, __INLINE_CODE_64__ and __INLINE_CODE_65__ resolve the same key for a given path. That is not a round trip: __INLINE_CODE_66__ materialises an absent path with the default rather than leaving the target unchanged.

__CODE_BLOCK_12__

Traversable values are plain objects, arrays and errors — and, because __INLINE_CODE_67__ cannot distinguish them from plain objects, class instances whose __INLINE_CODE_68__ tag is __INLINE_CODE_69__, so __INLINE_CODE_70__ reads the instance's own property. Functions, __INLINE_CODE_71__, __INLINE_CODE_72__, __INLINE_CODE_73__, __INLINE_CODE_74__, __INLINE_CODE_75__, __INLINE_CODE_76__, __INLINE_CODE_77__, typed arrays, __INLINE_CODE_78__, __INLINE_CODE_79__ and primitives are not traversable, and yield the default when the path continues past them.

Traversability only governs stepping through a value on the way to a child. It says nothing about a value that is the endpoint of the path: __INLINE_CODE_80__ returns the live socket, and with __INLINE_CODE_81__ serializes its internals, without the predicate being consulted at all.

A lookup on an error reads the error's __INLINE_CODE_82__ form, so __INLINE_CODE_83__ resolves and an own key holding a class instance arrives as a __INLINE_CODE_84__ marker rather than a live object. An error that is the endpoint of the path is returned as-is — no lookup happens on it, so no conversion happens either.

__CODE_BLOCK_13__

Routing error lookups through __INLINE_CODE_85__ also inherits its depth limits, so error-borne data is not read at full fidelity. The __INLINE_CODE_86__ chain resolves three levels deep (__INLINE_CODE_87__); the fourth cause is the string __INLINE_CODE_88__, and a lookup on it returns the default. Lowdefy's own wrap (__INLINE_CODE_89__ → __INLINE_CODE_90__ → __INLINE_CODE_91__ → driver error) is only three __INLINE_CODE_92__ links deep, so it resolves in full - __INLINE_CODE_93__ reads the driver error's message unchanged. Truncation needs a fifth link, such as a driver error that itself wraps a lower-level cause like a socket error. Objects held on an error are truncated at five levels (__INLINE_CODE_94__). A miss returns a recognisable default, but reading the subtree itself returns a tree with __INLINE_CODE_95__ baked in as a string literal — which will render if it reaches app config through something like __INLINE_CODE_96__.

__CODE_BLOCK_14__

A third fidelity loss is not about depth: __INLINE_CODE_97__ enumerates an error's own keys with __INLINE_CODE_98__, so a non-enumerable own property or an accessor never reaches the extracted form, and __INLINE_CODE_99__ returns the default for it rather than the value. __INLINE_CODE_100__'s own __INLINE_CODE_101__ array is non-enumerable, so it is lost this way; a getter defined on an __INLINE_CODE_102__ subclass is lost the same way. This is scoped narrowly - Node's own __INLINE_CODE_103__/__INLINE_CODE_104__-style errors are unaffected, since __INLINE_CODE_105__, __INLINE_CODE_106__, __INLINE_CODE_107__ and __INLINE_CODE_108__ are ordinary enumerable own properties, and no Lowdefy error class defines a property with __INLINE_CODE_109__ - so the loss reaches third-party and built-in errors, not Lowdefy's own. __INLINE_CODE_110__ is the one an app author is realistically likely to meet.

__CODE_BLOCK_15__

Lookups are own properties only, so a built-in prototype member is never returned as a value: __INLINE_CODE_111__ yields the default, and __INLINE_CODE_112__ returns __INLINE_CODE_113__ because __INLINE_CODE_114__ is own on arrays.

Reserved segments (see Reserved keys) at any depth throw __INLINE_CODE_115__, even when a default is given — a reserved segment is illegal input, not a missing path. Wrap in try/catch to fall back to the default. The scan runs on the split segments before the walk, so a literal dotted key containing a reserved name throws rather than resolving: __INLINE_CODE_116__ throws, matching __INLINE_CODE_117__ and __INLINE_CODE_118__.

getKey
__CODE_BLOCK_16__

Read a single key off a plain object. The key is literal — no dot-path splitting. Reads with __INLINE_CODE_119__, so inherited members never leak as values. Returns __INLINE_CODE_120__ when the key is absent. Throws __INLINE_CODE_121__ if __INLINE_CODE_122__ is not a plain object or __INLINE_CODE_123__ is not a string, and __INLINE_CODE_124__ if __INLINE_CODE_125__ is reserved.

__CODE_BLOCK_17__
isReserved
__CODE_BLOCK_18__

True if __INLINE_CODE_126__ is one of the reserved keys. Use this to guard a call site that should skip, warn or otherwise degrade on a reserved key instead of catching __INLINE_CODE_127__ — see Reserved keys and Keyed maps.

__CODE_BLOCK_19__
joinPath
__CODE_BLOCK_20__

Join segments into a dot-path, re-escaping literal backslashes (__INLINE_CODE_128__) and literal dots (__INLINE_CODE_129__) inside a segment. Inverse of __INLINE_CODE_130__. Throws __INLINE_CODE_131__ if __INLINE_CODE_132__ is not an array. Non-string segments are coerced with __INLINE_CODE_133__.

__CODE_BLOCK_21__
mergeObjects
__CODE_BLOCK_22__

Deep-merge an array of plain objects, left to right. Non-plain-object entries in the array are ignored; a non-array argument is returned as-is.

__CODE_BLOCK_23__
  • Non-mutating. Inputs are never modified; a fresh object is returned.

  • Arrays are atomic leaves. A later array replaces an earlier one rather than index-merging into it. Same for __INLINE_CODE_134__, __INLINE_CODE_135__, __INLINE_CODE_136__ and any other non-plain value.

  • Reserved keys are skipped, not thrown on — see Reserved keys.

  • Plain objects and arrays are always copied. No plain object or array in the result is reference-identical to one in the inputs, at any depth reachable through plain objects and arrays. Every other value — __INLINE_CODE_137__, __INLINE_CODE_138__, __INLINE_CODE_139__, a function, and any other value whose type tag marks it as non-plain — is an opaque leaf and is shared by reference, along with whatever it holds: a plain object hanging off a shared __INLINE_CODE_140__ or function is shared too. A plain user class is not exempt from the split: it has no such tag, so it is copied and flattened into a bare object, losing its prototype and methods.

    The copy carries own enumerable string keys only. Non-enumerable keys and symbol keys are dropped, a getter is flattened to its value at merge time, and a null-prototype object comes back with __INLINE_CODE_141__ — so an object deliberately created with __INLINE_CODE_142__ loses that hardening on the way through a merge.

    __CODE_BLOCK_24__
  • A later __INLINE_CODE_143__ replaces an earlier value rather than being skipped. A caller that wants "no override" must omit the key rather than set it to __INLINE_CODE_144__.

    __CODE_BLOCK_25__

    Every in-repo call site is defaults-first, overrides-last (e.g. __INLINE_CODE_145__, __INLINE_CODE_146__). A call site that inverts that order and passes a possibly-__INLINE_CODE_147__ value in the override position silently erases the value it meant to keep.

  • No identity pass-through. __INLINE_CODE_148__ returns a copy, never __INLINE_CODE_149__ itself. If a caller needs a stable reference across renders, memoise at the call site.

omit
__CODE_BLOCK_26__

Remove an array of keys from a object. Uses __INLINE_CODE_150__ from this package, and inherits its behaviour — dot-paths, and __INLINE_CODE_151__ on a reserved segment.

__CODE_BLOCK_27__
ReservedKeyError
__CODE_BLOCK_28__

Thrown by the path and key helpers when a segment or key is one of the reserved keys. Extends __INLINE_CODE_152__ with __INLINE_CODE_153__ and a __INLINE_CODE_154__ property carrying the offending key. The message is always __INLINE_CODE_155__ — the fixed format is part of the contract, so the segment is surfaced in logs and serialized error payloads.

__CODE_BLOCK_29__
serializer
serializer.copy
serializer.deserialize
serializer.deserializeFromString
serializer.serialize
serializer.serializeToString
set
__CODE_BLOCK_30__

Sets a value in a object at a key given by path, and returns __INLINE_CODE_156__. Intermediate objects are created as needed (autovivification); the next segment decides whether a missing intermediate becomes an array or an object. Returns __INLINE_CODE_157__ unchanged if __INLINE_CODE_158__ is not a plain object or __INLINE_CODE_159__ is not a string.

__CODE_BLOCK_31__

At each level of the walk the strict segment wins if it is present on the target. If it is absent, the segment is joined with successive following segments and the shortest joined key present on the target is used, so a write reaches a literal dotted key instead of creating a nested twin beside it. If nothing matches, the intermediate is created as usual.

__CODE_BLOCK_32__

Paths are strings only; array paths are not supported. The leaf value always replaces what is there; to merge instead, write __INLINE_CODE_160__.

Reserved segments (see Reserved keys) at any depth throw __INLINE_CODE_161__ before anything is written, so a rejected path leaves the target untouched.

setKey
__CODE_BLOCK_33__

Set a single key on a plain object. The key is literal — no dot-path splitting, no autovivification. Throws __INLINE_CODE_162__ if __INLINE_CODE_163__ is not a plain object or __INLINE_CODE_164__ is not a string, and __INLINE_CODE_165__ if __INLINE_CODE_166__ is reserved (including on __INLINE_CODE_167__ targets, where the write would be safe — consistency beats per-target precision). Returns __INLINE_CODE_168__.

__CODE_BLOCK_34__

Use __INLINE_CODE_169__ rather than __INLINE_CODE_170__ wherever the key is derived from user input — see Keyed maps.

splitPath
__CODE_BLOCK_35__

Split a dot-path into segments. __INLINE_CODE_171__ is a literal dot and __INLINE_CODE_172__ a literal backslash; any other backslash is an ordinary character, so a key such as __INLINE_CODE_173__ needs no escaping. Throws __INLINE_CODE_174__ if __INLINE_CODE_175__ is not a string.

__CODE_BLOCK_36__
stableStringify
__CODE_BLOCK_37__

Derived from https://github.com/substack/json-stable-stringify

Returns a deterministic JSON stringified object.

swap
__CODE_BLOCK_38__

Swaps the object at the from index with the object at the to index.

__CODE_BLOCK_39__
type

A collection of type predicates used across the monorepo. Prefer these over native checks so behaviour stays consistent.

__CODE_BLOCK_40__

Returns the Lowdefy type name: __INLINE_CODE_176__, __INLINE_CODE_177__, __INLINE_CODE_178__, __INLINE_CODE_179__, __INLINE_CODE_180__, __INLINE_CODE_181__, __INLINE_CODE_182__, __INLINE_CODE_183__, __INLINE_CODE_184__, __INLINE_CODE_185__, __INLINE_CODE_186__, __INLINE_CODE_187__, __INLINE_CODE_188__, __INLINE_CODE_189__, __INLINE_CODE_190__, __INLINE_CODE_191__, __INLINE_CODE_192__, a typed-array/__INLINE_CODE_193__ name, __INLINE_CODE_194__ for plain objects, or the lowercased constructor name for anything else (__INLINE_CODE_195__ is __INLINE_CODE_196__).

Predicate True for
__INLINE_CODE_197__ __INLINE_CODE_198__
__INLINE_CODE_199__ plain objects only — __INLINE_CODE_200__ yes, __INLINE_CODE_201__ no
__INLINE_CODE_202__ __INLINE_CODE_203__
__INLINE_CODE_204__ __INLINE_CODE_205__
__INLINE_CODE_206__ any callable, generator functions included
__INLINE_CODE_207__ __INLINE_CODE_208__
__INLINE_CODE_209__ __INLINE_CODE_210__ and finite — __INLINE_CODE_211__ and __INLINE_CODE_212__ are false
__INLINE_CODE_213__ __INLINE_CODE_214__ is not __INLINE_CODE_215__ — note __INLINE_CODE_216__, __INLINE_CODE_217__ and __INLINE_CODE_218__ coerce to __INLINE_CODE_219__, so true
__INLINE_CODE_220__ __INLINE_CODE_221__
__INLINE_CODE_222__ __INLINE_CODE_223__ with a valid time — __INLINE_CODE_224__ is false
__INLINE_CODE_225__ __INLINE_CODE_226__
__INLINE_CODE_227__ a JS __INLINE_CODE_228__ instance (not "is defined")
__INLINE_CODE_229__ __INLINE_CODE_230__
__INLINE_CODE_231__ __INLINE_CODE_232__
__INLINE_CODE_233__ __INLINE_CODE_234__ or __INLINE_CODE_235__ — the check to reach for
__INLINE_CODE_236__ __INLINE_CODE_237__, __INLINE_CODE_238__, string, number, boolean and date
__INLINE_CODE_239__ a plain object with no own keys
__INLINE_CODE_240__ an ISO-8601 date-time string
__INLINE_CODE_241__ a valid Lowdefy id — __INLINE_CODE_242__, no leading/trailing __INLINE_CODE_243__, no numeric-leading segment, not __INLINE_CODE_244__-prefixed
__INLINE_CODE_245__ a plain object with a __INLINE_CODE_246__ key holding a valid name

__INLINE_CODE_247__ returns the value when it matches __INLINE_CODE_248__, and a safe fallback rather than throwing when it does not: __INLINE_CODE_249__ for __INLINE_CODE_250__ (empty strings included), __INLINE_CODE_251__, __INLINE_CODE_252__, __INLINE_CODE_253__ and __INLINE_CODE_254__, __INLINE_CODE_255__ for __INLINE_CODE_256__, __INLINE_CODE_257__ for __INLINE_CODE_258__, and for __INLINE_CODE_259__ the value unless it is __INLINE_CODE_260__. An unknown __INLINE_CODE_261__ returns __INLINE_CODE_262__.

__INLINE_CODE_263__ treating __INLINE_CODE_264__ as primitive is a deliberate Lowdefy convention, not JS semantics. The __INLINE_CODE_265__ operator is app-developer-facing — do not "fix" this without coordinating with the operators-js, blocks-antd selector, and nunjucks consumers.

Type identification uses __INLINE_CODE_266__ only; there is no cross-realm (vm/iframe/worker) duck-typing.

unset
__CODE_BLOCK_41__

Unset a property on a object. Supports dot-notation. Returns __INLINE_CODE_267__, including when the path does not exist or an intermediate is missing or primitive (a no-op).

__CODE_BLOCK_42__

At each level of the walk the strict segment wins if it is present. If it is absent, the segment is joined with successive following segments and the shortest joined key present on the target is used, so a literal dotted key is reachable at any depth without escaping. A present strict segment always wins, even when it holds a value the walk cannot descend into: __INLINE_CODE_268__ is a no-op.

__CODE_BLOCK_43__

Joined candidates are matched shortest-first and there is no backtracking: once a joined key matches, a later miss is a no-op rather than a retry with a longer join. Given __INLINE_CODE_269__, __INLINE_CODE_270__ takes __INLINE_CODE_271__, finds no __INLINE_CODE_272__ inside it, and leaves __INLINE_CODE_273__ intact.

Throws __INLINE_CODE_274__ if __INLINE_CODE_275__ is not a plain object. Returns __INLINE_CODE_276__ without doing anything if __INLINE_CODE_277__ is not a string.

Reserved segments (see Reserved keys) at any depth throw __INLINE_CODE_278__. A joined candidate always contains a dot, so it can never be a reserved name.

unsetKey
__CODE_BLOCK_44__

Delete a single key from a plain object. The key is literal — no dot-path splitting. Deletes only own properties, so an absent key never touches the prototype chain. Throws __INLINE_CODE_279__ if __INLINE_CODE_280__ is not a plain object or __INLINE_CODE_281__ is not a string, and __INLINE_CODE_282__ if __INLINE_CODE_283__ is reserved. Returns __INLINE_CODE_284__.

__CODE_BLOCK_45__
urlQuery
urlQuery.parse
__CODE_BLOCK_46__

Parse a urlQuery serialized by urlQuery.stringify.

__CODE_BLOCK_47__

Entries are written with __INLINE_CODE_285__, so URL keys matching a reserved key are silently skipped and parsing continues (__INLINE_CODE_286__ parses to __INLINE_CODE_287__). Values that do not deserialize are kept as the raw string.

urlQuery.stringify
__CODE_BLOCK_48__

Serialize a urlQuery object to use as URL query parameters. Nested objects are serialized using __INLINE_CODE_288__.

__CODE_BLOCK_49__

Other exports

Export Signature Purpose
__INLINE_CODE_289__ __INLINE_CODE_290__ Default __INLINE_CODE_291__ map for __INLINE_CODE_292__.
__INLINE_CODE_293__ __INLINE_CODE_294__ Wraps an async getter so in-flight and resolved promises are served from a cache.
__INLINE_CODE_295__ __INLINE_CODE_296__ Serializable error props, following __INLINE_CODE_297__ chains, with cycle and depth limits.
__INLINE_CODE_298__ __INLINE_CODE_299__ Locale date/datetime/time/month pattern (__INLINE_CODE_300__ style tokens).
__INLINE_CODE_301__ __INLINE_CODE_302__ Locale decimal separator.
__INLINE_CODE_303__ __INLINE_CODE_304__ Locale thousands separator.
__INLINE_CODE_305__ __INLINE_CODE_306__ Normalized operator name for an operator object, else __INLINE_CODE_307__.
__INLINE_CODE_308__ __INLINE_CODE_309__ Least-recently-used cache with __INLINE_CODE_310__/__INLINE_CODE_311__.
__INLINE_CODE_312__ __INLINE_CODE_313__ Resolve and format an i18n message via __INLINE_CODE_314__.
__INLINE_CODE_315__ __INLINE_CODE_316__ Promise resolving after __INLINE_CODE_317__.

More Lowdefy resources

Licence

Apache-2.0

Keywords