shelving 1.34.0 → 1.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/db/Database.d.ts CHANGED
@@ -51,9 +51,14 @@ export declare class DataQuery<T extends Data = Data> extends Query<T> implement
51
51
  get resultsMap(): ResultsMap<T> | PromiseLike<ResultsMap<T>>;
52
52
  /**
53
53
  * Count the number of results of this set of documents.
54
- * @return Number of documents in the collection (possibly promised).
54
+ * @return Number of documents matching the query (possibly promised).
55
55
  */
56
56
  get count(): number | PromiseLike<number>;
57
+ /**
58
+ * Does at least one document exist for this query?
59
+ * @return `true` if a document exists or `false` otherwise (possibly promised).
60
+ */
61
+ get exists(): boolean | PromiseLike<boolean>;
57
62
  /**
58
63
  * Get an entry for the first document matched by this query or `undefined` if this query has no results.
59
64
  *
@@ -124,13 +129,11 @@ export declare class DataDocument<T extends Data = Data> implements Observable<R
124
129
  get optional(): DataQuery<T>;
125
130
  /**
126
131
  * Does this document exist?
127
- *
128
- * @return Document's data, or `undefined` if the document doesn't exist (possibly promised).
132
+ * @return `true` if a document exists or `false` otherwise (possibly promised).
129
133
  */
130
134
  get exists(): boolean | PromiseLike<boolean>;
131
135
  /**
132
136
  * Get the result of this document.
133
- *
134
137
  * @return Document's data, or `undefined` if the document doesn't exist (possibly promised).
135
138
  */
136
139
  get result(): Result<T> | PromiseLike<Result<T>>;
package/db/Database.js CHANGED
@@ -1,4 +1,4 @@
1
- import { callAsync, getFirstItem, throwAsync, validate, toMap, countItems, TransformObserver, NOT_NULLISH, } from "../util/index.js";
1
+ import { callAsync, getFirstItem, throwAsync, validate, toMap, countItems, TransformObserver, NOT_NULLISH, hasItems, } from "../util/index.js";
2
2
  import { DataUpdate, Update } from "../update/index.js";
3
3
  import { Feedback, InvalidFeedback } from "../feedback/index.js";
4
4
  import { Filters, Query, EqualFilter } from "../query/index.js";
@@ -74,11 +74,18 @@ export class DataQuery extends Query {
74
74
  }
75
75
  /**
76
76
  * Count the number of results of this set of documents.
77
- * @return Number of documents in the collection (possibly promised).
77
+ * @return Number of documents matching the query (possibly promised).
78
78
  */
79
79
  get count() {
80
80
  return callAsync(countItems, this.results);
81
81
  }
82
+ /**
83
+ * Does at least one document exist for this query?
84
+ * @return `true` if a document exists or `false` otherwise (possibly promised).
85
+ */
86
+ get exists() {
87
+ return callAsync(hasItems, this.max(1).results);
88
+ }
82
89
  /**
83
90
  * Get an entry for the first document matched by this query or `undefined` if this query has no results.
84
91
  *
@@ -194,15 +201,13 @@ export class DataDocument {
194
201
  }
195
202
  /**
196
203
  * Does this document exist?
197
- *
198
- * @return Document's data, or `undefined` if the document doesn't exist (possibly promised).
204
+ * @return `true` if a document exists or `false` otherwise (possibly promised).
199
205
  */
200
206
  get exists() {
201
207
  return callAsync(Boolean, this.provider.get(this));
202
208
  }
203
209
  /**
204
210
  * Get the result of this document.
205
- *
206
211
  * @return Document's data, or `undefined` if the document doesn't exist (possibly promised).
207
212
  */
