@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)throwsTypeError— coerce numeric ids yourself. - Targets must be plain objects. Arrays and class instances throw
TypeError;Object.create(null)is accepted (type.isObjectreportstruefor it). getKeyreturns its default only when the key is genuinely absent. A key holdingundefinedreturnsundefined, not the default.- Native
Mapneeds none of this — it stores keys in an internal slot and is pollution-safe by construction. UseMapdirectly 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 (ActionError → RequestError → ServiceError → 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
- Getting started with Lowdefy - https://docs.lowdefy.com/tutorial-start
- Lowdefy docs - https://docs.lowdefy.com
- Lowdefy website - https://lowdefy.com
- Community forum - https://github.com/lowdefy/lowdefy/discussions
- Bug reports and feature requests - https://github.com/lowdefy/lowdefy/issues
Licence
); // 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
- Getting started with Lowdefy - https://docs.lowdefy.com/tutorial-start
- Lowdefy docs - https://docs.lowdefy.com
- Lowdefy website - https://lowdefy.com
- Community forum - https://github.com/lowdefy/lowdefy/discussions
- Bug reports and feature requests - https://github.com/lowdefy/lowdefy/issues