Ok Bob, give them the analogy.
Bob:
Javascript is... a dense forest. And you... want to pitch your tent somewhere. You just need a friendly plot of flat earth, to use as a home base, so you can move on to follow your dreams. You search through the dense forest, looking for... a clearing.
Thanks Bob that's plenty.
The clearing module gives you all the features any sane person would want to see in javascript.
Clearing uses an unorthodox (controversial??) technique to achieve a lot of convenience. Functionality is added by extending native classes and prototypes with non-enumerable, Symbol properties comparable to the following:
const coolNewHelper = Symbol();
Object.defineProperty(Object.prototype, coolNewHelper, { enumerable: false, value: function() {
return Object.keys(this).map(key => key.toUpperCase());
}});
const obj = { a: 1, b: 2, c: 3 };
console.log(obj[coolNewHelper]());Extending native classes and prototypes is kind of crazy, but only doing so with non-enumerable,
Symbol properties maintains compatibility with the vast majority of npm modules. All symbols are
accessible via the clearing (or cl) global object.
Simply import the clearing module to make all functionality globally available:
import '@gershy/clearing';Type guard for strict class/constructor matching. Handles primitives, null, undefined, and NaN correctly.
const { isCls } = clearing;
isCls(5, Number); // true
isCls(5, NaN); // false
isCls(5, String); // false
isCls('abc', String); // true
isCls(null, null); // true
isCls(undefined, undefined); // true
isCls(null, undefined); // false
isCls(undefined, null); // false
isCls(NaN, NaN); // true
isCls({}, Object); // true
isCls([], Array); // true
isCls(new Map(), Map); // trueType guard for instanceof checks (including inheritance).
const { inCls } = clearing;
inCls([], Array); // true
inCls(new (class X extends Array {})(), Array); // true
inCls({}, Object); // true
inCls(5, Number); // false (primitives are not instanceof)Returns the class/constructor name of a value, or special names for null/undefined/NaN.
const { getClsName } = clearing;
getClsName(5); // 'Number'
getClsName('abc'); // 'String'
getClsName(null); // 'Null'
getClsName(undefined); // 'Undef'
getClsName(NaN); // 'Nan'
getClsName({}); // 'Object'
getClsName(Object.create(null)); // 'Prototypeless'Returns the constructor function of a value, or null if not available.
const { getCls } = clearing;
getCls(5); // Number
getCls('abc'); // String
getCls(null); // null
getCls(undefined); // null
getCls({}); // Object
getCls(Object.create(null)); // nullSpecial value (undefined) used to signal omission in mapping helpers.
const { skip } = clearing;
[1, 2, 3].map(v => v > 1 ? v : skip); // [skip, 2, 3]Unified handler for both Promise and non-Promise values. Applies onFulfilled/onRejected as appropriate.
const { then } = clearing;
then(Promise.resolve(5), v => v * 2); // Promise resolving to 10
then(5, v => v * 2); // 10
then(Promise.reject('fail'), undefined, e => 'fallback'); // Promise resolving to 'fallback'Runs a function (sync or async), catching errors and returning a Promise. Optionally handles errors with onRejected.
const { safe } = clearing;
await safe(() => JSON.parse('{ bad }'), e => 'fallback'); // 'fallback'
await safe(async () => await fetch('bad-url'), e => null); // null if fetch throwsAlong with utility functions, @gershy/clearing adds functionality to native prototypes, keyed by symbols, and exposes those symbols via the clearing (or cl) globals. These symbols are unique symbols, in typescript terms. Note that unique symbol is widened only to symbol on assignment-to-property-reference:
clearing.map; // Typescript knows this is `unique symbol`
const symbol = clearing.map;
symbol; // Typescript thinks this is a regular `symbol`! Ouch.In order to gain the sophisticated typing provided by clearing, you must preserve the unique symbol typing of all @gershy/clearing symbols. My greatest apology to @gershy/clearing users it the lack of a succinct way to reference a bunch of symbols at once, while retaining strong typing. For example, the most intuitive way to reference a batch of symbols - via destructuring - is the easiest way to lose your typing:
const { map, toArr, toObj } = clearing; // Typescript thinks these are regular `symbol`s
{ a: 1, b: 2 }[map](v => v * 3); // Typescript thinks this is `any` - very sad!To preserve your typing you can:
- Reference directly from the clearing global:
{ a: 1, b: 2 }[cl.map](v => v * 3); // Typescript gets `{ a: number, b: number }`!- Use explicit typing:
const map: typeof clearing.map = clearing.map; // `map` is now a `unique symbol`!
{ a: 1, b: 2 }[map](v => v * 3); // Typescript gets `{ a: number, b: number }`!- Riot against the typescript maintainers for making things work this way (just kidding just kidding, they have their reasonssssss. I mean, kinda. Don't riot. They're smart people.)
There are currently no extensions on the Object class.
References a potentially nested own property.
const obj = { a: { b: { c: 'z' } } };
console.log(obj[cl.at]([ 'a', 'b', 'c' ])); // "z"A default value can be supplied if nested lookup finds nothing.
const obj = { a: { b: { c: 'z' } } };
console.log(obj[cl.at]([ 'missing' ], 'default')); // "default"
console.log(obj[cl.at]([ 'a', 'lol', 'c' ], 'default')); // "default"
console.log(obj[cl.at]([ 'a', 'b', 'haha' ], 'default')); // "default"Returns the number of own properties in an object.
const { count } = clearing;
console.log({ }[cl.count]()); // 0
console.log({ a: 1, b: 2 }[cl.count]()); // 2
console.log({ a: 1, b: 2, c: 3 }[cl.count]()); // 3Returns whether an object has any properties.
console.log({ }[cl.empty]()); // true
console.log({ a: 1 }[cl.empty]()); // falseSplits an object into sub-object groups based on a function returning group names.
const obj = { a: 1, b: 10, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 2 };
console.log(obj[cl.group](n => {
if (n < 4) return 'small';
if (n < 8) return 'medium';
return 'big';
});
/*
{
small: { a: 1, c: 3, j: 2 },
medium: { d: 4, e: 5, f: 6, g: 7 },
big: { b: 10, h: 8, i: 9 }
}
*/Determines own property existence.
const obj = { a: 1, b: 2 };
console.log(obj[cl.has]('a')); // true
console.log(obj[cl.has]('b')); // true
console.log(obj[cl.has]('z')); // falseMaps over object values, returning a new object. Return skip to omit a property.
const obj = { a: 1, b: 2, c: 3 };
console.log(obj[cl.map](v => v * 2)); // { a: 2, b: 4, c: 6 }
console.log(obj[cl.map](v => v > 1 ? v : cl.skip)); // { b: 2, c: 3 }Maps over object entries returning [key, value] pairs to construct a new object; allows remapping
keys.
const obj = { a: 1, b: 2 };
console.log(obj[cl.mapk]((v, k) => [ k.toUpperCase(), v * 10 ])); // { A: 10, B: 20 }Deep merges another object into this (mutates in place). Use skip for deletion.
const obj = { a: 1, b: { c: 2, d: 3 } };
obj[cl.merge]({ b: { c: 100 }, e: 4 });
console.log(obj); // { a: 1, b: { c: 100, d: 3 }, e: 4 }
// Deleting properties
obj[cl.merge]({ a: skip });
console.log(obj); // { b: { c: 100, d: 3 }, e: 4 }Returns a copy of the object with specified keys removed/omitted.
const obj = { a: 1, b: 2, c: 3, d: 4 };
console.log(obj[cl.slash]([ 'b', 'd' ])); // { a: 1, c: 3 }Returns a copy of the object containing only the specified keys.
const obj = { a: 1, b: 2, c: 3, d: 4 };
console.log(obj[cl.slice]([ 'b', 'd' ])); // { b: 2, d: 4 }Converts an object to an array by mapping over its entries.
const obj = { a: 1, b: 2, c: 3 };
console.log(obj[cl.toArr]((v, k) => `${k}=${v}`)); // [ 'a=1', 'b=2', 'c=3' ]Makes objects iterable, yielding [key, value] pairs.
const obj = { a: 1, b: 2 };
for (const [ k, v ] of obj[cl.walk]())
// Logs twice:
// 1. a 1
// 2. b 2
console.log(k, v);There are currently no extensions on the Array class.
Pushes an item onto the array and returns that item.
const arr = [ 1, 2, 3 ];
const added = arr[cl.add](4);
console.log(added); // 4
console.log(arr); // [ 1, 2, 3, 4 ]Returns the length of the array.
console.log([ 1, 2, 3 ][cl.count]()); // 3
console.log([][cl.count]()); // 0Returns whether the array has no elements.
console.log([][cl.empty]()); // true
console.log([ 1, 2, 3 ][cl.empty]()); // falseFinds an element matching a predicate, returning { found, val, ind }.
const arr = [ 10, 20, 30, 40 ];
const result = arr[cl.find](v => v > 25);
console.log(result); // { found: true, val: 30, ind: 2 }
const missing = arr[cl.find](v => v > 100);
console.log(missing); // { found: false, val: null, ind: null }Splits an array into sub-arrays based on a function returning group names.
const arr = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
console.log(arr[cl.group](n => {
if (n < 4) return 'small';
if (n < 8) return 'medium';
return 'big';
}));
/*
{
small: [ 1, 2, 3 ],
medium: [ 4, 5, 6, 7 ],
big: [ 8, 9, 10 ]
}
*/Checks if an array includes a value.
console.log([ 1, 2, 3 ][cl.has](2)); // true
console.log([ 1, 2, 3 ][cl.has](5)); // falseMaps over array values. Return cl.skip to omit an element; it's filter + map in one.
const arr = [ 1, 2, 3, 4, 5 ];
console.log(arr[cl.map](v => v * 2)); // [ 2, 4, 6, 8, 10 ]
console.log(arr[cl.map](v => v > 2 ? v * 10 : cl.skip)); // [ 30, 40, 50 ]Removes the first occurrence of a value from the array (mutates in place).
const arr = [ 1, 2, 3, 2, 4 ];
arr[cl.rem](2);
console.log(arr); // [ 1, 3, 2, 4 ]Converts an array to an object by mapping each element to a [key, value] pair.
const arr = [ 'a', 'b', 'c' ];
console.log(arr[cl.toObj]((v, i) => [ v, i ])); // { a: 0, b: 1, c: 2 }Allows writing coherent multiline strings with predictable indentation.
const text = (() => {
return (() => {
return (() => {
return String[cl.baseline](`
| Even though this text is in an indented scope, it will have predictable indentation.
|
| This is achieved by using the pipe ("|") character plus a space as a delimiter for
| every line.
|
| You can also pass a 2nd argument to String[baseline] to change the delimiter.
|
| Thanks for your attention :)
`);
})();
})();
})();Character sets for encoding/decoding numbers to strings.
console.log(String[cl.base32]); // '0123456789abcdefghijklmnopqrstuv'
console.log(String[cl.base36]); // '0123456789abcdefghijklmnopqrstuvwxyz'
console.log(String[cl.base62]); // '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
console.log(String[cl.base64Url]); // '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_'
console.log(String[cl.base64Std]); // '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/'Returns the character code at a given index (default 0).
console.log('A'[cl.code]()); // 65
console.log('AB'[cl.code](1)); // 66Returns the length of the string.
console.log('hello'[cl.count]()); // 5
console.log(''[cl.count]()); // 0Splits a string by a delimiter with a maximum number of cuts.
console.log('a:b:c:d:e'[cl.cut](':')); // [ 'a', 'b:c:d:e' ]
console.log('a:b:c:d:e'[cl.cut](':', 2)); // [ 'a', 'b', 'c:d:e' ]
console.log('a:b:c:d:e'[cl.cut](':', Infinity)); // [ 'a', 'b', 'c', 'd', 'e' ]Checks if a string contains a substring.
console.log('hello world'[cl.has]('world')); // true
console.log('hello world'[cl.has]('xyz')); // falseChecks if a string starts with a given prefix.
console.log('hello world'[cl.hasHead]('hello')); // true
console.log('hello world'[cl.hasHead]('world')); // falseChecks if a string ends with a given suffix.
console.log('hello world'[cl.hasTail]('world')); // true
console.log('hello world'[cl.hasTail]('hello')); // falseIndents each line of a string.
const text = 'line1\nline2\nline3';
console.log(text[cl.indent]()); // 2 spaces (default)
console.log(text[cl.indent](4)); // 4 spaces
console.log(text[cl.indent](2, '-')); // '--' prefix
console.log(text[cl.indent]('>>> ')); // custom prefixConverts the string to lowercase.
console.log('HELLO'[cl.lower]()); // 'hello'Converts the string to uppercase.
console.log('hello'[cl.upper]()); // 'HELLO'Pads the start of the string to a given length.
console.log('5'[cl.padHead](3, '0')); // '005'Pads the end of the string to a given length.
console.log('5'[cl.padTail](3, '0')); // '500'Decodes a string to a BigInt using a given charset.
console.log('ff'[cl.toNum](String[cl.charset]('0123456789abcdef'))); // 255n
console.log('10'[cl.toNum](String[cl.base62])); // 62nConstants for 32-bit and 64-bit integer ranges.
console.log(Number[cl.int32]); // 4294967296 (2^32)
console.log(Number[cl.int64]); // 18446744073709551616 (2^64)Converts a character code to its character.
console.log((65)[cl.char]()); // 'A'
console.log((97)[cl.char]()); // 'a'Returns whether a number is an integer.
console.log((5)[cl.isInt]()); // true
console.log((5.1)[cl.isInt]()); // falseCreates an array of length n by mapping over indices.
console.log((5)[cl.toArr](i => i * 2)); // [ 0, 2, 4, 6, 8 ]Creates an object by mapping over indices, returning [key, value] pairs.
console.log((3)[cl.toObj](i => [ `key${i}`, i * 10 ])); // { key0: 0, key1: 10, key2: 20 }Encodes a number to a string using a given charset.
console.log((255)[cl.toStr](String[cl.charset]('0123456789abcdef'))); // 'ff'
console.log((62)[cl.toStr](String[cl.base62])); // '10'
console.log((5)[cl.toStr](String[cl.base62], 4)); // '0005' (padded)Makes numbers iterable from 0 to n-1.
for (const i of 3) console.log(i);
// 0
// 1
// 2
console.log([ ...5 ]); // [ 0, 1, 2, 3, 4 ]Yields the bits of a number (least significant first).
console.log([ ...(13)[cl.bits]() ]); // [ 1, 0, 1, 1 ] (13 is 1101 in binary)Same as Number.prototype[cl.toStr], but for BigInt values.
console.log((1000000000000n)[cl.toStr](String[cl.base62])); // 'bLY38W'Throws an error if the assertion function returns false.
Error[cl.assert]({ x: 5, y: 10 }, ({ x, y }) => x < y); // passes
Error[cl.assert]({ x: 10, y: 5 }, ({ x, y }) => x < y); // throws!Modifies an error's message and adds properties. Returns the error for chaining.
// Add context to errors:
throw Error('oops')[cl.mod]({ code: 'err99', context: { userId: 123 } });
// Overwrite the error message by passing a string, or supplying "message" or "msg" properties:
throw Error('oops')[cl.mod]('new message');
throw Error('oops')[cl.mod]({ msg: 'new message', extra: 'data' });
throw Error('oops')[cl.mod]({ message: 'new message', extra: 'data' });
// Pass a callback to succinctly reference the original message:
throw Error('oops')[cl.mod](msg => `Modified message! Original: ${msg}`);
throw Error('oops')[cl.mod](msg => ({
msg: `Modified message! Original: ${msg}`,
extra: 'data'
}));Shorthand for throw error[cl.mod](props).
Error('something failed')[cl.fire]({ code: 'err99' });Converts an error (and its cause chain) to a plain, json-serializable object.
const err = Error('outer')[cl.mod]({ cause: Error('inner') });
console.log(err[cl.limn]());
/*
{
form: 'Error',
msg: 'outer',
trace: [ ... ],
cause: { form: 'Error', msg: 'inner', trace: [ ... ], cause: null }
}
*/Marks an error (and its causes) as suppressed for custom error handling.
const err = Error('handled gracefully');
err[cl.suppress]();
console.log(err[Symbol.for('clearing.err.suppressed')]); // trueUseful for preventing certain errors from crashing the js process, for example in node.js:
process.on('unhandledException', err => {
// Ignore suppressed errors
if (err[Symbol.for('clearing.err.suppressed')]) return;
console.log('Fatal error!');
process.exit(1);
});Alias for Promise.all.
const results = await Promise[cl.allArr]([ fetch('/a'), fetch('/b'), fetch('/c') ]);Like Promise.all, but for an object of promises, returning an object of results.
const results = await Promise[cl.allObj]({
user: fetchUser(),
posts: fetchPosts(),
comments: fetchComments()
});
console.log(results.user, results.posts, results.comments);Creates a promise with externally accessible resolve and reject functions.
const p = Promise[cl.later]();
setTimeout(() => p.resolve('done!'), 1000);
console.log(await p); // 'done!'Converts a promise-resolving-to-iterable to an array.
const prm = Promise.resolve([ 1, 3, 5 ]);
const vals = await prm[cl.toArr](v => 'a'.repeat(v));
console.log(vals); // [ 'a', 'aaa', 'aaaaa' ]
const prm2 = Promise.resolve('abc');
const vals2 = await prm2[cl.toArr](v => v.repeat(2));
console.log(vals2); // [ 'aa', 'bb', 'cc' ]Returns the size of the set.
console.log(new Set([ 1, 2, 3 ])[cl.count]()); // 3Returns whether the set is empty.
console.log(new Set()[cl.empty]()); // true
console.log(new Set([ 1 ])[cl.empty]()); // falseFinds an element matching a predicate, returning { found, val }.
const s = new Set([ 10, 20, 30 ]);
console.log(s[cl.find](v => v > 15)); // { found: true, val: 20 }Maps over set values to produce an array.
const s = new Set([ 1, 2, 3 ]);
console.log(s[cl.map](v => v * 2)); // [ 2, 4, 6 ]Removes a value from the set.
const s = new Set([ 1, 2, 3 ]);
s[cl.rem](2);
console.log([ ...s ]); // [ 1, 3 ]Converts a set to an array by mapping over its values.
const s = new Set([ 1, 2, 3 ]);
console.log(s[cl.toArr](v => v * 10)); // [ 10, 20, 30 ]Converts a set to an object by mapping each value to a [key, value] pair.
const s = new Set([ 'a', 'b', 'c' ]);
console.log(s[cl.toObj](v => [ v, v.toUpperCase() ])); // { a: 'A', b: 'B', c: 'C' }Alias for Map.prototype.set (for consistency with Set).
const m = new Map();
m[cl.add]('key', 'value');Returns the size of the map.
const m = new Map([ [ 'a', 1 ], [ 'b', 2 ] ]);
console.log(m[cl.count]()); // 2Returns whether the map is empty.
console.log(new Map()[cl.empty]()); // true
console.log(new Map([ [ 'a', 1 ] ])[cl.empty]()); // falseFinds an entry matching a predicate, returning { found, val, key }.
const m = new Map([ [ 'a', 10 ], [ 'b', 20 ], [ 'c', 30 ] ]);
console.log(m[cl.find](v => v > 15)); // { found: true, val: 20, key: 'b' }Maps entries to produce an object; iterator receives (val, key) and returns [key, val].
const m = new Map([ [ 'a', 1 ], [ 'b', 2 ] ]);
console.log(m[cl.map]((v, k) => [ k.toUpperCase(), v * 10 ])); // { A: 10, B: 20 }Removes an entry from the map by key.
const m = new Map([ [ 'a', 1 ], [ 'b', 2 ] ]);
m[cl.rem]('a');
console.log(m[cl.count]()); // 1Converts a map to an array by mapping over its entries.
const m = new Map([ [ 'a', 1 ], [ 'b', 2 ] ]);
console.log(m[cl.toArr]((v, k) => `${k}=${v}`)); // [ 'a=1', 'b=2' ]Converts a map to an object by mapping over its entries.
const m = new Map([ [ 'a', 1 ], [ 'b', 2 ] ]);
console.log(m[cl.toObj]((v, k) => [ k, v * 100 ])); // { a: 100, b: 200 }Converts a generator into an array, synchronously.
const generatorFn = function*() { yield 1; yield 2; yield 10; }
const vals = generatorFn()[cl.toArr](v => 'a'.repeat(v));
console.log(vals); // [ 'a', 'aa', 'aaaaaaaaaa' ]Converts an generator into an array promise.
const generatorFn = async function*() {
await new Promise(r => setTimeout(r, 1000));
yield 1;
await new Promise(r => setTimeout(r, 1000));
yield 2;
await new Promise(r => setTimeout(r, 1000));
yield 10;
await new Promise(r => setTimeout(r, 1000));
};
const vals = await generatorFn()[cl.toArr](v => 'a'.repeat(v)); // This will take 4 seconds
console.log(vals); // [ 'a', 'aa', 'aaaaaaaaaa' ]