208
213
  get result() {
package/db/Pagination.js CHANGED
@@ -1,4 +1,4 @@
1
- import { getLastItem, assertLength, assertNumber, yieldMerged, toMap, LOADING } from "../util/index.js";
1
+ import { getLastItem, assertNumber, yieldMerged, toMap, LOADING, assertMaximum } from "../util/index.js";
2
2
  import { State } from "../stream/index.js";
3
3
  import { ConditionError } from "../index.js";
4
4
  /**
@@ -40,7 +40,7 @@ export class Pagination extends State {
40
40
  };
41
41
  this.ref = ref;
42
42
  assertNumber(ref.limit); // Collection must have a numeric limit to paginate (otherwise you'd be retrieving the entire set of documents).
43
- assertLength(ref.sorts, 1, Infinity); // Collection must have at least one sort order to paginate.
43
+ assertMaximum(ref.sorts.size, 1); // Collection must have at least one sort order to paginate.
44
44
  this.limit = ref.limit;
45
45
  }
46
46
  /**
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "state-management",
12
12
  "query-builder"
13
13
  ],
14
- "version": "1.34.0",
14
+ "version": "1.38.0",
15
15
  "repository": "https://github.com/dhoulb/shelving",
16
16
  "author": "Dave Houlbrooke <dave@shax.com>",
17
17
  "license": "0BSD",
@@ -60,12 +60,12 @@
60
60
  },
61
61
  "devDependencies": {
62
62
  "@google-cloud/firestore": "^4.15.1",
63
- "@types/jest": "^27.0.3",
64
- "@types/react": "^17.0.37",
63
+ "@types/jest": "^27.4.0",
64
+ "@types/react": "^17.0.38",
65
65
  "@types/react-dom": "^17.0.11",
66
- "@typescript-eslint/eslint-plugin": "^5.7.0",
67
- "@typescript-eslint/parser": "^5.7.0",
68
- "eslint": "^8.5.0",
66
+ "@typescript-eslint/eslint-plugin": "^5.8.1",
67
+ "@typescript-eslint/parser": "^5.8.1",
68
+ "eslint": "^8.6.0",
69
69
  "eslint-config-prettier": "^8.3.0",
70
70
  "eslint-plugin-import": "^2.25.3",
71
71
  "eslint-plugin-prettier": "^4.0.0",
package/query/Rules.d.ts CHANGED
@@ -8,7 +8,7 @@ export declare abstract class Rules<T extends Data, R extends Rule<T>> extends R
8
8
  /** Get the last rule. */
9
9
  get last(): R | undefined;
10
10
  /** Get the number of rules. */
11
- get length(): number;
11
+ get size(): number;
12
12
  constructor(...rules: R[]);
13
13
  toString(): string;
14
14
  /** Iterate over the rules. */
package/query/Rules.js CHANGED
@@ -15,7 +15,7 @@ export class Rules extends Rule {
15
15
  return this._rules[this._rules.length - 1];
16
16
  }
17
17
  /** Get the number of rules. */
18
- get length() {
18
+ get size() {
19
19
  return this._rules.length;
20
20
  }
21
21
  toString() {
@@ -3,8 +3,8 @@ import { State } from "./State.js";
3
3
  /** State that stores an array and has additional methods to help with that. */
4
4
  export declare class ArrayState<T> extends State<ImmutableArray<T>> implements Iterable<T> {
5
5
  _value: never[];
6
- /** Count the number of entries in this map-like object. */
7
- get count(): number;
6
+ /** Get the length of the current value of this state. */
7
+ get length(): number;
8
8
  /** Add an item to this array. */
9
9
  add(item: T): void;
10
10
  /** Remove an item from this array. */
@@ -1,4 +1,4 @@
1
- import { swapItem, toggleItem, withItem, withoutItem, countItems } from "../util/index.js";
1
+ import { swapItem, toggleItem, withItem, withoutItem } from "../util/index.js";
2
2
  import { State } from "./State.js";
3
3
  /** State that stores an array and has additional methods to help with that. */
4
4
  export class ArrayState extends State {
@@ -7,9 +7,9 @@ export class ArrayState extends State {
7
7
  // Set default value to be empty array.
8
8
  this._value = [];
9
9
  }
10
- /** Count the number of entries in this map-like object. */
11
- get count() {
12
- return countItems(this.value);
10
+ /** Get the length of the current value of this state. */
11
+ get length() {
12
+ return this.value.length;
13
13
  }
14
14
  /** Add an item to this array. */
15
15
  add(item) {
package/util/array.d.ts CHANGED
@@ -163,3 +163,15 @@ export declare function removeItems<T>(arr: MutableArray<T>, items: Iterable<T>)
163
163
  export declare function uniqueArray<T>(input: Iterable<T>): ImmutableArray<T>;
164
164
  /** Apply a limit to an array. */
165
165
  export declare function limitArray<T>(arr: ImmutableArray<T>, limit: number): ImmutableArray<T>;
166
+ /** Does an array have the specified minimum length. */
167
+ export declare function isMinimumLength<T>(arr: ImmutableArray<T>, min?: 1): arr is [T, ...T[]];
168
+ export declare function isMinimumLength<T>(arr: ImmutableArray<T>, min: 2): arr is [T, T, ...T[]];
169
+ export declare function isMinimumLength<T>(arr: ImmutableArray<T>, min: 3): arr is [T, T, T, ...T[]];
170
+ export declare function isMinimumLength<T>(arr: ImmutableArray<T>, min: 4): arr is [T, T, T, T, ...T[]];
171
+ export declare function isMinimumLength<T>(arr: ImmutableArray<T>, min: number): arr is [T, T, T, T, T, ...T[]];
172
+ /** Get an array if it has the specified minimum length. */
173
+ export declare function getMinimumLength<T>(arr: ImmutableArray<T>, min?: 1): [T, ...T[]];
174
+ export declare function getMinimumLength<T>(arr: ImmutableArray<T>, min: 2): [T, T, ...T[]];
175
+ export declare function getMinimumLength<T>(arr: ImmutableArray<T>, min: 3): [T, T, T, ...T[]];
176
+ export declare function getMinimumLength<T>(arr: ImmutableArray<T>, min: 4): [T, T, T, T, ...T[]];
177
+ export declare function getMinimumLength<T>(arr: ImmutableArray<T>, min: number): [T, T, T, T, T, ...T[]];
package/util/array.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { AssertionError } from "../error/index.js";
1
2
  /** Is an unknown value an array? */
2
3
  export const isArray = (v) => v instanceof Array;
3
4
  /** Is an unknown value an item in a specified array? */
@@ -244,3 +245,11 @@ export function uniqueArray(input) {
244
245
  export function limitArray(arr, limit) {
245
246
  return limit > arr.length ? arr : arr.slice(0, limit);
246
247
  }
248
+ export function isMinimumLength(arr, min = 1) {
249
+ return arr.length >= min;
250
+ }
251
+ export function getMinimumLength(arr, min = 1) {
252
+ if (arr.length >= min)
253
+ return arr;
254
+ throw new AssertionError(`Must have minimum length ${min}`, arr);
255
+ }
package/util/assert.d.ts CHANGED
@@ -24,15 +24,21 @@ export declare function assertProp<K extends string | number | symbol, T extends
24
24
  [L in K]: unknown;
25
25
  }>(value: T | unknown, key: K): asserts value is T;
26
26
  /** Assert that a value is an array. */
27
- export declare function assertArray<T extends ImmutableArray>(value: T | unknown): asserts value is T;
27
+ export declare function assertArray<T>(value: ImmutableArray<T> | unknown): asserts value is ImmutableArray<T>;
28
28
  /** Assert that a value has a specific length (or length is in a specific range). */
29
- export declare function assertLength<T extends {
30
- length: number;
31
- }>(value: T | unknown, min: number, max?: number): asserts value is T;
29
+ export declare function assertMinimumLength<T>(value: ImmutableArray<T> | unknown, min?: 1): asserts value is [T, ...T[]];
30
+ export declare function assertMinimumLength<T>(value: ImmutableArray<T> | unknown, min: 2): asserts value is [T, T, ...T[]];
31
+ export declare function assertMinimumLength<T>(value: ImmutableArray<T> | unknown, min: 3): asserts value is [T, T, T, ...T[]];
32
+ export declare function assertMinimumLength<T>(value: ImmutableArray<T> | unknown, min: 4): asserts value is [T, T, T, T, ...T[]];
33
+ export declare function assertMinimumLength<T>(value: ImmutableArray<T> | unknown, min: number): asserts value is [T, T, T, T, T, ...T[]];
34
+ /** Assert that a value has a specific length (or length is in a specific range). */
35
+ export declare function assertLength<T>(value: ImmutableArray<T> | unknown, min: number, max?: number): asserts value is ImmutableArray<T>;
36
+ /** Assert that a value is a number greater than. */
37
+ export declare function assertBetween(value: number | unknown, min: number, max: number): asserts value is number;
32
38
  /** Assert that a value is a number greater than. */
33
- export declare function assertGreater(value: number | unknown, target: number): asserts value is number;
39
+ export declare function assertMaximum(value: number | unknown, max: number): asserts value is number;
34
40
  /** Assert that a value is a number less than. */
35
- export declare function assertLess(value: number | unknown, target: number): asserts value is number;
41
+ export declare function assertMinimum(value: number | unknown, min: number): asserts value is number;
36
42
  /** Assert that a value is an instance of something. */
37
43
  export declare function assertInstance<T>(value: T | unknown, type: Class<T>): asserts value is T;
38
44
  /** Assert that a value is a function. */
@@ -42,8 +48,8 @@ export declare function assertValue<T>(value: T | typeof NOVALUE): asserts value
42
48
  /** Assert that a value is not the `NOVALUE` constant. */
43
49
  export declare function assertDefined<T>(value: T | undefined): asserts value is T;
44
50
  /** Expect a synchronous value. */
45
- export declare function assertSync<T>(value: Promise<T> | T): asserts value is T;
51
+ export declare function assertSynchronous<T>(value: Promise<T> | T): asserts value is T;
46
52
  /** Expect an asynchronous value. */
47
- export declare function assertAsync<T>(value: PromiseLike<T> | T): asserts value is PromiseLike<T>;
53
+ export declare function assertAsynchronous<T>(value: PromiseLike<T> | T): asserts value is PromiseLike<T>;
48
54
  /** Expect a promise. */
49
55
  export declare function assertPromise<T>(value: Promise<T> | T): asserts value is Promise<T>;
package/util/assert.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { AssertionError } from "../error/index.js";
2
2
  import { debug } from "./debug.js";
3
3
  import { isObject } from "./object.js";
4
+ import { isArray } from "./array.js";
4
5
  import { NOVALUE } from "./constants.js";
5
6
  import { isAsync } from "./async.js";
6
7
  /** Assert a boolean condition is true. */
@@ -53,20 +54,29 @@ export function assertArray(value) {
53
54
  if (!(value instanceof Array))
54
55
  throw new AssertionError(`Must be array`, value);
55
56
  }
57
+ export function assertMinimumLength(value, min = 1) {
58
+ if (!isArray(value) || value.length < min)
59
+ throw new AssertionError(`Must be array with minimum length ${min}`, value);
60
+ }
56
61
  /** Assert that a value has a specific length (or length is in a specific range). */
57
62
  export function assertLength(value, min, max = min) {
58
- if (!isObject(value) || typeof value.length !== "number" || value.length < min || value.length > max)
59
- throw new AssertionError(`Must have length ${min}–${max}`, value);
63
+ if (!isArray(value) || value.length < min || value.length > max)
64
+ throw new AssertionError(`Must be array with length ${min}-${max}`, value);
65
+ }
66
+ /** Assert that a value is a number greater than. */
67
+ export function assertBetween(value, min, max) {
68
+ if (typeof value !== "number" || value < min || value > max)
69
+ throw new AssertionError(`Must be number between ${min}-${max}`, value);
60
70
  }
61
71
  /** Assert that a value is a number greater than. */
62
- export function assertGreater(value, target) {
63
- if (typeof value !== "number" || value <= target)
64
- throw new AssertionError(`Must be greater than ${target}`, value);
72
+ export function assertMaximum(value, max) {
73
+ if (typeof value !== "number" || value > max)
74
+ throw new AssertionError(`Must be number with maximum ${max}`, value);
65
75
  }
66
76
  /** Assert that a value is a number less than. */
67
- export function assertLess(value, target) {
68
- if (typeof value !== "number" || value >= target)
69
- throw new AssertionError(`Must be less than ${target}`, value);
77
+ export function assertMinimum(value, min) {
78
+ if (typeof value !== "number" || value < min)
79
+ throw new AssertionError(`Must be number with minimum ${min}`, value);
70
80
  }
71
81
  /** Assert that a value is an instance of something. */
72
82
  export function assertInstance(value, type) {
@@ -89,12 +99,12 @@ export function assertDefined(value) {
89
99
  throw new AssertionError("Must be defined", value);
90
100
  }
91
101
  /** Expect a synchronous value. */
92
- export function assertSync(value) {
102
+ export function assertSynchronous(value) {
93
103
  if (isAsync(value))
94
104
  throw new AssertionError("Must be synchronous", value);
95
105
  }
96
106
  /** Expect an asynchronous value. */
97
- export function assertAsync(value) {
107
+ export function assertAsynchronous(value) {
98
108
  if (!isAsync(value))
99
109
  throw new AssertionError("Must be asynchronous", value);
100
110
  }
package/util/iterate.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { ImmutableMap } from "./map.js";
2
1
  import type { ImmutableArray } from "./array.js";
3
2
  import type { Entry } from "./entry.js";
4
3
  import { DONE } from "./constants.js";
@@ -23,8 +22,6 @@ export declare const isIterable: <T extends Iterable<unknown>>(value: unknown) =
23
22
  * - Note: Array and Map instances etc will return true because they implement `Symbol.iterator`
24
23
  */
25
24
  export declare const isAsyncIterable: <T extends AsyncIterable<unknown>>(value: unknown) => value is T;
26
- /** Get the known size or length of an object (e.g. `Array`, `Map`, and `Set` have known size), or return `undefined` if the size cannot be established. */
27
- export declare const getSize: (obj: Iterable<unknown> | ImmutableMap | ImmutableArray) => number | undefined;
28
25
  /**
29
26
  * Count the number items of an iterable.
30
27
  * - Checks `items.size` or `items.length` first, or consumes the iterable and counts its iterations.
@@ -35,8 +32,16 @@ export declare function countItems(items: Iterable<unknown>): number;
35
32
  * - Note: this consumes the iterable so you won't be able to use it again.
36
33
  */
37
34
  export declare function countIterations(items: Iterable<unknown>): number;
38
- /** Sum an iterable set of numbers and return the total. */
39
- export declare function sumItems(input: Iterable<number>): number;
35
+ /**
36
+ * Does an iterable have one or more items.
37
+ * - Checks `items.size` or `items.length` first, or consumes the iterable and counts its iterations.
38
+ */
39
+ export declare function hasItems(items: Iterable<unknown>): boolean;
40
+ /**
41
+ * Does an iterable have one or more items.
42
+ * - Checks `items.size` or `items.length` first, or consumes the iterable and counts its iterations.
43
+ */
44
+ export declare function hasIterations(items: Iterable<unknown>): boolean;
40
45
  /**
41
46
  * Yield a range of numbers from `start` to `end`
42
47
  * - Yields in descending order if `end` is lower than `start`
package/util/iterate.js CHANGED
@@ -13,14 +13,14 @@ export const isIterable = (value) => typeof value === "object" && !!value && Sym
13
13
  */
14
14
  export const isAsyncIterable = (value) => typeof value === "object" && !!value && Symbol.asyncIterator in value;
15
15
  /** Get the known size or length of an object (e.g. `Array`, `Map`, and `Set` have known size), or return `undefined` if the size cannot be established. */
16
- export const getSize = (obj) => ("size" in obj && typeof obj.size === "number" ? obj.size : "length" in obj && typeof obj.length === "number" ? obj.length : undefined);
16
+ const getKnownSize = (obj) => ("size" in obj && typeof obj.size === "number" ? obj.size : "length" in obj && typeof obj.length === "number" ? obj.length : undefined);
17
17
  /**
18
18
  * Count the number items of an iterable.
19
19
  * - Checks `items.size` or `items.length` first, or consumes the iterable and counts its iterations.
20
20
  */
21
21
  export function countItems(items) {
22
22
  var _a;
23
- return (_a = getSize(items)) !== null && _a !== void 0 ? _a : countIterations(items);
23
+ return (_a = getKnownSize(items)) !== null && _a !== void 0 ? _a : countIterations(items);
24
24
  }
25
25
  /**
26
26
  * Count the number of iterations an iterable does.
@@ -32,12 +32,22 @@ export function countIterations(items) {
32
32
  count++; // eslint-disable-line @typescript-eslint/no-unused-vars
33
33
  return count;
34
34
  }
35
- /** Sum an iterable set of numbers and return the total. */
36
- export function sumItems(input) {
37
- let sum = 0;
38
- for (const num of input)
39
- sum += num;
40
- return sum;
35
+ /**
36
+ * Does an iterable have one or more items.
37
+ * - Checks `items.size` or `items.length` first, or consumes the iterable and counts its iterations.
38
+ */
39
+ export function hasItems(items) {
40
+ var _a;
41
+ return !!((_a = getKnownSize(items)) !== null && _a !== void 0 ? _a : hasIterations(items));
42
+ }
43
+ /**
44
+ * Does an iterable have one or more items.
45
+ * - Checks `items.size` or `items.length` first, or consumes the iterable and counts its iterations.
46
+ */
47
+ export function hasIterations(items) {
48
+ for (const unused of items)
49
+ return true; // eslint-disable-line @typescript-eslint/no-unused-vars
50
+ return false;
41
51
  }
42
52
  /**
43
53
  * Yield a range of numbers from `start` to `end`
@@ -57,7 +67,7 @@ export function* yieldRange(start, end) {
57
67
  */
58
68
  export function limitItems(items, limit) {
59
69
  var _a;
60
- const size = (_a = getSize(items)) !== null && _a !== void 0 ? _a : Infinity;
70
+ const size = (_a = getKnownSize(items)) !== null && _a !== void 0 ? _a : Infinity;
61
71
  return size <= limit ? items : yieldUntilLimit(items, limit);
62
72
  }
63
73
  /** Yield items from a source iterable until we hit a maximum iteration count. */
package/util/number.d.ts CHANGED
@@ -11,6 +11,11 @@ export declare const IS_NUMBER: (v: unknown) => v is number;
11
11
  * - Everything else returns `null`
12
12
  */
13
13
  export declare function toNumber(value: unknown): number | null;
14
+ /**
15
+ * Assertively convert an unknown value to a finite number.
16
+ * @throws `AssertionError` if the value cannot be converted.
17
+ */
18
+ export declare function getNumber(value: unknown): number;
14
19
  /**
15
20
  * Round numbers to a given step.
16
21
  *
@@ -44,10 +49,14 @@ export declare const formatNumber: (num: number, precision?: number) => string;
44
49
  */
45
50
  export declare const isBetween: (num: number, start: number, end: number) => boolean;
46
51
  /**
47
- * Apply a min/max to a number to return a number that's definitely in the specified range.
52
+ * Apply a min/max to a number to return a number that's definitely in the specified range.
48
53
  *
49
54
  * @param num The number to apply the min/max to, e.g. `17`
50
55
  * @param start The start of the range, e.g. `10`
51
56
  * @param end The end of the range, e.g. `20`
52
57
  */
53
58
  export declare const getBetween: (num: number, start: number, end: number) => number;
59
+ /** Sum an iterable set of numbers and return the total. */
60
+ export declare function sumNumbers(nums: Iterable<number>): number;
61
+ /** Find the number that's closest to a target in an iterable set of numbers. */
62
+ export declare function getClosestNumber<T extends number>(nums: Iterable<T>, target: number): T | undefined;
package/util/number.js CHANGED
@@ -21,6 +21,16 @@ export function toNumber(value) {
21
21
  return null;
22
22
  }
23
23
  const NUMERIC = /[^0-9-.]/g;
24
+ /**
25
+ * Assertively convert an unknown value to a finite number.
26
+ * @throws `AssertionError` if the value cannot be converted.
27
+ */
28
+ export function getNumber(value) {
29
+ const num = toNumber(value);
30
+ if (num === null)
31
+ throw new AssertionError("Must be number", value);
32
+ return num;
33
+ }
24
34
  /**
25
35
  * Round numbers to a given step.
26
36
  *
@@ -58,10 +68,25 @@ export const formatNumber = (num, precision = 10) => new Intl.NumberFormat(undef
58
68
  */
59
69
  export const isBetween = (num, start, end) => num >= start && num <= end;
60
70
  /**
61
- * Apply a min/max to a number to return a number that's definitely in the specified range.
71
+ * Apply a min/max to a number to return a number that's definitely in the specified range.
62
72
  *
63
73
  * @param num The number to apply the min/max to, e.g. `17`
64
74
  * @param start The start of the range, e.g. `10`
65
75
  * @param end The end of the range, e.g. `20`
66
76
  */
67
77
  export const getBetween = (num, start, end) => Math.max(start, Math.min(end, num));
78
+ /** Sum an iterable set of numbers and return the total. */
79
+ export function sumNumbers(nums) {
80
+ let sum = 0;
81
+ for (const num of nums)
82
+ sum += num;
83
+ return sum;
84
+ }
85
+ /** Find the number that's closest to a target in an iterable set of numbers. */
86
+ export function getClosestNumber(nums, target) {
87
+ let closest = undefined;
88
+ for (const item of nums)
89
+ if (closest === undefined || Math.abs(item - target) < Math.abs(closest - target))
90
+ closest = item;
91
+ return closest;
92
+ }