From f9d45f0f6277aab27542888483fa5105ba6a909a Mon Sep 17 00:00:00 2001 From: jheer Date: Tue, 9 Jul 2024 13:24:32 +0200 Subject: [PATCH] feat: Update types and comments. --- docs/api/op.md | 42 ++--- src/format/value.js | 1 + src/op/functions/array.js | 135 +++++++++++++-- src/op/functions/bin.js | 10 +- src/op/functions/date.js | 299 ++++++++++++++++++++++++++++++--- src/op/functions/json.js | 16 +- src/op/functions/math.js | 327 +++++++++++++++++++++++++++++++++---- src/op/functions/object.js | 76 +++++++-- src/op/functions/recode.js | 18 +- src/op/functions/string.js | 254 ++++++++++++++++++++++++---- src/op/op-api.js | 189 +++++++++++++-------- src/table/ColumnTable.js | 30 ++-- src/table/Table.js | 58 +++++-- src/table/types.ts | 9 +- src/util/array-type.js | 5 + src/util/is-array-type.js | 4 + src/util/is-map-or-set.js | 4 + src/util/is-map.js | 4 + src/util/is-set.js | 4 + src/util/is-string.js | 4 + src/util/is-typed-array.js | 4 + test/types-test.ts | 104 ++++++++++++ 22 files changed, 1340 insertions(+), 257 deletions(-) create mode 100644 test/types-test.ts diff --git a/docs/api/op.md b/docs/api/op.md index 2f5c8627..d2387605 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 b6974f44..a3a180c9 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 1033a9e7..14e3167e 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 c074e34a..7206343c 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 9b9057c7..1b7971b2 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 bd980eb6..d0e4e6ef 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 df891fad..b7064fff 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 e6d570fe..a00f7d7b 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 5cfc13e5..34b9b5eb 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 06a7268e..581f795f 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 1a01734f..534846b6 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 e21d9cf2..43320650 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 21d74490..734f8e15 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 3e548c52..17dfba77 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 fc54efd2..e7dadf13 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 a8410572..5fa76dda 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 49b42e10..b29edb82 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 7948024c..c7be6098 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 8944f024..3b26a434 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 653c8a56..7944070e 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 0228a8ef..065ba907 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 00000000..b6422555 --- /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();