You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					288 lines
				
				10 KiB
			
		
		
			
		
	
	
					288 lines
				
				10 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								# jsprim: utilities for primitive JavaScript types
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This module provides miscellaneous facilities for working with strings,
							 | 
						||
| 
								 | 
							
								numbers, dates, and objects and arrays of these basic types.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### deepCopy(obj)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Creates a deep copy of a primitive type, object, or array of primitive types.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### deepEqual(obj1, obj2)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Returns whether two objects are equal.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### isEmpty(obj)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Returns true if the given object has no properties and false otherwise.  This
							 | 
						||
| 
								 | 
							
								is O(1) (unlike `Object.keys(obj).length === 0`, which is O(N)).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### hasKey(obj, key)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Returns true if the given object has an enumerable, non-inherited property
							 | 
						||
| 
								 | 
							
								called `key`.  [For information on enumerability and ownership of properties, see
							 | 
						||
| 
								 | 
							
								the MDN
							 | 
						||
| 
								 | 
							
								documentation.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### forEachKey(obj, callback)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Like Array.forEach, but iterates enumerable, owned properties of an object
							 | 
						||
| 
								 | 
							
								rather than elements of an array.  Equivalent to:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for (var key in obj) {
							 | 
						||
| 
								 | 
							
								            if (Object.prototype.hasOwnProperty.call(obj, key)) {
							 | 
						||
| 
								 | 
							
								                    callback(key, obj[key]);
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### flattenObject(obj, depth)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Flattens an object up to a given level of nesting, returning an array of arrays
							 | 
						||
| 
								 | 
							
								of length "depth + 1", where the first "depth" elements correspond to flattened
							 | 
						||
| 
								 | 
							
								columns and the last element contains the remaining object .  For example:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    flattenObject({
							 | 
						||
| 
								 | 
							
								        'I': {
							 | 
						||
| 
								 | 
							
								            'A': {
							 | 
						||
| 
								 | 
							
								                'i': {
							 | 
						||
| 
								 | 
							
								                    'datum1': [ 1, 2 ],
							 | 
						||
| 
								 | 
							
								                    'datum2': [ 3, 4 ]
							 | 
						||
| 
								 | 
							
								                },
							 | 
						||
| 
								 | 
							
								                'ii': {
							 | 
						||
| 
								 | 
							
								                    'datum1': [ 3, 4 ]
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								            },
							 | 
						||
| 
								 | 
							
								            'B': {
							 | 
						||
| 
								 | 
							
								                'i': {
							 | 
						||
| 
								 | 
							
								                    'datum1': [ 5, 6 ]
							 | 
						||
| 
								 | 
							
								                },
							 | 
						||
| 
								 | 
							
								                'ii': {
							 | 
						||
| 
								 | 
							
								                    'datum1': [ 7, 8 ],
							 | 
						||
| 
								 | 
							
								                    'datum2': [ 3, 4 ],
							 | 
						||
| 
								 | 
							
								                },
							 | 
						||
| 
								 | 
							
								                'iii': {
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								        'II': {
							 | 
						||
| 
								 | 
							
								            'A': {
							 | 
						||
| 
								 | 
							
								                'i': {
							 | 
						||
| 
								 | 
							
								                    'datum1': [ 1, 2 ],
							 | 
						||
| 
								 | 
							
								                    'datum2': [ 3, 4 ]
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }, 3)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								becomes:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    [
							 | 
						||
| 
								 | 
							
								        [ 'I',  'A', 'i',   { 'datum1': [ 1, 2 ], 'datum2': [ 3, 4 ] } ],
							 | 
						||
| 
								 | 
							
								        [ 'I',  'A', 'ii',  { 'datum1': [ 3, 4 ] } ],
							 | 
						||
| 
								 | 
							
								        [ 'I',  'B', 'i',   { 'datum1': [ 5, 6 ] } ],
							 | 
						||
| 
								 | 
							
								        [ 'I',  'B', 'ii',  { 'datum1': [ 7, 8 ], 'datum2': [ 3, 4 ] } ],
							 | 
						||
| 
								 | 
							
								        [ 'I',  'B', 'iii', {} ],
							 | 
						||
| 
								 | 
							
								        [ 'II', 'A', 'i',   { 'datum1': [ 1, 2 ], 'datum2': [ 3, 4 ] } ]
							 | 
						||
| 
								 | 
							
								    ]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This function is strict: "depth" must be a non-negative integer and "obj" must
							 | 
						||
| 
								 | 
							
								be a non-null object with at least "depth" levels of nesting under all keys.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### flattenIter(obj, depth, func)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This is similar to `flattenObject` except that instead of returning an array,
							 | 
						||
| 
								 | 
							
								this function invokes `func(entry)` for each `entry` in the array that
							 | 
						||
| 
								 | 
							
								`flattenObject` would return.  `flattenIter(obj, depth, func)` is logically
							 | 
						||
| 
								 | 
							
								equivalent to `flattenObject(obj, depth).forEach(func)`.  Importantly, this
							 | 
						||
| 
								 | 
							
								version never constructs the full array.  Its memory usage is O(depth) rather
							 | 
						||
| 
								 | 
							
								than O(n) (where `n` is the number of flattened elements).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								There's another difference between `flattenObject` and `flattenIter` that's
							 | 
						||
| 
								 | 
							
								related to the special case where `depth === 0`.  In this case, `flattenObject`
							 | 
						||
| 
								 | 
							
								omits the array wrapping `obj` (which is regrettable).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### pluck(obj, key)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Fetch nested property "key" from object "obj", traversing objects as needed.
							 | 
						||
| 
								 | 
							
								For example, `pluck(obj, "foo.bar.baz")` is roughly equivalent to
							 | 
						||
| 
								 | 
							
								`obj.foo.bar.baz`, except that:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								1. If traversal fails, the resulting value is undefined, and no error is
							 | 
						||
| 
								 | 
							
								   thrown.  For example, `pluck({}, "foo.bar")` is just undefined.
							 | 
						||
| 
								 | 
							
								2. If "obj" has property "key" directly (without traversing), the
							 | 
						||
| 
								 | 
							
								   corresponding property is returned.  For example,
							 | 
						||
| 
								 | 
							
								   `pluck({ 'foo.bar': 1 }, 'foo.bar')` is 1, not undefined.  This is also
							 | 
						||
| 
								 | 
							
								   true recursively, so `pluck({ 'a': { 'foo.bar': 1 } }, 'a.foo.bar')` is
							 | 
						||
| 
								 | 
							
								   also 1, not undefined.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### randElt(array)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Returns an element from "array" selected uniformly at random.  If "array" is
							 | 
						||
| 
								 | 
							
								empty, throws an Error.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### startsWith(str, prefix)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Returns true if the given string starts with the given prefix and false
							 | 
						||
| 
								 | 
							
								otherwise.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### endsWith(str, suffix)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Returns true if the given string ends with the given suffix and false
							 | 
						||
| 
								 | 
							
								otherwise.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### parseInteger(str, options)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Parses the contents of `str` (a string) as an integer. On success, the integer
							 | 
						||
| 
								 | 
							
								value is returned (as a number). On failure, an error is **returned** describing
							 | 
						||
| 
								 | 
							
								why parsing failed.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								By default, leading and trailing whitespace characters are not allowed, nor are
							 | 
						||
| 
								 | 
							
								trailing characters that are not part of the numeric representation. This
							 | 
						||
| 
								 | 
							
								behaviour can be toggled by using the options below. The empty string (`''`) is
							 | 
						||
| 
								 | 
							
								not considered valid input. If the return value cannot be precisely represented
							 | 
						||
| 
								 | 
							
								as a number (i.e., is smaller than `Number.MIN_SAFE_INTEGER` or larger than
							 | 
						||
| 
								 | 
							
								`Number.MAX_SAFE_INTEGER`), an error is returned. Additionally, the string
							 | 
						||
| 
								 | 
							
								`'-0'` will be parsed as the integer `0`, instead of as the IEEE floating point
							 | 
						||
| 
								 | 
							
								value `-0`.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This function accepts both upper and lowercase characters for digits, similar to
							 | 
						||
| 
								 | 
							
								`parseInt()`, `Number()`, and [strtol(3C)](https://illumos.org/man/3C/strtol).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The following may be specified in `options`:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Option             | Type    | Default | Meaning
							 | 
						||
| 
								 | 
							
								------------------ | ------- | ------- | ---------------------------
							 | 
						||
| 
								 | 
							
								base               | number  | 10      | numeric base (radix) to use, in the range 2 to 36
							 | 
						||
| 
								 | 
							
								allowSign          | boolean | true    | whether to interpret any leading `+` (positive) and `-` (negative) characters
							 | 
						||
| 
								 | 
							
								allowImprecise     | boolean | false   | whether to accept values that may have lost precision (past `MAX_SAFE_INTEGER` or below `MIN_SAFE_INTEGER`)
							 | 
						||
| 
								 | 
							
								allowPrefix        | boolean | false   | whether to interpret the prefixes `0b` (base 2), `0o` (base 8), `0t` (base 10), or `0x` (base 16)
							 | 
						||
| 
								 | 
							
								allowTrailing      | boolean | false   | whether to ignore trailing characters
							 | 
						||
| 
								 | 
							
								trimWhitespace     | boolean | false   | whether to trim any leading or trailing whitespace/line terminators
							 | 
						||
| 
								 | 
							
								leadingZeroIsOctal | boolean | false   | whether a leading zero indicates octal
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Note that if `base` is unspecified, and `allowPrefix` or `leadingZeroIsOctal`
							 | 
						||
| 
								 | 
							
								are, then the leading characters can change the default base from 10. If `base`
							 | 
						||
| 
								 | 
							
								is explicitly specified and `allowPrefix` is true, then the prefix will only be
							 | 
						||
| 
								 | 
							
								accepted if it matches the specified base. `base` and `leadingZeroIsOctal`
							 | 
						||
| 
								 | 
							
								cannot be used together.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								**Context:** It's tricky to parse integers with JavaScript's built-in facilities
							 | 
						||
| 
								 | 
							
								for several reasons:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								- `parseInt()` and `Number()` by default allow the base to be specified in the
							 | 
						||
| 
								 | 
							
								  input string by a prefix (e.g., `0x` for hex).
							 | 
						||
| 
								 | 
							
								- `parseInt()` allows trailing nonnumeric characters.
							 | 
						||
| 
								 | 
							
								- `Number(str)` returns 0 when `str` is the empty string (`''`).
							 | 
						||
| 
								 | 
							
								- Both functions return incorrect values when the input string represents a
							 | 
						||
| 
								 | 
							
								  valid integer outside the range of integers that can be represented precisely.
							 | 
						||
| 
								 | 
							
								  Specifically, `parseInt('9007199254740993')` returns 9007199254740992.
							 | 
						||
| 
								 | 
							
								- Both functions always accept `-` and `+` signs before the digit.
							 | 
						||
| 
								 | 
							
								- Some older JavaScript engines always interpret a leading 0 as indicating
							 | 
						||
| 
								 | 
							
								  octal, which can be surprising when parsing input from users who expect a
							 | 
						||
| 
								 | 
							
								  leading zero to be insignificant.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								While each of these may be desirable in some contexts, there are also times when
							 | 
						||
| 
								 | 
							
								none of them are wanted. `parseInteger()` grants greater control over what
							 | 
						||
| 
								 | 
							
								input's permissible.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### iso8601(date)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Converts a Date object to an ISO8601 date string of the form
							 | 
						||
| 
								 | 
							
								"YYYY-MM-DDTHH:MM:SS.sssZ".  This format is not customizable.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### parseDateTime(str)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Parses a date expressed as a string, as either a number of milliseconds since
							 | 
						||
| 
								 | 
							
								the epoch or any string format that Date accepts, giving preference to the
							 | 
						||
| 
								 | 
							
								former where these two sets overlap (e.g., strings containing small numbers).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### hrtimeDiff(timeA, timeB)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Given two hrtime readings (as from Node's `process.hrtime()`), where timeA is
							 | 
						||
| 
								 | 
							
								later than timeB, compute the difference and return that as an hrtime.  It is
							 | 
						||
| 
								 | 
							
								illegal to invoke this for a pair of times where timeB is newer than timeA.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### hrtimeAdd(timeA, timeB)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Add two hrtime intervals (as from Node's `process.hrtime()`), returning a new
							 | 
						||
| 
								 | 
							
								hrtime interval array.  This function does not modify either input argument.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### hrtimeAccum(timeA, timeB)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Add two hrtime intervals (as from Node's `process.hrtime()`), storing the
							 | 
						||
| 
								 | 
							
								result in `timeA`.  This function overwrites (and returns) the first argument
							 | 
						||
| 
								 | 
							
								passed in.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### hrtimeNanosec(timeA), hrtimeMicrosec(timeA), hrtimeMillisec(timeA)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This suite of functions converts a hrtime interval (as from Node's
							 | 
						||
| 
								 | 
							
								`process.hrtime()`) into a scalar number of nanoseconds, microseconds or
							 | 
						||
| 
								 | 
							
								milliseconds.  Results are truncated, as with `Math.floor()`.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### validateJsonObject(schema, object)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Uses JSON validation (via JSV) to validate the given object against the given
							 | 
						||
| 
								 | 
							
								schema.  On success, returns null.  On failure, *returns* (does not throw) a
							 | 
						||
| 
								 | 
							
								useful Error object.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### extraProperties(object, allowed)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Check an object for unexpected properties.  Accepts the object to check, and an
							 | 
						||
| 
								 | 
							
								array of allowed property name strings.  If extra properties are detected, an
							 | 
						||
| 
								 | 
							
								array of extra property names is returned.  If no properties other than those
							 | 
						||
| 
								 | 
							
								in the allowed list are present on the object, the returned array will be of
							 | 
						||
| 
								 | 
							
								zero length.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### mergeObjects(provided, overrides, defaults)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Merge properties from objects "provided", "overrides", and "defaults".  The
							 | 
						||
| 
								 | 
							
								intended use case is for functions that accept named arguments in an "args"
							 | 
						||
| 
								 | 
							
								object, but want to provide some default values and override other values.  In
							 | 
						||
| 
								 | 
							
								that case, "provided" is what the caller specified, "overrides" are what the
							 | 
						||
| 
								 | 
							
								function wants to override, and "defaults" contains default values.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The function starts with the values in "defaults", overrides them with the
							 | 
						||
| 
								 | 
							
								values in "provided", and then overrides those with the values in "overrides".
							 | 
						||
| 
								 | 
							
								For convenience, any of these objects may be falsey, in which case they will be
							 | 
						||
| 
								 | 
							
								ignored.  The input objects are never modified, but properties in the returned
							 | 
						||
| 
								 | 
							
								object are not deep-copied.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								For example:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    mergeObjects(undefined, { 'objectMode': true }, { 'highWaterMark': 0 })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								returns:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    { 'objectMode': true, 'highWaterMark': 0 }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								For another example:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    mergeObjects(
							 | 
						||
| 
								 | 
							
								        { 'highWaterMark': 16, 'objectMode': 7 }, /* from caller */
							 | 
						||
| 
								 | 
							
								        { 'objectMode': true },                   /* overrides */
							 | 
						||
| 
								 | 
							
								        { 'highWaterMark': 0 });                  /* default */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								returns:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    { 'objectMode': true, 'highWaterMark': 16 }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# Contributing
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								See separate [contribution guidelines](CONTRIBUTING.md).
							 |