diff --git a/docs/api/op.md b/docs/api/op.md
index 2f5c862..d238760 100644
--- a/docs/api/op.md
+++ b/docs/api/op.md
@@ -54,14 +54,6 @@ Merges two or more arrays in sequence, returning a new array.
* *values*: The arrays to merge.
-
#
-op.join(array[, delimiter]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/array.js)
-
-Creates and returns a new string by concatenating all of the elements in an *array* (or an array-like object), separated by commas or a specified *delimiter* string. If the *array* has only one item, then that item will be returned without using the delimiter.
-
-* *array*: The input array value.
-* *join*: The delimiter string (default `','`).
-
#
op.includes(array, value[, index]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/array.js)
@@ -79,6 +71,14 @@ Returns the first index at which a given *value* can be found in the *sequence*
* *sequence*: The input array or string value.
* *value*: The value to search for.
+
#
+op.join(array[, delimiter]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/array.js)
+
+Creates and returns a new string by concatenating all of the elements in an *array* (or an array-like object), separated by commas or a specified *delimiter* string. If the *array* has only one item, then that item will be returned without using the delimiter.
+
+* *array*: The input array value.
+* *delimiter*: The delimiter string (default `','`).
+
#
op.lastindexof(sequence, value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/array.js)
@@ -102,21 +102,12 @@ Returns a new array in which the given *property* has been extracted for each el
* *array*: The input array value.
* *property*: The property name string to extract. Nested properties are not supported: the input `"a.b"` will indicates a property with that exact name, *not* a nested property `"b"` of the object `"a"`.
-
#
-op.slice(sequence[, start, end]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/array.js)
-
-Returns a copy of a portion of the input *sequence* (array or string) selected from *start* to *end* (*end* not included) where *start* and *end* represent the index of items in the sequence.
-
-* *sequence*: The input array or string value.
-* *start*: The starting integer index to copy from (inclusive, default `0`).
-* *end*: The ending integer index to copy from (exclusive, default `sequence.length`).
-
#
-op.reverse(array) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/array.js)
+op.reverse(sequence) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/array.js)
-Returns a new array with the element order reversed: the first *array* element becomes the last, and the last *array* element becomes the first. The input *array* is unchanged.
+Returns a new array or string with the element order reversed: the first *sequence* element becomes the last, and the last *sequence* element becomes the first. The input *sequence* is unchanged.
-* *array*: The input array value.
+* *sequence*: The input array or string value.
#
op.sequence([start,] stop[, step]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/sequence.js)
@@ -127,6 +118,14 @@ Returns an array containing an arithmetic sequence from the *start* value to the
* *stop*: The stopping value of the sequence. The stop value is exclusive; it is not included in the result.
* *step*: The step increment between sequence values (default `1`).
+
#
+op.slice(sequence[, start, end]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/array.js)
+
+Returns a copy of a portion of the input *sequence* (array or string) selected from *start* to *end* (*end* not included) where *start* and *end* represent the index of items in the sequence.
+
+* *sequence*: The input array or string value.
+* *start*: The starting integer index to copy from (inclusive, default `0`).
+* *end*: The ending integer index to copy from (exclusive, default `sequence.length`).
@@ -683,7 +682,7 @@ Compare two values for equality, using join semantics in which `null !== null`.
Returns a boolean indicating whether the *object* has the specified *key* as its own property (as opposed to inheriting it). If the *object* is a [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) or [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) instance, the `has` method will be invoked directly on the object, otherwise `Object.hasOwnProperty` is used.
* *object*: The object, Map, or Set to test for property membership.
-* *property*: The string property name to test for.
+* *key*: The string key (property name) to test for.
#
op.keys(object) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/object.js)
@@ -811,6 +810,7 @@ If specified, the *index* looks up a value of the resulting match. If *index* is
* *value*: The input string value.
* *regexp*: The [regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) to match against.
+* *index*: The index into the match result array or capture group.
*Examples*
diff --git a/src/format/value.js b/src/format/value.js
index b6974f4..a3a180c 100644
--- a/src/format/value.js
+++ b/src/format/value.js
@@ -45,6 +45,7 @@ export default function(v, options = {}) {
} else {
const s = JSON.stringify(
v,
+ // @ts-ignore
(k, v) => isTypedArray(v) ? Array.from(v) : v
);
// @ts-ignore
diff --git a/src/op/functions/array.js b/src/op/functions/array.js
index 1033a9e..14e3167 100644
--- a/src/op/functions/array.js
+++ b/src/op/functions/array.js
@@ -6,20 +6,123 @@ import isValid from '../../util/is-valid.js';
const isSeq = (seq) => isArrayType(seq) || isString(seq);
export default {
- compact: (arr) => isArrayType(arr) ? arr.filter(v => isValid(v)) : arr,
- concat: (...values) => [].concat(...values),
- includes: (seq, value, index) => isSeq(seq)
- ? seq.includes(value, index)
- : false,
- indexof: (seq, value) => isSeq(seq) ? seq.indexOf(value) : -1,
- join: (arr, delim) => isArrayType(arr) ? arr.join(delim) : NULL,
- lastindexof: (seq, value) => isSeq(seq) ? seq.lastIndexOf(value) : -1,
- length: (seq) => isSeq(seq) ? seq.length : 0,
- pluck: (arr, prop) => isArrayType(arr)
- ? arr.map(v => isValid(v) ? v[prop] : NULL)
- : NULL,
- reverse: (seq) => isArrayType(seq) ? seq.slice().reverse()
- : isString(seq) ? seq.split('').reverse().join('')
- : NULL,
- slice: (seq, start, end) => isSeq(seq) ? seq.slice(start, end) : NULL
+ /**
+ * Returns a new compacted array with invalid values
+ * (`null`, `undefined`, `NaN`) removed.
+ * @template T
+ * @param {T[]} array The input array.
+ * @return {T[]} A compacted array.
+ */
+ compact: (array) => isArrayType(array)
+ ? array.filter(v => isValid(v))
+ : array,
+
+ /**
+ * Merges two or more arrays in sequence, returning a new array.
+ * @template T
+ * @param {...(T|T[])} values The arrays to merge.
+ * @return {T[]} The merged array.
+ */
+ concat: (...values) => [].concat(...values),
+
+ /**
+ * Determines whether an *array* includes a certain *value* among its
+ * entries, returning `true` or `false` as appropriate.
+ * @template T
+ * @param {T[]} sequence The input array value.
+ * @param {T} value The value to search for.
+ * @param {number} [index=0] The integer index to start searching
+ * from (default `0`).
+ * @return {boolean} True if the value is included, false otherwise.
+ */
+ includes: (sequence, value, index) => isSeq(sequence)
+ ? sequence.includes(value, index)
+ : false,
+
+ /**
+ * Returns the first index at which a given *value* can be found in the
+ * *sequence* (array or string), or -1 if it is not present.
+ * @template T
+ * @param {T[]|string} sequence The input array or string value.
+ * @param {T} value The value to search for.
+ * @return {number} The index of the value, or -1 if not present.
+ */
+ indexof: (sequence, value) => isSeq(sequence)
+ // @ts-ignore
+ ? sequence.indexOf(value)
+ : -1,
+
+ /**
+ * Creates and returns a new string by concatenating all of the elements
+ * in an *array* (or an array-like object), separated by commas or a
+ * specified *delimiter* string. If the *array* has only one item, then
+ * that item will be returned without using the delimiter.
+ * @template T
+ * @param {T[]} array The input array value.
+ * @param {string} delim The delimiter string (default `','`).
+ * @return {string} The joined string.
+ */
+ join: (array, delim) => isArrayType(array) ? array.join(delim) : NULL,
+
+ /**
+ * Returns the last index at which a given *value* can be found in the
+ * *sequence* (array or string), or -1 if it is not present.
+ * @template T
+ * @param {T[]|string} sequence The input array or string value.
+ * @param {T} value The value to search for.
+ * @return {number} The last index of the value, or -1 if not present.
+ */
+ lastindexof: (sequence, value) => isSeq(sequence)
+ // @ts-ignore
+ ? sequence.lastIndexOf(value)
+ : -1,
+
+ /**
+ * Returns the length of the input *sequence* (array or string).
+ * @param {Array|string} sequence The input array or string value.
+ * @return {number} The length of the sequence.
+ */
+ length: (sequence) => isSeq(sequence) ? sequence.length : 0,
+
+ /**
+ * Returns a new array in which the given *property* has been extracted
+ * for each element in the input *array*.
+ * @param {Array} array The input array value.
+ * @param {string} property The property name string to extract. Nested
+ * properties are not supported: the input `"a.b"` will indicates a
+ * property with that exact name, *not* a nested property `"b"` of
+ * the object `"a"`.
+ * @return {Array} An array of plucked properties.
+ */
+ pluck: (array, property) => isArrayType(array)
+ ? array.map(v => isValid(v) ? v[property] : NULL)
+ : NULL,
+
+ /**
+ * Returns a new array or string with the element order reversed: the first
+ * *sequence* element becomes the last, and the last *sequence* element
+ * becomes the first. The input *sequence* is unchanged.
+ * @template T
+ * @param {T[]|string} sequence The input array or string value.
+ * @return {T[]|string} The reversed sequence.
+ */
+ reverse: (sequence) => isArrayType(sequence) ? sequence.slice().reverse()
+ : isString(sequence) ? sequence.split('').reverse().join('')
+ : NULL,
+
+ /**
+ * Returns a copy of a portion of the input *sequence* (array or string)
+ * selected from *start* to *end* (*end* not included) where *start* and
+ * *end* represent the index of items in the sequence.
+ * @template T
+ * @param {T[]|string} sequence The input array or string value.
+ * @param {number} [start=0] The starting integer index to copy from
+ * (inclusive, default `0`).
+ * @param {number} [end] The ending integer index to copy from (exclusive,
+ * default `sequence.length`).
+ * @return {T[]|string} The sliced sequence.
+ */
+ slice: (sequence, start, end) => isSeq(sequence)
+ ? sequence.slice(start, end)
+ : NULL
};
diff --git a/src/op/functions/bin.js b/src/op/functions/bin.js
index c074e34..7206343 100644
--- a/src/op/functions/bin.js
+++ b/src/op/functions/bin.js
@@ -3,11 +3,11 @@
* Useful for creating equal-width histograms.
* Values outside the [min, max] range will be mapped to
* -Infinity (< min) or +Infinity (> max).
- * @param {number} value - The value to bin.
- * @param {number} min - The minimum bin boundary.
- * @param {number} max - The maximum bin boundary.
- * @param {number} step - The step size between bin boundaries.
- * @param {number} [offset=0] - Offset in steps by which to adjust
+ * @param {number} value The value to bin.
+ * @param {number} min The minimum bin boundary.
+ * @param {number} max The maximum bin boundary.
+ * @param {number} step The step size between bin boundaries.
+ * @param {number} [offset=0] Offset in steps by which to adjust
* the bin value. An offset of 1 will return the next boundary.
*/
export default function(value, min, max, step, offset) {
diff --git a/src/op/functions/date.js b/src/op/functions/date.js
index 9b9057c..1b7971b 100644
--- a/src/op/functions/date.js
+++ b/src/op/functions/date.js
@@ -64,6 +64,12 @@ function utcdatetime(year, month, date, hours, minutes, seconds, milliseconds) {
));
}
+/**
+ * Return the current day of the year in local time as a number
+ * between 1 and 366.
+ * @param {Date|number} date A date or timestamp.
+ * @return {number} The day of the year in local time.
+ */
function dayofyear(date) {
t1.setTime(+date);
t1.setHours(0, 0, 0, 0);
@@ -74,6 +80,12 @@ function dayofyear(date) {
return Math.floor(1 + ((+t1 - +t0) - tz) / msDay);
}
+/**
+ * Return the current day of the year in UTC time as a number
+ * between 1 and 366.
+ * @param {Date|number} date A date or timestamp.
+ * @return {number} The day of the year in UTC time.
+ */
function utcdayofyear(date) {
t1.setTime(+date);
t1.setUTCHours(0, 0, 0, 0);
@@ -81,6 +93,12 @@ function utcdayofyear(date) {
return Math.floor(1 + (+t1 - t0) / msDay);
}
+/**
+ * Return the current week of the year in local time as a number
+ * between 1 and 52.
+ * @param {Date|number} date A date or timestamp.
+ * @return {number} The week of the year in local time.
+ */
function week(date, firstday) {
const i = firstday || 0;
t1.setTime(+date);
@@ -95,6 +113,12 @@ function week(date, firstday) {
return Math.floor((1 + (+t1 - +t0) - tz) / msWeek);
}
+/**
+ * Return the current week of the year in UTC time as a number
+ * between 1 and 52.
+ * @param {Date|number} date A date or timestamp.
+ * @return {number} The week of the year in UTC time.
+ */
function utcweek(date, firstday) {
const i = firstday || 0;
t1.setTime(+date);
@@ -109,32 +133,259 @@ function utcweek(date, firstday) {
}
export default {
- format_date: (date, shorten) => formatDate(t(date), !shorten),
- format_utcdate: (date, shorten) => formatUTCDate(t(date), !shorten),
- timestamp: (date) => +t(date),
- year: (date) => t(date).getFullYear(),
- quarter: (date) => Math.floor(t(date).getMonth() / 3),
- month: (date) => t(date).getMonth(),
- date: (date) => t(date).getDate(),
- dayofweek: (date) => t(date).getDay(),
- hours: (date) => t(date).getHours(),
- minutes: (date) => t(date).getMinutes(),
- seconds: (date) => t(date).getSeconds(),
- milliseconds: (date) => t(date).getMilliseconds(),
- utcyear: (date) => t(date).getUTCFullYear(),
- utcquarter: (date) => Math.floor(t(date).getUTCMonth() / 3),
- utcmonth: (date) => t(date).getUTCMonth(),
- utcdate: (date) => t(date).getUTCDate(),
- utcdayofweek: (date) => t(date).getUTCDay(),
- utchours: (date) => t(date).getUTCHours(),
- utcminutes: (date) => t(date).getUTCMinutes(),
- utcseconds: (date) => t(date).getUTCSeconds(),
- utcmilliseconds: (date) => t(date).getUTCMilliseconds(),
+ /**
+ * Returns an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) formatted
+ * string for the given *date* in local timezone. The resulting string is
+ * compatible with *parse_date* and JavaScript's built-in *Date.parse*.
+ * @param {Date | number} date The input Date or timestamp value.
+ * @param {boolean} [shorten=false] A boolean flag (default `false`)
+ * indicating if the formatted string should be shortened if possible.
+ * For example, the local date `2001-01-01` will shorten from
+ * `"2001-01-01T00:00:00.000"` to `"2001-01-01T00:00"`.
+ * @return {string} The formatted date string in local time.
+ */
+ format_date: (date, shorten) => formatDate(t(date), !shorten),
+
+ /**
+ * Returns an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) formatted
+ * string for the given *date* in Coordinated Universal Time (UTC). The
+ * resulting string is compatible with *parse_date* and JavaScript's
+ * built-in *Date.parse*.
+ * @param {Date | number} date The input Date or timestamp value.
+ * @param {boolean} [shorten=false] A boolean flag (default `false`)
+ * indicating if the formatted string should be shortened if possible.
+ * For example, the the UTC date `2001-01-01` will shorten from
+ * `"2001-01-01T00:00:00.000Z"` to `"2001-01-01"`
+ * @return {string} The formatted date string in UTC time.
+ */
+ format_utcdate: (date, shorten) => formatUTCDate(t(date), !shorten),
+
+ /**
+ * Returns the number of milliseconds elapsed since midnight, January 1,
+ * 1970 Universal Coordinated Time (UTC).
+ * @return {number} The timestamp for now.
+ */
+ now: Date.now,
+
+ /**
+ * Returns the timestamp for a *date* as the number of milliseconds elapsed
+ * since January 1, 1970 00:00:00 UTC.
+ * @param {Date | number} date The input Date value.
+ * @return {number} The timestamp value.
+ */
+ timestamp: (date) => +t(date),
+
+ /**
+ * Creates and returns a new Date value. If no arguments are provided,
+ * the current date and time are used.
+ * @param {number} [year] The year.
+ * @param {number} [month=0] The (zero-based) month.
+ * @param {number} [date=1] The date within the month.
+ * @param {number} [hours=0] The hour within the day.
+ * @param {number} [minutes=0] The minute within the hour.
+ * @param {number} [seconds=0] The second within the minute.
+ * @param {number} [milliseconds=0] The milliseconds within the second.
+ * @return {Date} The Date value.
+ */
datetime,
- dayofyear,
+
+ /**
+ * Returns the year of the specified *date* according to local time.
+ * @param {Date | number} date The input Date or timestamp value.
+ * @return {number} The year value in local time.
+ */
+ year: (date) => t(date).getFullYear(),
+
+ /**
+ * Returns the zero-based quarter of the specified *date* according to
+ * local time.
+ * @param {Date | number} date The input Date or timestamp value.
+ * @return {number} The quarter value in local time.
+ */
+ quarter: (date) => Math.floor(t(date).getMonth() / 3),
+
+ /**
+ * Returns the zero-based month of the specified *date* according to local
+ * time. A value of `0` indicates January, `1` indicates February, and so on.
+ * @param {Date | number} date The input Date or timestamp value.
+ * @return {number} The month value in local time.
+ */
+ month: (date) => t(date).getMonth(),
+
+ /**
+ * Returns the week number of the year (0-53) for the specified *date*
+ * according to local time. By default, Sunday is used as the first day
+ * of the week. All days in a new year preceding the first Sunday are
+ * considered to be in week 0.
+ * @param {Date | number} date The input Date or timestamp value.
+ * @param {number} firstday The number of first day of the week (default
+ * `0` for Sunday, `1` for Monday and so on).
+ * @return {number} The week of the year in local time.
+ */
week,
+
+ /**
+ * Returns the date (day of month) of the specified *date* according
+ * to local time.
+ * @param {Date | number} date The input Date or timestamp value.
+ * @return {number} The date (day of month) value.
+ */
+ date: (date) => t(date).getDate(),
+
+ /**
+ * Returns the day of the year (1-366) of the specified *date* according
+ * to local time.
+ * @param {Date | number} date The input Date or timestamp value.
+ * @return {number} The day of the year in local time.
+ */
+ dayofyear,
+
+ /**
+ * Returns the Sunday-based day of the week (0-6) of the specified *date*
+ * according to local time. A value of `0` indicates Sunday, `1` indicates
+ * Monday, and so on.
+ * @param {Date | number} date The input Date or timestamp value.
+ * @return {number} The day of the week value in local time.
+ */
+ dayofweek: (date) => t(date).getDay(),
+
+ /**
+ * Returns the hour of the day for the specified *date* according
+ * to local time.
+ * @param {Date | number} date The input Date or timestamp value.
+ * @return {number} The hour value in local time.
+ */
+ hours: (date) => t(date).getHours(),
+
+ /**
+ * Returns the minute of the hour for the specified *date* according
+ * to local time.
+ * @param {Date | number} date The input Date or timestamp value.
+ * @return {number} The minutes value in local time.
+ */
+ minutes: (date) => t(date).getMinutes(),
+
+ /**
+ * Returns the seconds of the minute for the specified *date* according
+ * to local time.
+ * @param {Date | number} date The input Date or timestamp value.
+ * @return {number} The seconds value in local time.
+ */
+ seconds: (date) => t(date).getSeconds(),
+
+ /**
+ * Returns the milliseconds of the second for the specified *date* according
+ * to local time.
+ * @param {Date | number} date The input Date or timestamp value.
+ * @return {number} The milliseconds value in local time.
+ */
+ milliseconds: (date) => t(date).getMilliseconds(),
+
+ /**
+ * Creates and returns a new Date value using Coordinated Universal Time
+ * (UTC). If no arguments are provided, the current date and time are used.
+ * @param {number} [year] The year.
+ * @param {number} [month=0] The (zero-based) month.
+ * @param {number} [date=1] The date within the month.
+ * @param {number} [hours=0] The hour within the day.
+ * @param {number} [minutes=0] The minute within the hour.
+ * @param {number} [seconds=0] The second within the minute.
+ * @param {number} [milliseconds=0] The milliseconds within the second.
+ * @return {Date} The Date value.
+ */
utcdatetime,
- utcdayofyear,
+
+ /**
+ * Returns the year of the specified *date* according to Coordinated
+ * Universal Time (UTC).
+ * @param {Date | number} date The input Date or timestamp value.
+ * @return {number} The year value in UTC time.
+ */
+ utcyear: (date) => t(date).getUTCFullYear(),
+
+ /**
+ * Returns the zero-based quarter of the specified *date* according to
+ * Coordinated Universal Time (UTC)
+ * @param {Date | number} date The input Date or timestamp value.
+ * @return {number} The quarter value in UTC time.
+ */
+ utcquarter: (date) => Math.floor(t(date).getUTCMonth() / 3),
+
+ /**
+ * Returns the zero-based month of the specified *date* according to
+ * Coordinated Universal Time (UTC). A value of `0` indicates January,
+ * `1` indicates February, and so on.
+ * @param {Date | number} date The input Date or timestamp value.
+ * @return {number} The month value in UTC time.
+ */
+ utcmonth: (date) => t(date).getUTCMonth(),
+
+ /**
+ * Returns the week number of the year (0-53) for the specified *date*
+ * according to Coordinated Universal Time (UTC). By default, Sunday is
+ * used as the first day of the week. All days in a new year preceding the
+ * first Sunday are considered to be in week 0.
+ * @param {Date | number} date The input Date or timestamp value.
+ * @param {number} firstday The number of first day of the week (default
+ * `0` for Sunday, `1` for Monday and so on).
+ * @return {number} The week of the year in UTC time.
+ */
utcweek,
- now: Date.now
+
+ /**
+ * Returns the date (day of month) of the specified *date* according to
+ * Coordinated Universal Time (UTC).
+ * @param {Date | number} date The input Date or timestamp value.
+ * @return {number} The date (day of month) value in UTC time.
+ */
+ utcdate: (date) => t(date).getUTCDate(),
+
+ /**
+ * Returns the day of the year (1-366) of the specified *date* according
+ * to Coordinated Universal Time (UTC).
+ * @param {Date | number} date The input Date or timestamp value.
+ * @return {number} The day of the year in UTC time.
+ */
+ utcdayofyear,
+
+ /**
+ * Returns the Sunday-based day of the week (0-6) of the specified *date*
+ * according to Coordinated Universal Time (UTC). A value of `0` indicates
+ * Sunday, `1` indicates Monday, and so on.
+ * @param {Date | number} date The input Date or timestamp value.
+ * @return {number} The day of the week in UTC time.
+ */
+ utcdayofweek: (date) => t(date).getUTCDay(),
+
+ /**
+ * Returns the hour of the day for the specified *date* according to
+ * Coordinated Universal Time (UTC).
+ * @param {Date | number} date The input Date or timestamp value.
+ * @return {number} The hours value in UTC time.
+ */
+ utchours: (date) => t(date).getUTCHours(),
+
+ /**
+ * Returns the minute of the hour for the specified *date* according to
+ * Coordinated Universal Time (UTC).
+ * @param {Date | number} date The input Date or timestamp value.
+ * @return {number} The minutes value in UTC time.
+ */
+ utcminutes: (date) => t(date).getUTCMinutes(),
+
+ /**
+ * Returns the seconds of the minute for the specified *date* according to
+ * Coordinated Universal Time (UTC).
+ * @param {Date | number} date The input Date or timestamp value.
+ * @return {number} The seconds value in UTC time.
+ */
+ utcseconds: (date) => t(date).getUTCSeconds(),
+
+ /**
+ * Returns the milliseconds of the second for the specified *date* according to
+ * Coordinated Universal Time (UTC).
+ * @param {Date | number} date The input Date or timestamp value.
+ * @return {number} The milliseconds value in UTC time.
+ */
+ utcmilliseconds: (date) => t(date).getUTCMilliseconds()
};
diff --git a/src/op/functions/json.js b/src/op/functions/json.js
index bd980eb..d0e4e6e 100644
--- a/src/op/functions/json.js
+++ b/src/op/functions/json.js
@@ -1,4 +1,16 @@
export default {
- parse_json: (str) => JSON.parse(str),
- to_json: (val) => JSON.stringify(val)
+ /**
+ * Parses a string *value* in JSON format, constructing the JavaScript
+ * value or object described by the string.
+ * @param {string} value The input string value.
+ * @return {any} The parsed JSON.
+ */
+ parse_json: (value) => JSON.parse(value),
+
+ /**
+ * Converts a JavaScript object or value to a JSON string.
+ * @param {*} value The value to convert to a JSON string.
+ * @return {string} The JSON string.
+ */
+ to_json: (value) => JSON.stringify(value)
};
diff --git a/src/op/functions/math.js b/src/op/functions/math.js
index df891fa..b7064ff 100644
--- a/src/op/functions/math.js
+++ b/src/op/functions/math.js
@@ -1,43 +1,300 @@
import { random } from '../../util/random.js';
export default {
+ /**
+ * Return a random floating point number between 0 (inclusive) and 1
+ * (exclusive). By default uses *Math.random*. Use the *seed* method
+ * to instead use a seeded random number generator.
+ * @return {number} A pseudorandom number between 0 and 1.
+ */
random,
- is_nan: Number.isNaN,
+
+ /**
+ * Tests if the input *value* is not a number (`NaN`); equivalent
+ * to *Number.isNaN*.
+ * @param {*} value The value to test.
+ * @return {boolean} True if the value is not a number, false otherwise.
+ */
+ is_nan: Number.isNaN,
+
+ /**
+ * Tests if the input *value* is finite; equivalent to *Number.isFinite*.
+ * @param {*} value The value to test.
+ * @return {boolean} True if the value is finite, false otherwise.
+ */
is_finite: Number.isFinite,
- abs: Math.abs,
- cbrt: Math.cbrt,
- ceil: Math.ceil,
- clz32: Math.clz32,
- exp: Math.exp,
- expm1: Math.expm1,
- floor: Math.floor,
- fround: Math.fround,
+ /**
+ * Returns the absolute value of the input *value*; equivalent to *Math.abs*.
+ * @param {number} value The input number value.
+ * @return {number} The absolute value.
+ */
+ abs: Math.abs,
+
+ /**
+ * Returns the cube root value of the input *value*; equivalent to
+ * *Math.cbrt*.
+ * @param {number} value The input number value.
+ * @return {number} The cube root value.
+ */
+ cbrt: Math.cbrt,
+
+ /**
+ * Returns the ceiling of the input *value*, the nearest integer equal to
+ * or greater than the input; equivalent to *Math.ceil*.
+ * @param {number} value The input number value.
+ * @return {number} The ceiling value.
+ */
+ ceil: Math.ceil,
+
+ /**
+ * Returns the number of leading zero bits in the 32-bit binary
+ * representation of a number *value*; equivalent to *Math.clz32*.
+ * @param {number} value The input number value.
+ * @return {number} The leading zero bits value.
+ */
+ clz32: Math.clz32,
+
+ /**
+ * Returns *evalue*, where *e* is Euler's number, the base of the
+ * natural logarithm; equivalent to *Math.exp*.
+ * @param {number} value The input number value.
+ * @return {number} The base-e exponentiated value.
+ */
+ exp: Math.exp,
+
+ /**
+ * Returns *evalue - 1*, where *e* is Euler's number, the base of
+ * the natural logarithm; equivalent to *Math.expm1*.
+ * @param {number} value The input number value.
+ * @return {number} The base-e exponentiated value minus 1.
+ */
+ expm1: Math.expm1,
+
+ /**
+ * Returns the floor of the input *value*, the nearest integer equal to or
+ * less than the input; equivalent to *Math.floor*.
+ * @param {number} value The input number value.
+ * @return {number} The floor value.
+ */
+ floor: Math.floor,
+
+ /**
+ * Returns the nearest 32-bit single precision float representation of the
+ * input number *value*; equivalent to *Math.fround*. Useful for translating
+ * between 64-bit `Number` values and values from a `Float32Array`.
+ * @param {number} value The input number value.
+ * @return {number} The rounded value.
+ */
+ fround: Math.fround,
+
+ /**
+ * Returns the greatest (maximum) value among the input *values*; equivalent
+ * to *Math.max*. This is _not_ an aggregate function, see *op.max* to
+ * compute a maximum value across multiple rows.
+ * @param {...number} values The input number values.
+ * @return {number} The greatest (maximum) value among the inputs.
+ */
greatest: Math.max,
- least: Math.min,
- log: Math.log,
- log10: Math.log10,
- log1p: Math.log1p,
- log2: Math.log2,
- pow: Math.pow,
- round: Math.round,
- sign: Math.sign,
- sqrt: Math.sqrt,
- trunc: Math.trunc,
-
- degrees: (rad) => 180 * rad / Math.PI,
- radians: (deg) => Math.PI * deg / 180,
- acos: Math.acos,
- acosh: Math.acosh,
- asin: Math.asin,
- asinh: Math.asinh,
- atan: Math.atan,
- atan2: Math.atan2,
- atanh: Math.atanh,
- cos: Math.cos,
- cosh: Math.cosh,
- sin: Math.sin,
- sinh: Math.sinh,
- tan: Math.tan,
- tanh: Math.tanh
+
+ /**
+ * Returns the least (minimum) value among the input *values*; equivalent
+ * to *Math.min*. This is _not_ an aggregate function, see *op.min* to
+ * compute a minimum value across multiple rows.
+ * @param {...number} values The input number values.
+ * @return {number} The least (minimum) value among the inputs.
+ */
+ least: Math.min,
+
+ /**
+ * Returns the natural logarithm (base *e*) of a number *value*; equivalent
+ * to *Math.log*.
+ * @param {number} value The input number value.
+ * @return {number} The base-e log value.
+ */
+ log: Math.log,
+
+ /**
+ * Returns the base 10 logarithm of a number *value*; equivalent
+ * to *Math.log10*.
+ * @param {number} value The input number value.
+ * @return {number} The base-10 log value.
+ */
+ log10: Math.log10,
+
+ /**
+ * Returns the natural logarithm (base *e*) of 1 + a number *value*;
+ * equivalent to *Math.log1p*.
+ * @param {number} value The input number value.
+ * @return {number} The base-e log of value + 1.
+ */
+ log1p: Math.log1p,
+
+ /**
+ * Returns the base 2 logarithm of a number *value*; equivalent
+ * to *Math.log2*.
+ * @param {number} value The input number value.
+ * @return {number} The base-2 log value.
+ */
+ log2: Math.log2,
+
+ /**
+ * Returns the *base* raised to the *exponent* power, that is,
+ * *base**exponent*; equivalent to *Math.pow*.
+ * @param {number} base The base number value.
+ * @param {number} exponent The exponent number value.
+ * @return {number} The exponentiated value.
+ */
+ pow: Math.pow,
+
+ /**
+ * Returns the value of a number rounded to the nearest integer;
+ * equivalent to *Math.round*.
+ * @param {number} value The input number value.
+ * @return {number} The rounded value.
+ */
+ round: Math.round,
+
+ /**
+ * Returns either a positive or negative +/- 1, indicating the sign of the
+ * input *value*; equivalent to *Math.sign*.
+ * @param {number} value The input number value.
+ * @return {number} The sign of the value.
+ */
+ sign: Math.sign,
+
+ /**
+ * Returns the square root of the input *value*; equivalent to *Math.sqrt*.
+ * @param {number} value The input number value.
+ * @return {number} The square root value.
+ */
+ sqrt: Math.sqrt,
+
+ /**
+ * Returns the integer part of a number by removing any fractional digits;
+ * equivalent to *Math.trunc*.
+ * @param {number} value The input number value.
+ * @return {number} The truncated value.
+ */
+ trunc: Math.trunc,
+
+ /**
+ * Converts the input *radians* value to degrees.
+ * @param {number} radians The input radians value.
+ * @return {number} The value in degrees
+ */
+ degrees: (radians) => 180 * radians / Math.PI,
+
+ /**
+ * Converts the input *degrees* value to radians.
+ * @param {number} degrees The input degrees value.
+ * @return {number} The value in radians.
+ */
+ radians: (degrees) => Math.PI * degrees / 180,
+
+ /**
+ * Returns the arc-cosine (in radians) of a number *value*;
+ * equivalent to *Math.acos*.
+ * @param {number} value The input number value.
+ * @return {number} The arc-cosine value.
+ */
+ acos: Math.acos,
+
+ /**
+ * Returns the hyperbolic arc-cosine of a number *value*;
+ * equivalent to *Math.acosh*.
+ * @param {number} value The input number value.
+ * @return {number} The hyperbolic arc-cosine value.
+ */
+ acosh: Math.acosh,
+
+ /**
+ * Returns the arc-sine (in radians) of a number *value*;
+ * equivalent to *Math.asin*.
+ * @param {number} value The input number value.
+ * @return {number} The arc-sine value.
+ */
+ asin: Math.asin,
+
+ /**
+ * Returns the hyperbolic arc-sine of a number *value*;
+ * equivalent to *Math.asinh*.
+ * @param {number} value The input number value.
+ * @return {number} The hyperbolic arc-sine value.
+ */
+ asinh: Math.asinh,
+
+ /**
+ * Returns the arc-tangent (in radians) of a number *value*;
+ * equivalent to *Math.atan*.
+ * @param {number} value The input number value.
+ * @return {number} The arc-tangent value.
+ */
+ atan: Math.atan,
+
+ /**
+ * Returns the angle in the plane (in radians) between the positive x-axis
+ * and the ray from (0, 0) to the point (*x*, *y*);
+ * equivalent to *Math.atan2*.
+ * @param {number} y The y coordinate of the point.
+ * @param {number} x The x coordinate of the point.
+ * @return {number} The arc-tangent angle.
+ */
+ atan2: Math.atan2,
+
+ /**
+ * Returns the hyperbolic arc-tangent of a number *value*;
+ * equivalent to *Math.atanh*.
+ * @param {number} value The input number value.
+ * @return {number} The hyperbolic arc-tangent value.
+ */
+ atanh: Math.atanh,
+
+ /**
+ * Returns the cosine (in radians) of a number *value*;
+ * equivalent to *Math.cos*.
+ * @param {number} value The input number value.
+ * @return {number} The cosine value.
+ */
+ cos: Math.cos,
+
+ /**
+ * Returns the hyperbolic cosine (in radians) of a number *value*;
+ * equivalent to *Math.cosh*.
+ * @param {number} value The input number value.
+ * @return {number} The hyperbolic cosine value.
+ */
+ cosh: Math.cosh,
+
+ /**
+ * Returns the sine (in radians) of a number *value*;
+ * equivalent to *Math.sin*.
+ * @param {number} value The input number value.
+ * @return {number} The sine value.
+ */
+ sin: Math.sin,
+
+ /**
+ * Returns the hyperbolic sine (in radians) of a number *value*;
+ * equivalent to *Math.sinh*.
+ * @param {number} value The input number value.
+ * @return {number} The hyperbolic sine value.
+ */
+ sinh: Math.sinh,
+
+ /**
+ * Returns the tangent (in radians) of a number *value*;
+ * equivalent to *Math.tan*.
+ * @param {number} value The input number value.
+ * @return {number} The tangent value.
+ */
+ tan: Math.tan,
+
+ /**
+ * Returns the hyperbolic tangent (in radians) of a number *value*;
+ * equivalent to *Math.tanh*.
+ * @param {number} value The input number value.
+ * @return {number} The hyperbolic tangent value.
+ */
+ tanh: Math.tanh
};
diff --git a/src/op/functions/object.js b/src/op/functions/object.js
index e6d570f..a00f7d7 100644
--- a/src/op/functions/object.js
+++ b/src/op/functions/object.js
@@ -8,17 +8,67 @@ function array(iter) {
}
export default {
- has: (obj, key) => isMapOrSet(obj) ? obj.has(key)
- : obj != null ? has(obj, key)
- : false,
- keys: (obj) => isMap(obj) ? array(obj.keys())
- : obj != null ? Object.keys(obj)
- : [],
- values: (obj) => isMapOrSet(obj) ? array(obj.values())
- : obj != null ? Object.values(obj)
- : [],
- entries: (obj) => isMapOrSet(obj) ? array(obj.entries())
- : obj != null ? Object.entries(obj)
- : [],
- object: (entries) => entries ? Object.fromEntries(entries) : NULL
+ /**
+ * Returns a boolean indicating whether the *object* has the specified *key*
+ * as its own property (as opposed to inheriting it). If the *object* is a
+ * *Map* or *Set* instance, the *has* method will be invoked directly on the
+ * object, otherwise *Object.hasOwnProperty* is used.
+ * @template K, V
+ * @param {Map|Set|Record} object The object, Map, or Set to
+ * test for property membership.
+ * @param {K} key The property key to test for.
+ * @return {boolean} True if the object has the given key, false otherwise.
+ */
+ has: (object, key) => isMapOrSet(object) ? object.has(key)
+ : object != null ? has(object, `${key}`)
+ : false,
+
+ /**
+ * Returns an array of a given *object*'s own enumerable property names. If
+ * the *object* is a *Map* instance, the *keys* method will be invoked
+ * directly on the object, otherwise *Object.keys* is used.
+ * @template K, V
+ * @param {Map|Record} object The input object or Map value.
+ * @return {K[]} An array of property key name strings.
+ */
+ keys: (object) => isMap(object) ? array(object.keys())
+ : object != null ? Object.keys(object)
+ : [],
+
+ /**
+ * Returns an array of a given *object*'s own enumerable property values. If
+ * the *object* is a *Map* or *Set* instance, the *values* method will be
+ * invoked directly on the object, otherwise *Object.values* is used.
+ * @template K, V
+ * @param {Map|Set|Record} object The input
+ * object, Map, or Set value.
+ * @return {V[]} An array of property values.
+ */
+ values: (object) => isMapOrSet(object) ? array(object.values())
+ : object != null ? Object.values(object)
+ : [],
+
+ /**
+ * Returns an array of a given *object*'s own enumerable keyed property
+ * `[key, value]` pairs. If the *object* is a *Map* or *Set* instance, the
+ * *entries* method will be invoked directly on the object, otherwise
+ * *Object.entries* is used.
+ * @template K, V
+ * @param {Map|Set|Record} object The input
+ * object, Map, or Set value.
+ * @return {[K, V][]} An array of property values.
+ */
+ entries: (object) => isMapOrSet(object) ? array(object.entries())
+ : object != null ? Object.entries(object)
+ : [],
+
+ /**
+ * Returns a new object given iterable *entries* of `[key, value]` pairs.
+ * This method is Arquero's version of the *Object.fromEntries* method.
+ * @template K, V
+ * @param {Iterable<[K, V]>} entries An iterable collection of `[key, value]`
+ * pairs, such as an array of two-element arrays or a *Map*.
+ * @return {Record} An object of consolidated key-value pairs.
+ */
+ object: (entries) => entries ? Object.fromEntries(entries) : NULL
};
diff --git a/src/op/functions/recode.js b/src/op/functions/recode.js
index 5cfc13e..34b9b5e 100644
--- a/src/op/functions/recode.js
+++ b/src/op/functions/recode.js
@@ -5,20 +5,22 @@ import has from '../../util/has.js';
* value map. If a fallback value is specified, it will be returned when
* a matching value is not found in the map; otherwise, the input value
* is returned unchanged.
- * @param {*} value The value to recode. The value must be safely
+ * @template T
+ * @param {T} value The value to recode. The value must be safely
* coercible to a string for lookup against the value map.
- * @param {object|Map} map An object or Map with input values for keys and
- * output recoded values as values. If a non-Map object, only the object's
- * own properties will be considered.
- * @param {*} [fallback] A default fallback value to use if the input
+ * @param {Map|Record} map An object or Map with input values
+ * for keys and output recoded values as values. If a non-Map object, only
+ * the object's own properties will be considered.
+ * @param {T} [fallback] A default fallback value to use if the input
* value is not found in the value map.
- * @return {*} The recoded value.
+ * @return {T} The recoded value.
*/
export default function(value, map, fallback) {
if (map instanceof Map) {
if (map.has(value)) return map.get(value);
- } else if (has(map, value)) {
- return map[value];
+ } else {
+ const key = `${value}`;
+ if (has(map, key)) return map[key];
}
return fallback !== undefined ? fallback : value;
}
diff --git a/src/op/functions/string.js b/src/op/functions/string.js
index 06a7268..581f795 100644
--- a/src/op/functions/string.js
+++ b/src/op/functions/string.js
@@ -1,36 +1,222 @@
export default {
- parse_date: (str) => str == null ? str : new Date(str),
- parse_float: (str) => str == null ? str : Number.parseFloat(str),
- parse_int: (str, radix) => str == null ? str : Number.parseInt(str, radix),
- endswith: (str, search, length) => str == null ? false
- : String(str).endsWith(search, length),
- match: (str, regexp, index) => {
- const m = str == null ? str : String(str).match(regexp);
- return index == null || m == null ? m
- : typeof index === 'number' ? m[index]
- : m.groups ? m.groups[index]
- : null;
- },
- normalize: (str, form) => str == null ? str
- : String(str).normalize(form),
- padend: (str, len, fill) => str == null ? str
- : String(str).padEnd(len, fill),
- padstart: (str, len, fill) => str == null ? str
- : String(str).padStart(len, fill),
- upper: (str) => str == null ? str
- : String(str).toUpperCase(),
- lower: (str) => str == null ? str
- : String(str).toLowerCase(),
- repeat: (str, num) => str == null ? str
- : String(str).repeat(num),
- replace: (str, pattern, replacement) => str == null ? str
- : String(str).replace(pattern, String(replacement)),
- substring: (str, start, end) => str == null ? str
- : String(str).substring(start, end),
- split: (str, separator, limit) => str == null ? []
- : String(str).split(separator, limit),
- startswith: (str, search, length) => str == null ? false
- : String(str).startsWith(search, length),
- trim: (str) => str == null ? str
- : String(str).trim()
+ /**
+ * Parses a string *value* and returns a Date instance. Beware: this method
+ * uses JavaScript's *Date.parse()* functionality, which is inconsistently
+ * implemented across browsers. That said,
+ * [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) formatted strings such
+ * as those produced by *op.format_date* and *op.format_utcdate* should be
+ * supported across platforms. Note that "bare" ISO date strings such as
+ * `"2001-01-01"` are interpreted by JavaScript as indicating midnight of
+ * that day in Coordinated Universal Time (UTC), *not* local time. To
+ * indicate the local timezone, an ISO string can include additional time
+ * components and no `Z` suffix: `"2001-01-01T00:00"`.
+ * @param {*} value The input value.
+ * @return {Date} The parsed date value.
+ */
+ parse_date: (value) => value == null ? value : new Date(value),
+
+ /**
+ * Parses a string *value* and returns a floating point number.
+ * @param {*} value The input value.
+ * @return {number} The parsed number value.
+ */
+ parse_float: (value) => value == null ? value : Number.parseFloat(value),
+
+ /**
+ * Parses a string *value* and returns an integer of the specified radix
+ * (the base in mathematical numeral systems).
+ * @param {*} value The input value.
+ * @param {number} [radix] An integer between 2 and 36 that represents the
+ * radix (the base in mathematical numeral systems) of the string. Be
+ * careful: this does not default to 10! If *radix* is `undefined`, `0`,
+ * or unspecified, JavaScript assumes the following: If the input string
+ * begins with `"0x"` or `"0X"` (a zero, followed by lowercase or
+ * uppercase X), the radix is assumed to be 16 and the rest of the string
+ * is parsed as a hexidecimal number. If the input string begins with `"0"`
+ * (a zero), the radix is assumed to be 8 (octal) or 10 (decimal). Exactly
+ * which radix is chosen is implementation-dependent. If the input string
+ * begins with any other value, the radix is 10 (decimal).
+ * @return {number} The parsed integer value.
+ */
+ parse_int: (value, radix) => value == null ? value
+ : Number.parseInt(value, radix),
+
+ /**
+ * Determines whether a string *value* ends with the characters of a
+ * specified *search* string, returning `true` or `false` as appropriate.
+ * @param {any} value The input string value.
+ * @param {string} search The search string to test for.
+ * @param {number} [length] If provided, used as the length of *value*
+ * (default `value.length`).
+ * @return {boolean} True if the value ends with the search string,
+ * false otherwise.
+ */
+ endswith: (value, search, length) => value == null ? false
+ : String(value).endsWith(search, length),
+
+ /**
+ * Retrieves the result of matching a string *value* against a regular
+ * expression *regexp*. If no *index* is specified, returns an array
+ * whose contents depend on the presence or absence of the regular
+ * expression global (`g`) flag, or `null` if no matches are found. If the
+ * `g` flag is used, all results matching the complete regular expression
+ * will be returned, but capturing groups will not. If the `g` flag is not
+ * used, only the first complete match and its related capturing groups are
+ * returned.
+ *
+ * If specified, the *index* looks up a value of the resulting match. If
+ * *index* is a number, the corresponding index of the result array is
+ * returned. If *index* is a string, the value of the corresponding
+ * named capture group is returned, or `null` if there is no such group.
+ * @param {*} value The input string value.
+ * @param {*} regexp The regular expression to match against.
+ * @param {number|string} index The index into the match result array
+ * or capture group.
+ * @return {string|string[]} The match result.
+ */
+ match: (value, regexp, index) => {
+ const m = value == null ? value : String(value).match(regexp);
+ return index == null || m == null ? m
+ : typeof index === 'number' ? m[index]
+ : m.groups ? m.groups[index]
+ : null;
+ },
+
+ /**
+ * Returns the Unicode normalization form of the string *value*.
+ * @param {*} value The input value to normalize.
+ * @param {string} form The Unicode normalization form, one of
+ * `'NFC'` (default, canonical decomposition, followed by canonical
+ * composition), `'NFD'` (canonical decomposition), `'NFKC'` (compatibility
+ * decomposition, followed by canonical composition),
+ * or `'NFKD'` (compatibility decomposition).
+ * @return {string} The normalized string value.
+ */
+ normalize: (value, form) => value == null ? value
+ : String(value).normalize(form),
+
+ /**
+ * Pad a string *value* with a given *fill* string (applied from the end of
+ * *value* and repeated, if needed) so that the resulting string reaches a
+ * given *length*.
+ * @param {*} value The input value to pad.
+ * @param {number} length The length of the resulting string once the
+ * *value* string has been padded. If the length is lower than
+ * `value.length`, the *value* string will be returned as-is.
+ * @param {string} [fill] The string to pad the *value* string with
+ * (default `''`). If *fill* is too long to stay within the target
+ * *length*, it will be truncated: for left-to-right languages the
+ * left-most part and for right-to-left languages the right-most will
+ * be applied.
+ * @return {string} The padded string.
+ */
+ padend: (value, length, fill) => value == null ? value
+ : String(value).padEnd(length, fill),
+
+ /**
+ * Pad a string *value* with a given *fill* string (applied from the start
+ * of *value* and repeated, if needed) so that the resulting string reaches
+ * a given *length*.
+ * @param {*} value The input value to pad.
+ * @param {number} length The length of the resulting string once the
+ * *value* string has been padded. If the length is lower than
+ * `value.length`, the *value* string will be returned as-is.
+ * @param {string} [fill] The string to pad the *value* string with
+ * (default `''`). If *fill* is too long to stay within the target
+ * *length*, it will be truncated: for left-to-right languages the
+ * left-most part and for right-to-left languages the right-most will
+ * be applied.
+ * @return {string} The padded string.
+ */
+ padstart: (value, length, fill) => value == null ? value
+ : String(value).padStart(length, fill),
+
+ /**
+ * Returns the string *value* converted to upper case.
+ * @param {*} value The input string value.
+ * @return {string} The upper case string.
+ */
+ upper: (value) => value == null ? value : String(value).toUpperCase(),
+
+ /**
+ * Returns the string *value* converted to lower case.
+ * @param {*} value The input string value.
+ * @return {string} The lower case string.
+ */
+ lower: (value) => value == null ? value : String(value).toLowerCase(),
+
+ /**
+ * Returns a new string which contains the specified *number* of copies of
+ * the *value* string concatenated together.
+ * @param {*} value The input string to repeat.
+ * @param {*} number An integer between `0` and `+Infinity`, indicating the
+ * number of times to repeat the string.
+ * @return {string} The repeated string.
+ */
+ repeat: (value, number) => value == null ? value
+ : String(value).repeat(number),
+
+ /**
+ * Returns a new string with some or all matches of a *pattern* replaced by
+ * a *replacement*. The *pattern* can be a string or a regular expression,
+ * and the *replacement* must be a string. If *pattern* is a string, only
+ * the first occurrence will be replaced; to make multiple replacements, use
+ * a regular expression *pattern* with a `g` (global) flag.
+ * @param {*} value The input string value.
+ * @param {*} pattern The pattern string or regular expression to replace.
+ * @param {*} replacement The replacement string to use.
+ * @return {string} The string with patterns replaced.
+ */
+ replace: (value, pattern, replacement) => value == null ? value
+ : String(value).replace(pattern, String(replacement)),
+
+ /**
+ * Divides a string *value* into an ordered list of substrings based on a
+ * *separator* pattern, puts these substrings into an array, and returns the
+ * array.
+ * @param {*} value The input string value.
+ * @param {*} separator A string or regular expression pattern describing
+ * where each split should occur.
+ * @param {number} [limit] An integer specifying a limit on the number of
+ * substrings to be included in the array.
+ * @return {string[]}
+ */
+ split: (value, separator, limit) => value == null ? []
+ : String(value).split(separator, limit),
+
+ /**
+ * Determines whether a string *value* starts with the characters of a
+ * specified *search* string, returning `true` or `false` as appropriate.
+ * @param {*} value The input string value.
+ * @param {string} search The search string to test for.
+ * @param {number} [position=0] The position in the *value* string at which
+ * to begin searching (default `0`).
+ * @return {boolean} True if the string starts with the search pattern,
+ * false otherwise.
+ */
+ startswith: (value, search, position) => value == null ? false
+ : String(value).startsWith(search, position),
+
+ /**
+ * Returns the part of the string *value* between the *start* and *end*
+ * indexes, or to the end of the string.
+ * @param {*} value The input string value.
+ * @param {number} [start=0] The index of the first character to include in
+ * the returned substring (default `0`).
+ * @param {number} [end] The index of the first character to exclude from
+ * the returned substring (default `value.length`).
+ * @return {string} The substring.
+ */
+ substring: (value, start, end) => value == null ? value
+ : String(value).substring(start, end),
+
+ /**
+ * Returns a new string with whitespace removed from both ends of the input
+ * *value* string. Whitespace in this context is all the whitespace
+ * characters (space, tab, no-break space, etc.) and all the line terminator
+ * characters (LF, CR, etc.).
+ * @param {*} value The input string value to trim.
+ * @return {string} The trimmed string.
+ */
+ trim: (value) => value == null ? value : String(value).trim()
};
diff --git a/src/op/op-api.js b/src/op/op-api.js
index 1a01734..534846b 100644
--- a/src/op/op-api.js
+++ b/src/op/op-api.js
@@ -52,59 +52,65 @@ export default {
* Generate an object representing the current table row.
* @param {...string} names The column names to include in the object.
* If unspecified, all columns are included.
- * @return {Op|Struct} The generated row object.
+ * @return {Struct} The generated row object.
*/
row_object: (...names) => op('row_object', null, names.flat()),
/**
* Aggregate function to count the number of records (rows).
- * @returns {Op|number} The count of records.
+ * @returns {number} The count of records.
*/
count,
/**
* Aggregate function returning an arbitrary observed value.
- * @param {*} field The data field.
- * @return {*} An arbitrary observed value.
+ * @template T
+ * @param {T} field The data field.
+ * @return {T} An arbitrary observed value.
*/
any,
/**
* Aggregate function to collect an array of values.
- * @param {*} field The data field.
- * @return {Op|Array} A list of values.
+ * @template T
+ * @param {T} field The data field.
+ * @return {Array} A list of values.
*/
array_agg,
/**
* Aggregate function to collect an array of distinct (unique) values.
- * @param {*} field The data field.
- * @return {Op|Array} An array of unique values.
+ * @template T
+ * @param {T} field The data field.
+ * @return {Array} An array of unique values.
*/
array_agg_distinct,
/**
* Aggregate function to create an object given input key and value fields.
- * @param {*} key The object key field.
- * @param {*} value The object value field.
- * @return {Op|Struct} An object of key-value pairs.
+ * @template K, V
+ * @param {K} key The object key field.
+ * @param {V} value The object value field.
+ * @return {Record} An object of key-value pairs.
*/
object_agg,
/**
* Aggregate function to create a Map given input key and value fields.
- * @param {*} key The object key field.
- * @param {*} value The object value field.
- * @return {Op|Map} A Map of key-value pairs.
+ * @template K, V
+ * @param {K} key The object key field.
+ * @param {V} value The object value field.
+ * @return {Map} A Map of key-value pairs.
*/
map_agg,
/**
* Aggregate function to create an array in the style of Object.entries()
* given input key and value fields.
- * @param {*} key The object key field.
- * @param {*} value The object value field.
- * @return {Op|[[any, any]]} An array of [key, value] arrays.
+ * @template K, V
+ * @param {K} key The object key field.
+ * @param {V} value The object value field.
+ * @return {[K, V][]} An array of [key, value] arrays.
*/
entries_agg,
@@ -112,100 +118,117 @@ export default {
* Aggregate function to count the number of valid values.
* Invalid values are null, undefined, or NaN.
* @param {*} field The data field.
- * @return {Op|number} The count of valid values.
+ * @return {number} The count of valid values.
*/
+ // @ts-ignore
valid: (field) => op('valid', field),
/**
* Aggregate function to count the number of invalid values.
* Invalid values are null, undefined, or NaN.
* @param {*} field The data field.
- * @return {Op|number} The count of invalid values.
+ * @return {number} The count of invalid values.
*/
+ // @ts-ignore
invalid: (field) => op('invalid', field),
/**
* Aggregate function to count the number of distinct values.
* @param {*} field The data field.
- * @return {Op|number} The count of distinct values.
+ * @return {number} The count of distinct values.
*/
+ // @ts-ignore
distinct: (field) => op('distinct', field),
/**
* Aggregate function to determine the mode (most frequent) value.
- * @param {*} field The data field.
- * @return {Op|number} The mode value.
+ * @template T
+ * @param {T} field The data field.
+ * @return {T} The mode value.
*/
+ // @ts-ignore
mode: (field) => op('mode', field),
/**
* Aggregate function to sum values.
- * @param {string} field The data field.
- * @return {Op|number} The sum of the values.
+ * @param {*} field The data field.
+ * @return {number} The sum of the values.
*/
+ // @ts-ignore
sum: (field) => op('sum', field),
/**
* Aggregate function to multiply values.
* @param {*} field The data field.
- * @return {Op|number} The product of the values.
+ * @return {number} The product of the values.
*/
+ // @ts-ignore
product: (field) => op('product', field),
/**
* Aggregate function for the mean (average) value.
* @param {*} field The data field.
- * @return {Op|number} The mean (average) of the values.
+ * @return {number} The mean (average) of the values.
*/
+ // @ts-ignore
mean: (field) => op('mean', field),
/**
* Aggregate function for the average (mean) value.
* @param {*} field The data field.
- * @return {Op|number} The average (mean) of the values.
+ * @return {number} The average (mean) of the values.
*/
+ // @ts-ignore
average: (field) => op('average', field),
/**
* Aggregate function for the sample variance.
* @param {*} field The data field.
- * @return {Op|number} The sample variance of the values.
+ * @return {number} The sample variance of the values.
*/
+ // @ts-ignore
variance: (field) => op('variance', field),
/**
* Aggregate function for the population variance.
* @param {*} field The data field.
- * @return {Op|number} The population variance of the values.
+ * @return {number} The population variance of the values.
*/
+ // @ts-ignore
variancep: (field) => op('variancep', field),
/**
* Aggregate function for the sample standard deviation.
* @param {*} field The data field.
- * @return {Op|number} The sample standard deviation of the values.
+ * @return {number} The sample standard deviation of the values.
*/
+ // @ts-ignore
stdev: (field) => op('stdev', field),
/**
* Aggregate function for the population standard deviation.
* @param {*} field The data field.
- * @return {Op|number} The population standard deviation of the values.
+ * @return {number} The population standard deviation of the values.
*/
+ // @ts-ignore
stdevp: (field) => op('stdevp', field),
/**
* Aggregate function for the minimum value.
- * @param {*} field The data field.
- * @return {Op|number} The minimum value.
+ * @template T
+ * @param {T} field The data field.
+ * @return {T} The minimum value.
*/
+ // @ts-ignore
min: (field) => op('min', field),
/**
* Aggregate function for the maximum value.
- * @param {*} field The data field.
- * @return {Op|number} The maximum value.
+ * @template T
+ * @param {T} field The data field.
+ * @return {T} The maximum value.
*/
+ // @ts-ignore
max: (field) => op('max', field),
/**
@@ -213,32 +236,36 @@ export default {
* of a data field for a probability threshold.
* @param {*} field The data field.
* @param {number} p The probability threshold.
- * @return {Op|number} The quantile value.
+ * @return {number} The quantile value.
*/
+ // @ts-ignore
quantile: (field, p) => op('quantile', field, p),
/**
* Aggregate function for the median value.
* This is a shorthand for the 0.5 quantile value.
* @param {*} field The data field.
- * @return {Op|number} The median value.
+ * @return {number} The median value.
*/
+ // @ts-ignore
median: (field) => op('median', field),
/**
* Aggregate function for the sample covariance between two variables.
* @param {*} field1 The first data field.
* @param {*} field2 The second data field.
- * @return {Op|number} The sample covariance of the values.
+ * @return {number} The sample covariance of the values.
*/
+ // @ts-ignore
covariance: (field1, field2) => op('covariance', [field1, field2]),
/**
* Aggregate function for the population covariance between two variables.
* @param {*} field1 The first data field.
* @param {*} field2 The second data field.
- * @return {Op|number} The population covariance of the values.
+ * @return {number} The population covariance of the values.
*/
+ // @ts-ignore
covariancep: (field1, field2) => op('covariancep', [field1, field2]),
/**
@@ -247,8 +274,9 @@ export default {
* variable and then apply this function to the result.
* @param {*} field1 The first data field.
* @param {*} field2 The second data field.
- * @return {Op|number} The correlation between the field values.
+ * @return {number} The correlation between the field values.
*/
+ // @ts-ignore
corr: (field1, field2) => op('corr', [field1, field2]),
/**
@@ -261,15 +289,20 @@ export default {
* @param {number} [minstep] The minimum allowed step size between bins.
* @param {number} [step] The exact step size to use between bins.
* If specified, the maxbins and minstep arguments are ignored.
- * @return {Op|[number, number, number]} The bin [min, max, and step] values.
+ * @return {[number, number, number]} The bin [min, max, and step] values.
*/
- bins: (field, maxbins, nice, minstep, step) =>
- op('bins', field, [maxbins, nice, minstep, step]),
+ // @ts-ignore
+ bins: (field, maxbins, nice, minstep, step) => op(
+ 'bins',
+ field,
+ [maxbins, nice, minstep, step]
+ ),
/**
* Window function to assign consecutive row numbers, starting from 1.
- * @return {Op|number} The row number value.
+ * @return {number} The row number value.
*/
+ // @ts-ignore
row_number: () => op('row_number'),
/**
@@ -277,16 +310,18 @@ export default {
* from 1. Peer values are assigned the same rank. Subsequent ranks
* reflect the number of prior values: if the first two values tie for
* rank 1, the third value is assigned rank 3.
- * @return {Op|number} The rank value.
+ * @return {number} The rank value.
*/
+ // @ts-ignore
rank: () => op('rank'),
/**
* Window function to assign a fractional (average) rank to each value in
* a group, starting from 1. Peer values are assigned the average of their
* indices: if the first two values tie, both will be assigned rank 1.5.
- * @return {Op|number} The peer-averaged rank value.
+ * @return {number} The peer-averaged rank value.
*/
+ // @ts-ignore
avg_rank: () => op('avg_rank'),
/**
@@ -294,22 +329,25 @@ export default {
* starting from 1. Peer values are assigned the same rank. Subsequent
* ranks do not reflect the number of prior values: if the first two
* values tie for rank 1, the third value is assigned rank 2.
- * @return {Op|number} The dense rank value.
+ * @return {number} The dense rank value.
*/
+ // @ts-ignore
dense_rank: () => op('dense_rank'),
/**
* Window function to assign a percentage rank to each value in a group.
* The percent is calculated as (rank - 1) / (group_size - 1).
- * @return {Op|number} The percentage rank value.
+ * @return {number} The percentage rank value.
*/
+ // @ts-ignore
percent_rank: () => op('percent_rank'),
/**
* Window function to assign a cumulative distribution value between 0 and 1
* to each value in a group.
- * @return {Op|number} The cumulative distribution value.
+ * @return {number} The cumulative distribution value.
*/
+ // @ts-ignore
cume_dist: () => op('cume_dist'),
/**
@@ -317,70 +355,85 @@ export default {
* value in a group. Accepts an integer parameter indicating the number of
* buckets to use (e.g., 100 for percentiles, 5 for quintiles).
* @param {number} num The number of buckets for ntile calculation.
- * @return {*} The quantile value.
+ * @return {number} The quantile value.
*/
+ // @ts-ignore
ntile: (num) => op('ntile', null, num),
/**
* Window function to assign a value that precedes the current value by
* a specified number of positions. If no such value exists, returns a
* default value instead.
- * @param {*} field The data field.
+ * @template T
+ * @param {T} field The data field.
* @param {number} [offset=1] The lag offset from the current value.
- * @param {*} [defaultValue=undefined] The default value.
- * @return {*} The lagging value.
+ * @param {T} [defaultValue=undefined] The default value.
+ * @return {T} The lagging value.
*/
+ // @ts-ignore
lag: (field, offset, defaultValue) => op('lag', field, [offset, defaultValue]),
/**
* Window function to assign a value that follows the current value by
* a specified number of positions. If no such value exists, returns a
* default value instead.
- * @param {*} field The data field.
+ * @template T
+ * @param {T} field The data field.
* @param {number} [offset=1] The lead offset from the current value.
- * @param {*} [defaultValue=undefined] The default value.
- * @return {*} The leading value.
+ * @param {T} [defaultValue=undefined] The default value.
+ * @return {T} The leading value.
*/
+ // @ts-ignore
lead: (field, offset, defaultValue) => op('lead', field, [offset, defaultValue]),
/**
* Window function to assign the first value in a sliding window frame.
- * @param {*} field The data field.
- * @return {*} The first value in the current frame.
+ * @template T
+ * @param {T} field The data field.
+ * @return {T} The first value in the current frame.
*/
+ // @ts-ignore
first_value: (field) => op('first_value', field),
/**
* Window function to assign the last value in a sliding window frame.
- * @param {*} field The data field.
- * @return {*} The last value in the current frame.
+ * @template T
+ * @param {T} field The data field.
+ * @return {T} The last value in the current frame.
*/
+ // @ts-ignore
last_value: (field) => op('last_value', field),
/**
* Window function to assign the nth value in a sliding window frame
* (counting from 1), or undefined if no such value exists.
- * @param {*} field The data field.
+ * @template T
+ * @param {T} field The data field.
* @param {number} nth The nth position, starting from 1.
- * @return {*} The nth value in the current frame.
+ * @return {T} The nth value in the current frame.
*/
+ // @ts-ignore
nth_value: (field, nth) => op('nth_value', field, nth),
/**
* Window function to fill in missing values with preceding values.
- * @param {*} field The data field.
- * @param {*} [defaultValue=undefined] The default value.
- * @return {*} The current value if valid, otherwise the first preceding
+ * @template T
+ * @param {T} field The data field.
+ * @param {T} [defaultValue=undefined] The default value.
+ * @return {T} The current value if valid, otherwise the first preceding
* valid value. If no such value exists, returns the default value.
*/
+ // @ts-ignore
fill_down: (field, defaultValue) => op('fill_down', field, defaultValue),
/**
* Window function to fill in missing values with subsequent values.
- * @param {*} field The data field.
- * @param {*} [defaultValue=undefined] The default value.
- * @return {*} The current value if valid, otherwise the first subsequent
+ * @template T
+ * @param {T} field The data field.
+ * @param {T} [defaultValue=undefined] The default value.
+ * @return {T} The current value if valid, otherwise the first subsequent
* valid value. If no such value exists, returns the default value.
*/
+ // @ts-ignore
fill_up: (field, defaultValue) => op('fill_up', field, defaultValue)
};
diff --git a/src/table/ColumnTable.js b/src/table/ColumnTable.js
index e21d9cf..4332065 100644
--- a/src/table/ColumnTable.js
+++ b/src/table/ColumnTable.js
@@ -1,5 +1,4 @@
import { Table } from './Table.js';
-import toArray from '../util/to-array.js';
import {
antijoin,
assign,
@@ -38,18 +37,19 @@ import toCSV from '../format/to-csv.js';
import toHTML from '../format/to-html.js';
import toJSON from '../format/to-json.js';
import toMarkdown from '../format/to-markdown.js';
+import toArray from '../util/to-array.js';
/**
* A data table with transformation verbs.
*/
export class ColumnTable extends Table {
-
/**
* Create a new table with additional columns drawn from one or more input
* tables. All tables must have the same numer of rows and are reified
* prior to assignment. In the case of repeated column names, input table
* columns overwrite existing columns.
- * @param {...Table} tables The tables to merge with this table.
+ * @param {...(Table|import('./types.js').ColumnData)} tables
+ * The tables to merge with this table.
* @return {this} A new table with merged columns.
* @example table.assign(table1, table2)
*/
@@ -395,7 +395,7 @@ export class ColumnTable extends Table {
* @return {this} A new pivoted table.
* @example table.pivot('key', 'value')
* @example table.pivot(['keyA', 'keyB'], ['valueA', 'valueB'])
- * @example table.pivot({ key: d => d.key }, { value: d => sum(d.value) })
+ * @example table.pivot({ key: d => d.key }, { value: d => op.sum(d.value) })
*/
pivot(keys, values, options) {
return pivot(this, keys, values, options);
@@ -412,7 +412,7 @@ export class ColumnTable extends Table {
* @param {import('./types.js').SpreadOptions } [options]
* Options for spreading.
* @return {this} A new table with the spread columns added.
- * @example table.spread({ a: split(d.text, '') })
+ * @example table.spread({ a: d => op.split(d.text, '') })
* @example table.spread('arrayCol', { limit: 100 })
*/
spread(values, options) {
@@ -501,7 +501,7 @@ export class ColumnTable extends Table {
* Options for the join.
* @return {this} A new joined table.
* @example table.join(other, ['keyL', 'keyR'])
- * @example table.join(other, (a, b) => equal(a.keyL, b.keyR))
+ * @example table.join(other, (a, b) => op.equal(a.keyL, b.keyR))
*/
join(other, on, values, options) {
return join(this, other, on, values, options);
@@ -542,7 +542,7 @@ export class ColumnTable extends Table {
* overridden with `{left: true, right: false}`.
* @return {this} A new joined table.
* @example table.join_left(other, ['keyL', 'keyR'])
- * @example table.join_left(other, (a, b) => equal(a.keyL, b.keyR))
+ * @example table.join_left(other, (a, b) => op.equal(a.keyL, b.keyR))
*/
join_left(other, on, values, options) {
const opt = { ...options, left: true, right: false };
@@ -584,7 +584,7 @@ export class ColumnTable extends Table {
* with `{left: false, right: true}`.
* @return {this} A new joined table.
* @example table.join_right(other, ['keyL', 'keyR'])
- * @example table.join_right(other, (a, b) => equal(a.keyL, b.keyR))
+ * @example table.join_right(other, (a, b) => op.equal(a.keyL, b.keyR))
*/
join_right(other, on, values, options) {
const opt = { ...options, left: false, right: true };
@@ -626,7 +626,7 @@ export class ColumnTable extends Table {
* with `{left: true, right: true}`.
* @return {this} A new joined table.
* @example table.join_full(other, ['keyL', 'keyR'])
- * @example table.join_full(other, (a, b) => equal(a.keyL, b.keyR))
+ * @example table.join_full(other, (a, b) => op.equal(a.keyL, b.keyR))
*/
join_full(other, on, values, options) {
const opt = { ...options, left: true, right: true };
@@ -683,7 +683,7 @@ export class ColumnTable extends Table {
* @return {this} A new filtered table.
* @example table.semijoin(other)
* @example table.semijoin(other, ['keyL', 'keyR'])
- * @example table.semijoin(other, (a, b) => equal(a.keyL, b.keyR))
+ * @example table.semijoin(other, (a, b) => op.equal(a.keyL, b.keyR))
*/
semijoin(other, on) {
return semijoin(this, other, on);
@@ -710,7 +710,7 @@ export class ColumnTable extends Table {
* @return {this} A new filtered table.
* @example table.antijoin(other)
* @example table.antijoin(other, ['keyL', 'keyR'])
- * @example table.antijoin(other, (a, b) => equal(a.keyL, b.keyR))
+ * @example table.antijoin(other, (a, b) => op.equal(a.keyL, b.keyR))
*/
antijoin(other, on) {
return antijoin(this, other, on);
@@ -722,7 +722,7 @@ export class ColumnTable extends Table {
* Concatenate multiple tables into a single table, preserving all rows.
* This transformation mirrors the UNION_ALL operation in SQL.
* Only named columns in this table are included in the output.
- * @param {...import('./types.js').TableRef} tables
+ * @param {...import('./types.js').TableRefList} tables
* A list of tables to concatenate.
* @return {this} A new concatenated table.
* @example table.concat(other)
@@ -739,7 +739,7 @@ export class ColumnTable extends Table {
* similar to *concat* but suppresses duplicate rows with
* values identical to another row.
* Only named columns in this table are included in the output.
- * @param {...import('./types.js').TableRef} tables
+ * @param {...import('./types.js').TableRefList} tables
* A list of tables to union.
* @return {this} A new unioned table.
* @example table.union(other)
@@ -755,7 +755,7 @@ export class ColumnTable extends Table {
* values for all columns in all tables, and deduplicates the rows.
* This transformation is similar to a series of *semijoin*.
* calls, but additionally suppresses duplicate rows.
- * @param {...import('./types.js').TableRef} tables
+ * @param {...import('./types.js').TableRefList} tables
* A list of tables to intersect.
* @return {this} A new filtered table.
* @example table.intersect(other)
@@ -771,7 +771,7 @@ export class ColumnTable extends Table {
* this table that whose values do not occur in the other tables.
* This transformation is similar to a series of *anitjoin*
* calls, but additionally suppresses duplicate rows.
- * @param {...import('./types.js').TableRef} tables
+ * @param {...import('./types.js').TableRefList} tables
* A list of tables to difference.
* @return {this} A new filtered table.
* @example table.except(other)
diff --git a/src/table/Table.js b/src/table/Table.js
index 21d7449..734f8e1 100644
--- a/src/table/Table.js
+++ b/src/table/Table.js
@@ -3,15 +3,14 @@ import { rowObjectBuilder } from '../expression/row-object.js';
import resolve, { all } from '../helpers/selection.js';
import arrayType from '../util/array-type.js';
import error from '../util/error.js';
+import isArrayType from '../util/is-array-type.js';
import isNumber from '../util/is-number.js';
import repeat from '../util/repeat.js';
-import isArrayType from '../util/is-array-type.js';
/**
* Base class representing a column-oriented data table.
*/
export class Table {
-
/**
* Instantiate a Table instance.
* @param {import('./types.js').ColumnData} columns
@@ -31,25 +30,55 @@ export class Table {
const data = Object.freeze({ ...columns });
names = names?.slice() ?? Object.keys(data);
const nrows = names.length ? data[names[0]].length : 0;
- /** @private */
+ /**
+ * @private
+ * @type {readonly string[]}
+ */
this._names = Object.freeze(names);
- /** @private */
+ /**
+ * @private
+ * @type {import('./types.js').ColumnData}
+ */
this._data = data;
- /** @private */
+ /**
+ * @private
+ * @type {number}
+ */
this._total = nrows;
- /** @private */
+ /**
+ * @private
+ * @type {number}
+ */
this._nrows = filter?.count() ?? nrows;
- /** @private */
+ /**
+ * @private
+ * @type {import('./BitSet.js').BitSet}
+ */
this._mask = filter ?? null;
- /** @private */
+ /**
+ * @private
+ * @type {import('./types.js').GroupBySpec}
+ */
this._group = group ?? null;
- /** @private */
+ /**
+ * @private
+ * @type {import('./types.js').RowComparator}
+ */
this._order = order ?? null;
- /** @private */
+ /**
+ * @private
+ * @type {import('./types.js').Params}
+ */
this._params = params;
- /** @private */
+ /**
+ * @private
+ * @type {Uint32Array}
+ */
this._index = null;
- /** @private */
+ /**
+ * @private
+ * @type {number[][] | Uint32Array[]}
+ */
this._partitions = null;
}
@@ -429,9 +458,8 @@ export class Table {
// sort index vector
if (order && ordered) {
- const compare = this._order;
- const data = this._data;
- index.sort((a, b) => compare(a, b, data));
+ const { _order, _data } = this;
+ index.sort((a, b) => _order(a, b, _data));
}
// save indices if they reflect table metadata
diff --git a/src/table/types.ts b/src/table/types.ts
index 3e548c5..17dfba7 100644
--- a/src/table/types.ts
+++ b/src/table/types.ts
@@ -209,6 +209,9 @@ export type ExprList = ListEntry | ListEntry[];
/** A reference to a data table instance. */
export type TableRef = Table | string;
+/** A list of one or more table references. */
+export type TableRefList = TableRef | TableRef[];
+
/**
* One or more orderby sort criteria.
* If a string, order by the column with that name.
@@ -228,7 +231,11 @@ export type OrderKeys = OrderKey | OrderKey[];
export type JoinKey = ColumnRef | TableExprFunc;
/** An ordered set of join keys. */
-export type JoinKeys = JoinKey | [JoinKey[]] | [JoinKey[], JoinKey[]];
+export type JoinKeys =
+ | JoinKey
+ | [JoinKey[]]
+ | [JoinKey, JoinKey]
+ | [JoinKey[], JoinKey[]];
/** A predicate specification for joining two tables. */
export type JoinPredicate = JoinKeys | TableExprFunc2 | null;
diff --git a/src/util/array-type.js b/src/util/array-type.js
index fc54efd..e7dadf1 100644
--- a/src/util/array-type.js
+++ b/src/util/array-type.js
@@ -1,5 +1,10 @@
import isTypedArray from './is-typed-array.js';
+/**
+ * @param {*} column
+ * @returns {ArrayConstructor | import('../table/types.js').TypedArrayConstructor}
+ */
export default function(column) {
+ // @ts-ignore
return isTypedArray(column) ? column.constructor : Array;
}
diff --git a/src/util/is-array-type.js b/src/util/is-array-type.js
index a841057..5fa76dd 100644
--- a/src/util/is-array-type.js
+++ b/src/util/is-array-type.js
@@ -1,6 +1,10 @@
import isArray from './is-array.js';
import isTypedArray from './is-typed-array.js';
+/**
+ * @param {*} value
+ * @return {value is (any[] | import('../table/types.js').TypedArray)}
+ */
export default function isArrayType(value) {
return isArray(value) || isTypedArray(value);
}
diff --git a/src/util/is-map-or-set.js b/src/util/is-map-or-set.js
index 49b42e1..b29edb8 100644
--- a/src/util/is-map-or-set.js
+++ b/src/util/is-map-or-set.js
@@ -1,6 +1,10 @@
import isMap from './is-map.js';
import isSet from './is-set.js';
+/**
+ * @param {*} value
+ * @return {value is Map | Set}
+ */
export default function(value) {
return isMap(value) || isSet(value);
}
diff --git a/src/util/is-map.js b/src/util/is-map.js
index 7948024..c7be609 100644
--- a/src/util/is-map.js
+++ b/src/util/is-map.js
@@ -1,3 +1,7 @@
+/**
+ * @param {*} value
+ * @return {value is Map}
+ */
export default function(value) {
return value instanceof Map;
}
diff --git a/src/util/is-set.js b/src/util/is-set.js
index 8944f02..3b26a43 100644
--- a/src/util/is-set.js
+++ b/src/util/is-set.js
@@ -1,3 +1,7 @@
+/**
+ * @param {*} value
+ * @return {value is Set}
+ */
export default function(value) {
return value instanceof Set;
}
diff --git a/src/util/is-string.js b/src/util/is-string.js
index 653c8a5..7944070 100644
--- a/src/util/is-string.js
+++ b/src/util/is-string.js
@@ -1,3 +1,7 @@
+/**
+ * @param {*} value
+ * @return {value is String}
+ */
export default function(value) {
return typeof value === 'string';
}
diff --git a/src/util/is-typed-array.js b/src/util/is-typed-array.js
index 0228a8e..065ba90 100644
--- a/src/util/is-typed-array.js
+++ b/src/util/is-typed-array.js
@@ -1,5 +1,9 @@
const TypedArray = Object.getPrototypeOf(Int8Array);
+/**
+ * @param {*} value
+ * @return {value is import("../table/types.js").TypedArray}
+ */
export default function(value) {
return value instanceof TypedArray;
}
diff --git a/test/types-test.ts b/test/types-test.ts
new file mode 100644
index 0000000..b642255
--- /dev/null
+++ b/test/types-test.ts
@@ -0,0 +1,104 @@
+// Example code that should not cause any TypeScript errors
+import * as aq from '../src/api.js';
+const { op } = aq;
+
+const dt = aq.table({
+ x: [1, 2, 3],
+ y: ['a', 'b', 'c']
+});
+const other = aq.table({ u: [3, 2, 1 ] });
+const other2 = aq.table({ x: [4, 5, 6 ] });
+
+export const rt = dt
+ .antijoin(other)
+ .antijoin(other, ['keyL', 'keyR'])
+ .antijoin(other, (a, b) => op.equal(a.keyL, b.keyR))
+ .assign({ z: [4, 5, 6] }, other)
+ .concat(other)
+ .concat(other, other2)
+ .concat([other, other2])
+ .count()
+ .count({ as: 'foo' })
+ .cross(other)
+ .cross(other, [['leftKey', 'leftVal'], ['rightVal']])
+ .dedupe()
+ .dedupe('y')
+ .derive({
+ row1: op.row_number(),
+ lead1: op.lead('s'),
+ row2: () => op.row_number(),
+ lead2: (d: {s: string}) => op.lead(op.trim(d.s)),
+ z: (d: {x: number}) => (d.x - op.average(d.x)) / op.stdev(d.x),
+ avg: aq.rolling(
+ (d: {x: number}) => op.average(d.x),
+ [-5, 5]
+ ),
+ mix: (d: any) => d.x > 2 ? d.u : d.z
+ })
+ .except(other)
+ .except(other, other2)
+ .except([other, other2])
+ .filter((d: any) => d.x > 2 && d.s !== 'foo')
+ .filter((d: {x: number, s: string}) => d.x > 2 && d.s !== 'foo')
+ .fold('colA')
+ .fold(['colA', 'colB'], { as: ['k', 'v'] })
+ .groupby('y')
+ .ungroup()
+ .groupby({ g: 'y' })
+ .ungroup()
+ .impute({ v: () => 0 })
+ .impute({ v: (d: {v: number}) => op.mean(d.v) })
+ .impute({ v: () => 0 }, { expand: ['x', 'y'] })
+ .intersect(other)
+ .intersect(other, other2)
+ .intersect([other, other2])
+ .join(other, ['keyL', 'keyR'])
+ .join(other, (a, b) => op.equal(a.keyL, b.keyR))
+ .join_left(other, ['keyL', 'keyR'])
+ .join_left(other, (a, b) => op.equal(a.keyL, b.keyR))
+ .join_right(other, ['keyL', 'keyR'])
+ .join_right(other, (a, b) => op.equal(a.keyL, b.keyR))
+ .join_full(other, ['keyL', 'keyR'])
+ .join_full(other, (a, b) => op.equal(a.keyL, b.keyR))
+ .lookup(other, ['key1', 'key2'], 'value1', 'value2')
+ .orderby('x', aq.desc('u'))
+ .unorder()
+ .pivot('key', 'value')
+ .pivot(['keyA', 'keyB'], ['valueA', 'valueB'])
+ .pivot({ key: (d: any) => d.key }, { value: (d: any) => op.sum(d.value) })
+ .relocate(['x', 'y'], { after: 'z' })
+ .rename({ x: 'xx', y: 'yy' })
+ .rollup({
+ min1: op.min('x'),
+ max1: op.max('x'),
+ sum1: op.sum('x'),
+ mode1: op.mode('x'),
+ min2: (d: {x: number}) => op.min(d.x),
+ max2: (d: {s: string}) => op.max(d.s),
+ sum2: (d: {x: number}) => op.sum(d.x),
+ mode2: (d: {d: Date}) => op.mode(d.d),
+ mix: (d: {x: number, z: number}) => op.min(d.x) + op.sum(d.z)
+ })
+ .sample(100)
+ .sample(100, { replace: true })
+ .select('x')
+ .select({ x: 'xx' })
+ .select(aq.all(), aq.not('x'), aq.range(0, 5))
+ .semijoin(other)
+ .semijoin(other, ['keyL', 'keyR'])
+ .semijoin(other, (a, b) => op.equal(a.keyL, b.keyR))
+ .slice(1, -1)
+ .slice(2)
+ .spread({ a: (d: any) => op.split(d.y, '') })
+ .spread('arrayCol', { limit: 100 })
+ .union(other)
+ .union(other, other2)
+ .union([other, other2])
+ .unroll('arrayCol', { limit: 1000 });
+
+export const arrow : import('apache-arrow').Table = dt.toArrow();
+export const buf : Uint8Array = dt.toArrowIPC();
+export const csv : string = dt.toCSV({ delimiter: '\t' });
+export const json : string = dt.toJSON({ columns: ['x', 'y'] });
+export const html : string = dt.toHTML();
+export const md : string = dt.toMarkdown();