@ezez/utils 4.8.0 → 4.8.2
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/.claude/settings.local.json +14 -0
- package/CHANGELOG.md +18 -0
- package/CLAUDE.md +137 -0
- package/dist/get.d.ts.map +1 -1
- package/dist/get.js +1 -1
- package/dist/get.js.map +1 -1
- package/dist/mostFrequent.d.ts +1 -1
- package/dist/mostFrequent.d.ts.map +1 -1
- package/dist/mostFrequent.js +2 -5
- package/dist/mostFrequent.js.map +1 -1
- package/dist/samples.d.ts.map +1 -1
- package/dist/samples.js +2 -1
- package/dist/samples.js.map +1 -1
- package/dist/scale.d.ts.map +1 -1
- package/dist/scale.js +3 -0
- package/dist/scale.js.map +1 -1
- package/dist/serializeToBuffer/unserializeFromBuffer.d.ts.map +1 -1
- package/dist/serializeToBuffer/unserializeFromBuffer.js +1 -3
- package/dist/serializeToBuffer/unserializeFromBuffer.js.map +1 -1
- package/dist/sortByMultiple.d.ts.map +1 -1
- package/dist/sortByMultiple.js.map +1 -1
- package/dist/throttle.d.ts +3 -3
- package/dist/throttle.d.ts.map +1 -1
- package/dist/throttle.js +2 -3
- package/dist/throttle.js.map +1 -1
- package/dist/trimEnd.d.ts.map +1 -1
- package/dist/trimEnd.js +3 -0
- package/dist/trimEnd.js.map +1 -1
- package/dist/trimStart.d.ts.map +1 -1
- package/dist/trimStart.js +3 -0
- package/dist/trimStart.js.map +1 -1
- package/dist/waitFor.d.ts.map +1 -1
- package/dist/waitFor.js +19 -11
- package/dist/waitFor.js.map +1 -1
- package/docs/documents/Changelog.html +72 -54
- package/docs/functions/index.assertProps.html +2 -2
- package/docs/functions/index.cap.html +2 -2
- package/docs/functions/index.capitalize.html +2 -2
- package/docs/functions/index.coalesce.html +2 -2
- package/docs/functions/index.compareArrays.html +2 -2
- package/docs/functions/index.compareProps.html +2 -2
- package/docs/functions/index.deserialize.html +2 -2
- package/docs/functions/index.ensureArray.html +2 -2
- package/docs/functions/index.ensureDate.html +2 -2
- package/docs/functions/index.ensureError.html +2 -2
- package/docs/functions/index.ensurePrefix.html +2 -2
- package/docs/functions/index.ensureSuffix.html +2 -2
- package/docs/functions/index.ensureTimestamp.html +2 -2
- package/docs/functions/index.escapeRegExp.html +2 -2
- package/docs/functions/index.formatDate.html +2 -2
- package/docs/functions/index.formatHash.html +2 -2
- package/docs/functions/index.get.html +3 -3
- package/docs/functions/index.getMultiple.html +2 -2
- package/docs/functions/index.hasProps.html +2 -2
- package/docs/functions/index.ignore.html +2 -2
- package/docs/functions/index.insertSeparator.html +2 -2
- package/docs/functions/index.isEmpty.html +2 -2
- package/docs/functions/index.isNumericString.html +2 -2
- package/docs/functions/index.isPlainObject.html +2 -2
- package/docs/functions/index.last.html +2 -2
- package/docs/functions/index.later-1.html +2 -2
- package/docs/functions/index.mapAsync.html +2 -2
- package/docs/functions/index.mapValues.html +2 -2
- package/docs/functions/index.match.html +2 -2
- package/docs/functions/index.memoize.html +2 -2
- package/docs/functions/index.merge.html +2 -2
- package/docs/functions/index.mostFrequent.html +2 -2
- package/docs/functions/index.noop.html +2 -2
- package/docs/functions/index.occurrences.html +2 -2
- package/docs/functions/index.omit.html +2 -2
- package/docs/functions/index.pick.html +2 -2
- package/docs/functions/index.pull.html +2 -2
- package/docs/functions/index.race.html +2 -2
- package/docs/functions/index.remove.html +2 -2
- package/docs/functions/index.removeCommonProperties.html +2 -2
- package/docs/functions/index.replace.html +2 -2
- package/docs/functions/index.replaceDeep.html +2 -2
- package/docs/functions/index.rethrow.html +2 -2
- package/docs/functions/index.retry.html +2 -2
- package/docs/functions/index.round.html +2 -2
- package/docs/functions/index.safe.html +2 -2
- package/docs/functions/index.sample.html +2 -2
- package/docs/functions/index.samples.html +2 -2
- package/docs/functions/index.scale.html +3 -2
- package/docs/functions/index.seq.html +2 -2
- package/docs/functions/index.seqEarlyBreak.html +2 -2
- package/docs/functions/index.serialize.html +2 -2
- package/docs/functions/index.serializeToBuffer.html +2 -2
- package/docs/functions/index.set.html +2 -2
- package/docs/functions/index.setImmutable.html +2 -2
- package/docs/functions/index.shuffle.html +2 -2
- package/docs/functions/index.sortBy.html +2 -2
- package/docs/functions/index.sortByMultiple.html +3 -3
- package/docs/functions/index.sortProps.html +2 -2
- package/docs/functions/index.stripPrefix.html +2 -2
- package/docs/functions/index.stripSuffix.html +2 -2
- package/docs/functions/index.throttle.html +3 -3
- package/docs/functions/index.toggle.html +2 -2
- package/docs/functions/index.trim.html +2 -2
- package/docs/functions/index.trimEnd.html +2 -2
- package/docs/functions/index.trimStart.html +2 -2
- package/docs/functions/index.truthy.html +2 -2
- package/docs/functions/index.unique.html +2 -2
- package/docs/functions/index.unserializeFromBuffer.html +2 -2
- package/docs/functions/index.wait.html +2 -2
- package/docs/functions/index.waitFor.html +2 -2
- package/docs/functions/index.waitSync.html +2 -2
- package/docs/index.html +2 -2
- package/docs/interfaces/index.ComparePropsOptions.html +3 -3
- package/docs/interfaces/index.GetMultipleSource.html +2 -2
- package/docs/interfaces/index.GetSource.html +2 -2
- package/docs/interfaces/index.IsNumericStringOptions.html +2 -2
- package/docs/interfaces/index.OccurencesOptions.html +2 -2
- package/docs/interfaces/index.SetImmutableSource.html +2 -2
- package/docs/interfaces/index.SetSource.html +2 -2
- package/docs/interfaces/index.ThrottleOptions.html +3 -3
- package/docs/interfaces/index.ThrottledFunctionExtras.html +3 -3
- package/docs/modules/index.html +1 -1
- package/docs/modules.html +1 -1
- package/docs/types/index.CustomDeserializers.html +1 -1
- package/docs/types/index.CustomSerializers.html +1 -1
- package/docs/types/index.Later.html +2 -2
- package/docs/types/index.MapValuesFn.html +2 -2
- package/docs/types/index.MatchCallback.html +1 -1
- package/docs/types/index.MergeTwo.html +2 -2
- package/docs/types/index.SeqEarlyBreaker.html +2 -2
- package/docs/types/index.SeqFn.html +2 -2
- package/docs/types/index.SeqFunctions.html +2 -2
- package/docs/types/index.SetImmutablePath.html +2 -2
- package/docs/types/index.ThrottledFunction.html +1 -1
- package/docs/variables/index.mapValuesUNSET.html +2 -2
- package/docs/variables/index.mergeUNSET.html +2 -2
- package/esm/get.d.ts.map +1 -1
- package/esm/get.js +1 -1
- package/esm/get.js.map +1 -1
- package/esm/mostFrequent.d.ts +1 -1
- package/esm/mostFrequent.d.ts.map +1 -1
- package/esm/mostFrequent.js +2 -5
- package/esm/mostFrequent.js.map +1 -1
- package/esm/samples.d.ts.map +1 -1
- package/esm/samples.js +2 -1
- package/esm/samples.js.map +1 -1
- package/esm/scale.d.ts.map +1 -1
- package/esm/scale.js +3 -0
- package/esm/scale.js.map +1 -1
- package/esm/serializeToBuffer/unserializeFromBuffer.d.ts.map +1 -1
- package/esm/serializeToBuffer/unserializeFromBuffer.js +1 -3
- package/esm/serializeToBuffer/unserializeFromBuffer.js.map +1 -1
- package/esm/sortByMultiple.d.ts.map +1 -1
- package/esm/sortByMultiple.js.map +1 -1
- package/esm/throttle.d.ts +3 -3
- package/esm/throttle.d.ts.map +1 -1
- package/esm/throttle.js +2 -3
- package/esm/throttle.js.map +1 -1
- package/esm/trimEnd.d.ts.map +1 -1
- package/esm/trimEnd.js +3 -0
- package/esm/trimEnd.js.map +1 -1
- package/esm/trimStart.d.ts.map +1 -1
- package/esm/trimStart.js +3 -0
- package/esm/trimStart.js.map +1 -1
- package/esm/waitFor.d.ts.map +1 -1
- package/esm/waitFor.js +19 -11
- package/esm/waitFor.js.map +1 -1
- package/package.json +12 -17
- package/pnpm-workspace.yaml +2 -0
- package/src/get.ts +1 -1
- package/src/mostFrequent.spec.ts +2 -1
- package/src/mostFrequent.ts +3 -7
- package/src/race.spec.ts +42 -0
- package/src/race.ts +0 -2
- package/src/retry.spec.ts +87 -0
- package/src/retry.ts +0 -2
- package/src/samples.spec.ts +9 -0
- package/src/samples.ts +4 -1
- package/src/scale.spec.ts +18 -0
- package/src/scale.ts +4 -0
- package/src/serializeToBuffer/unserializeFromBuffer.spec.ts +10 -0
- package/src/serializeToBuffer/unserializeFromBuffer.ts +1 -4
- package/src/sortByMultiple.ts +1 -0
- package/src/throttle.spec.ts +35 -0
- package/src/throttle.ts +7 -8
- package/src/trimEnd.spec.ts +5 -0
- package/src/trimEnd.ts +3 -0
- package/src/trimStart.spec.ts +5 -0
- package/src/trimStart.ts +3 -0
- package/src/waitFor.spec.ts +61 -0
- package/src/waitFor.ts +33 -15
- package/src/waitSync.ts +2 -2
package/esm/waitFor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"waitFor.js","sourceRoot":"","sources":["../src/waitFor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAoB5B,MAAM,cAAc,GAAsB;IACtC,QAAQ,EAAE,gBAAgB;IAC1B,OAAO,EAAE,QAAQ;IACjB,QAAQ,EAAE,QAAQ;CACrB,CAAC;AAeF,MAAM,OAAO,GAAG,CAAI,EAAyB,EAAE,UAAmB,cAAc,EAAc,EAAE;
|
|
1
|
+
{"version":3,"file":"waitFor.js","sourceRoot":"","sources":["../src/waitFor.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ezez/utils",
|
|
3
|
-
"version": "4.8.
|
|
3
|
+
"version": "4.8.2",
|
|
4
4
|
"repository": "https://github.com/dzek69/bottom-line.git",
|
|
5
5
|
"author": "Jacek Nowacki @dzek69 <git-public@dzek.eu>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,25 +16,23 @@
|
|
|
16
16
|
"module": "./esm/index.js",
|
|
17
17
|
"type": "module",
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@babel/core": "^7.
|
|
20
|
-
"@babel/preset-env": "^7.
|
|
21
|
-
"@babel/preset-typescript": "^7.
|
|
22
|
-
"@ezez/eslint": "^9.39.
|
|
19
|
+
"@babel/core": "^7.29.7",
|
|
20
|
+
"@babel/preset-env": "^7.29.7",
|
|
21
|
+
"@babel/preset-typescript": "^7.29.7",
|
|
22
|
+
"@ezez/eslint": "^9.39.4",
|
|
23
23
|
"@types/jest": "^29.5.14",
|
|
24
|
-
"@types/lodash": "^4.
|
|
25
|
-
"@types/node": "^20.
|
|
24
|
+
"@types/lodash": "^4.17.24",
|
|
25
|
+
"@types/node": "^20.19.43",
|
|
26
26
|
"babel-plugin-module-extension": "^0.1.3",
|
|
27
|
-
"fs-extra": "^11.3.
|
|
27
|
+
"fs-extra": "^11.3.5",
|
|
28
28
|
"husky": "^8.0.3",
|
|
29
|
-
"jest": "^
|
|
30
|
-
"lodash": "^4.
|
|
29
|
+
"jest": "^30.4.2",
|
|
30
|
+
"lodash": "^4.18.1",
|
|
31
31
|
"must": "^0.13.4",
|
|
32
|
-
"
|
|
33
|
-
"prettier": "^3.4.2",
|
|
32
|
+
"prettier": "^3.8.4",
|
|
34
33
|
"resolve-tspaths": "^0.8.23",
|
|
35
|
-
"ts-node": "^10.9.2",
|
|
36
34
|
"typedoc": "0.27.6",
|
|
37
|
-
"typescript": "^5.
|
|
35
|
+
"typescript": "^5.9.3"
|
|
38
36
|
},
|
|
39
37
|
"husky": {
|
|
40
38
|
"hooks": {
|
|
@@ -47,7 +45,6 @@
|
|
|
47
45
|
"fixDefaultForCommonJS": false,
|
|
48
46
|
"jsx": false
|
|
49
47
|
},
|
|
50
|
-
"dependencies": {},
|
|
51
48
|
"scripts": {
|
|
52
49
|
"test": "NODE_ENV=test jest",
|
|
53
50
|
"test:lodashImports": "node test/lodash-tree.cjs",
|
|
@@ -58,8 +55,6 @@
|
|
|
58
55
|
"typecheck": "tsc --noEmit",
|
|
59
56
|
"lint": "ezlint src",
|
|
60
57
|
"lint:fix": "pnpm run lint --fix",
|
|
61
|
-
"start:dev": "nodemon",
|
|
62
|
-
"start:dev:compatibility": "TS_NODE_FILES=true pnpm run start:dev",
|
|
63
58
|
"updates": "pnpm dlx npm-check-updates --dep prod",
|
|
64
59
|
"updates:dev": "pnpm dlx npm-check-updates --dep dev",
|
|
65
60
|
"updates:all": "pnpm dlx npm-check-updates"
|
package/src/get.ts
CHANGED
|
@@ -30,7 +30,7 @@ interface Source { [key: string]: unknown }
|
|
|
30
30
|
* // else `5` will be returned
|
|
31
31
|
* @returns {*} - found value or default value
|
|
32
32
|
*/
|
|
33
|
-
const get = (source: Source, property: string | string[], defaultValue
|
|
33
|
+
const get = (source: Source, property: string | string[], defaultValue?: unknown): unknown => {
|
|
34
34
|
const properties = typeof property === "string" ? property.split(".") : [...property];
|
|
35
35
|
|
|
36
36
|
let result: unknown = source;
|
package/src/mostFrequent.spec.ts
CHANGED
|
@@ -16,7 +16,8 @@ describe("mostFrequent", () => {
|
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
it("returns undefined if array empty", () => {
|
|
19
|
-
|
|
19
|
+
const result = mostFrequent<number>([]);
|
|
20
|
+
must(result).equal(undefined);
|
|
20
21
|
});
|
|
21
22
|
|
|
22
23
|
it("compares strictly", () => {
|
package/src/mostFrequent.ts
CHANGED
|
@@ -2,24 +2,20 @@
|
|
|
2
2
|
* Finds most frequent value in array
|
|
3
3
|
* @param {Array} array
|
|
4
4
|
*/
|
|
5
|
-
const mostFrequent = <T>(array: T[]): T => {
|
|
5
|
+
const mostFrequent = <T>(array: T[]): T | undefined => {
|
|
6
6
|
let top = 0,
|
|
7
7
|
topValue = array[0];
|
|
8
8
|
|
|
9
9
|
const map = new Map<T, number>();
|
|
10
10
|
array.forEach(value => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
const next = map.get(value)! + 1;
|
|
15
|
-
map.set(value, map.get(value)! + 1);
|
|
11
|
+
const next = (map.get(value) ?? 0) + 1;
|
|
12
|
+
map.set(value, next);
|
|
16
13
|
if (next > top) {
|
|
17
14
|
top = next;
|
|
18
15
|
topValue = value;
|
|
19
16
|
}
|
|
20
17
|
});
|
|
21
18
|
|
|
22
|
-
// @ts-expect-error - idk if there is a good workaround for this with `noUncheckedIndexedAccess`
|
|
23
19
|
return topValue;
|
|
24
20
|
};
|
|
25
21
|
|
package/src/race.spec.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import must from "must"; // eslint-disable-line @typescript-eslint/no-shadow
|
|
2
|
+
|
|
3
|
+
import { race } from "./race";
|
|
4
|
+
import { wait } from "./wait";
|
|
5
|
+
|
|
6
|
+
describe("race", () => {
|
|
7
|
+
it("resolves with the promise value when it settles before the timeout", async () => {
|
|
8
|
+
const start = Date.now();
|
|
9
|
+
const result = await race(Promise.resolve(42), 100);
|
|
10
|
+
must(result).equal(42);
|
|
11
|
+
must(Date.now() - start).be.below(90);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("rejects with the promise error when it rejects before the timeout", async () => {
|
|
15
|
+
const start = Date.now();
|
|
16
|
+
await race(Promise.reject(new Error("boom")), 100).then(() => {
|
|
17
|
+
throw new Error("Should not resolve");
|
|
18
|
+
}, (e: unknown) => {
|
|
19
|
+
must((e as Error).message).equal("boom");
|
|
20
|
+
must(Date.now() - start).be.below(90);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("rejects with a timeout error when the promise is too slow", async () => {
|
|
25
|
+
const slow = wait(200).then(() => "late");
|
|
26
|
+
await race(slow, 50).then(() => {
|
|
27
|
+
throw new Error("Should not resolve");
|
|
28
|
+
}, (e: unknown) => {
|
|
29
|
+
must(e).instanceOf(Error);
|
|
30
|
+
must((e as Error).message).equal("Race: Timeout");
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("uses a custom timeout message", async () => {
|
|
35
|
+
const slow = wait(200).then(() => "late");
|
|
36
|
+
await race(slow, 50, "too slow").then(() => {
|
|
37
|
+
throw new Error("Should not resolve");
|
|
38
|
+
}, (e: unknown) => {
|
|
39
|
+
must((e as Error).message).equal("too slow");
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
});
|
package/src/race.ts
CHANGED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import must from "must"; // eslint-disable-line @typescript-eslint/no-shadow
|
|
2
|
+
|
|
3
|
+
import { retry } from "./retry";
|
|
4
|
+
|
|
5
|
+
describe("retry", () => {
|
|
6
|
+
it("returns the result on first success without retrying", async () => {
|
|
7
|
+
let calls = 0;
|
|
8
|
+
const result = await retry(async () => {
|
|
9
|
+
calls++;
|
|
10
|
+
return "ok";
|
|
11
|
+
});
|
|
12
|
+
must(result).equal("ok");
|
|
13
|
+
must(calls).equal(1);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("retries until the function succeeds", async () => {
|
|
17
|
+
let calls = 0;
|
|
18
|
+
const result = await retry(async () => {
|
|
19
|
+
calls++;
|
|
20
|
+
if (calls < 3) {
|
|
21
|
+
throw new Error("fail");
|
|
22
|
+
}
|
|
23
|
+
return calls;
|
|
24
|
+
});
|
|
25
|
+
must(result).equal(3);
|
|
26
|
+
must(calls).equal(3);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("with maxRetries 0 runs once and rethrows the error", async () => {
|
|
30
|
+
let calls = 0;
|
|
31
|
+
await retry(async () => {
|
|
32
|
+
calls++;
|
|
33
|
+
throw new Error("nope");
|
|
34
|
+
}, { maxRetries: 0 }).then(() => {
|
|
35
|
+
throw new Error("Should not resolve");
|
|
36
|
+
}, (e: unknown) => {
|
|
37
|
+
must((e as Error).message).equal("nope");
|
|
38
|
+
});
|
|
39
|
+
must(calls).equal(1);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("with maxRetries N runs N+1 times before giving up", async () => {
|
|
43
|
+
let calls = 0;
|
|
44
|
+
await retry(async () => {
|
|
45
|
+
calls++;
|
|
46
|
+
throw new Error("nope");
|
|
47
|
+
}, { maxRetries: 2 }).catch(() => { /* expected to reject */ });
|
|
48
|
+
must(calls).equal(3);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("stops early when earlyBreak returns true", async () => {
|
|
52
|
+
let calls = 0;
|
|
53
|
+
const seenCounts: number[] = [];
|
|
54
|
+
await retry(async () => {
|
|
55
|
+
calls++;
|
|
56
|
+
throw new Error("stop");
|
|
57
|
+
}, {
|
|
58
|
+
maxRetries: 10,
|
|
59
|
+
earlyBreak: (_e, count) => {
|
|
60
|
+
seenCounts.push(count);
|
|
61
|
+
return calls === 2;
|
|
62
|
+
},
|
|
63
|
+
}).catch(() => { /* expected to reject */ });
|
|
64
|
+
must(calls).equal(2);
|
|
65
|
+
must(seenCounts).eql([0, 1]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("waits between retries, passing the retry count to the wait function", async () => {
|
|
69
|
+
let calls = 0;
|
|
70
|
+
const waitCounts: number[] = [];
|
|
71
|
+
const result = await retry(async () => {
|
|
72
|
+
calls++;
|
|
73
|
+
if (calls < 3) {
|
|
74
|
+
throw new Error("fail");
|
|
75
|
+
}
|
|
76
|
+
return "done";
|
|
77
|
+
}, {
|
|
78
|
+
waitBetween: (count) => {
|
|
79
|
+
waitCounts.push(count);
|
|
80
|
+
return 10;
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
must(result).equal("done");
|
|
84
|
+
must(calls).equal(3);
|
|
85
|
+
must(waitCounts).eql([1, 2]);
|
|
86
|
+
});
|
|
87
|
+
});
|
package/src/retry.ts
CHANGED
package/src/samples.spec.ts
CHANGED
|
@@ -47,4 +47,13 @@ describe("samples", () => {
|
|
|
47
47
|
const elements = [1, 2, 3];
|
|
48
48
|
must(() => samples(elements, -1)).throw();
|
|
49
49
|
});
|
|
50
|
+
|
|
51
|
+
it("should not pad with undefined when allowShuffle and elementsToPick exceed length", async () => {
|
|
52
|
+
const elements = [1, 2, 3];
|
|
53
|
+
// BUG: with allowShuffle the early-return is skipped and the loop runs `elementsToPick`
|
|
54
|
+
// times; once the keys are exhausted it pushes `array[NaN]` (undefined) as padding.
|
|
55
|
+
const result = samples(elements, 5, true);
|
|
56
|
+
must(result).have.length(3);
|
|
57
|
+
must(result).not.include(undefined);
|
|
58
|
+
});
|
|
50
59
|
});
|
package/src/samples.ts
CHANGED
|
@@ -21,10 +21,13 @@ const samples = <T>(array: T[], elementsToPick: number, allowShuffle = false): T
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const keys = Object.keys(array);
|
|
24
|
+
// when shuffling we may be asked for more than we have - never pick more than exists, or we'd
|
|
25
|
+
// run out of keys and pad the result with `undefined`
|
|
26
|
+
const toPick = Math.min(elementsToPick, keys.length);
|
|
24
27
|
let picked = 0;
|
|
25
28
|
const result: T[] = [];
|
|
26
29
|
|
|
27
|
-
while (picked <
|
|
30
|
+
while (picked < toPick) {
|
|
28
31
|
const indexOfKey = Math.floor(Math.random() * keys.length);
|
|
29
32
|
const indexOfArray = Number(keys[indexOfKey]);
|
|
30
33
|
const element = array[indexOfArray]!;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import must from "must"; // eslint-disable-line @typescript-eslint/no-shadow
|
|
2
|
+
|
|
3
|
+
import { scale } from "./scale";
|
|
4
|
+
|
|
5
|
+
describe("scale", () => {
|
|
6
|
+
it("maps a value from one range to another", async () => {
|
|
7
|
+
must(scale(0, 10, 0, 100, 5)).equal(50);
|
|
8
|
+
must(scale(10, 20, 0, 200, 15)).equal(100);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("handles inverted target ranges", async () => {
|
|
12
|
+
must(scale(0, 10, 100, 0, 5)).equal(50);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("throws when fromMin equals fromMax", async () => {
|
|
16
|
+
must(() => scale(5, 5, 0, 100, 5)).throw(/fromMin and fromMax must not be equal/u);
|
|
17
|
+
});
|
|
18
|
+
});
|
package/src/scale.ts
CHANGED
|
@@ -8,8 +8,12 @@
|
|
|
8
8
|
* @param {number} toMin
|
|
9
9
|
* @param {number} toMax
|
|
10
10
|
* @param {number} number
|
|
11
|
+
* @throws {Error} when `fromMin` equals `fromMax` (the source range has zero width)
|
|
11
12
|
*/
|
|
12
13
|
const scale = (fromMin: number, fromMax: number, toMin: number, toMax: number, number: number): number => {
|
|
14
|
+
if (fromMin === fromMax) {
|
|
15
|
+
throw new Error("[scale] fromMin and fromMax must not be equal");
|
|
16
|
+
}
|
|
13
17
|
return toMin + ((number - fromMin) / (fromMax - fromMin) * (toMax - toMin));
|
|
14
18
|
};
|
|
15
19
|
|
|
@@ -60,4 +60,14 @@ describe("unserialize", () => {
|
|
|
60
60
|
0x35, 0x6a, 0x00, 0x22, 0x6e, 0x3a, 0x31, 0x22, 0x00,
|
|
61
61
|
]))).eql([1]);
|
|
62
62
|
});
|
|
63
|
+
|
|
64
|
+
it("unserializes multiple various arguments", async () => {
|
|
65
|
+
must(unserialize(Buffer.from([
|
|
66
|
+
0x35, 0x66, 0x00, 0x70, 0x69, 0x6e, 0x67, 0x32, 0x00,
|
|
67
|
+
0x35, 0x6a, 0x00, 0x22, 0x6e, 0x3a, 0x35, 0x22, 0x00,
|
|
68
|
+
0x35, 0x6a, 0x00, 0x22, 0x62, 0x3a, 0x31, 0x22, 0x00,
|
|
69
|
+
0x35, 0x6a, 0x00, 0x22, 0x6e, 0x3a, 0x36, 0x22, 0x00,
|
|
70
|
+
0x35, 0x6a, 0x00, 0x22, 0x6e, 0x3a, 0x39, 0x22, 0x00,
|
|
71
|
+
]))).eql(["ping2", 5, true, 6, 9]);
|
|
72
|
+
});
|
|
63
73
|
});
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { deserialize } from "../deserialize.js";
|
|
2
2
|
import { BINARY_MARK_BIN, BINARY_MARK_MAP, BINARY_MARK_STRING } from "./const.js";
|
|
3
3
|
|
|
4
|
-
const MAX_DATA_PARTS = 4;
|
|
5
|
-
|
|
6
4
|
const NOT_FOUND = -1;
|
|
7
5
|
const LAST_CHAR = -1;
|
|
8
6
|
|
|
@@ -29,8 +27,7 @@ const unserializeFromBuffer = <RT extends any[] = unknown[]>( // eslint-disable-
|
|
|
29
27
|
let startPoint = 0;
|
|
30
28
|
const result = [];
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
while (i++ < MAX_DATA_PARTS) {
|
|
30
|
+
while (startPoint < intData.length) {
|
|
34
31
|
const dataSplitPoint = intData.indexOf(0, startPoint); // find null
|
|
35
32
|
if (dataSplitPoint === NOT_FOUND) { // no null found = no data
|
|
36
33
|
break;
|
package/src/sortByMultiple.ts
CHANGED
package/src/throttle.spec.ts
CHANGED
|
@@ -133,4 +133,39 @@ describe("throttle", function() {
|
|
|
133
133
|
diffs[2].must.be.gte(300);
|
|
134
134
|
diffs[2].must.be.lt(350);
|
|
135
135
|
});
|
|
136
|
+
|
|
137
|
+
describe("flush", function() {
|
|
138
|
+
it("runs the pending trailing call immediately and only once", async function() {
|
|
139
|
+
let calls = 0;
|
|
140
|
+
const fn = () => {
|
|
141
|
+
calls++;
|
|
142
|
+
return calls;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const throttled = throttle(fn, 100, { leading: false, trailing: true });
|
|
146
|
+
throttled(); // schedules a trailing call ~100ms from now
|
|
147
|
+
throttled.flush(); // should run that planned call right now
|
|
148
|
+
|
|
149
|
+
calls.must.equal(1);
|
|
150
|
+
|
|
151
|
+
// flush must NOT leave (or schedule) another planned call behind
|
|
152
|
+
await wait(200);
|
|
153
|
+
calls.must.equal(1);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("is a no-op returning the last result when nothing is pending", async function() {
|
|
157
|
+
let calls = 0;
|
|
158
|
+
const fn = () => {
|
|
159
|
+
calls++;
|
|
160
|
+
return calls;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const throttled = throttle(fn, 100, { leading: true, trailing: true });
|
|
164
|
+
throttled(); // leading call runs immediately, nothing is pending afterwards
|
|
165
|
+
calls.must.equal(1);
|
|
166
|
+
|
|
167
|
+
throttled.flush();
|
|
168
|
+
calls.must.equal(1); // flush did not call fn again
|
|
169
|
+
});
|
|
170
|
+
});
|
|
136
171
|
});
|
package/src/throttle.ts
CHANGED
|
@@ -9,15 +9,15 @@ interface Opts {
|
|
|
9
9
|
trailing?: boolean;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
interface Extras {
|
|
12
|
+
interface Extras<R = unknown> {
|
|
13
13
|
/**
|
|
14
14
|
* Stops any planned calls (and resets the `time` array progress)
|
|
15
15
|
*/
|
|
16
16
|
cancel: () => void;
|
|
17
17
|
/**
|
|
18
|
-
* Immediately runs planned call.
|
|
18
|
+
* Immediately runs planned call, returning its result (or the last cached result if nothing is planned).
|
|
19
19
|
*/
|
|
20
|
-
flush: () =>
|
|
20
|
+
flush: () => R | undefined;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const defaultOptions: Required<Opts> = {
|
|
@@ -48,7 +48,7 @@ type CanReturnUndefined<F extends (...args: any[]) => any> = (...args: Parameter
|
|
|
48
48
|
*/
|
|
49
49
|
const throttle = <RT, F extends (...args: any[]) => RT>( // eslint-disable-line max-lines-per-function, @typescript-eslint/no-explicit-any
|
|
50
50
|
fn: F, time: number | [number, ...number[]] = 0, options?: Opts,
|
|
51
|
-
): CanReturnUndefined<F> & Extras => {
|
|
51
|
+
): CanReturnUndefined<F> & Extras<RT> => {
|
|
52
52
|
const opts: Required<Opts> = {
|
|
53
53
|
leading: options?.leading ?? defaultOptions.leading,
|
|
54
54
|
trailing: options?.trailing ?? defaultOptions.trailing,
|
|
@@ -109,7 +109,7 @@ const throttle = <RT, F extends (...args: any[]) => RT>( // eslint-disable-line
|
|
|
109
109
|
}, lastRun ? (lastTime - diffLastRun + 1) : lastTime);
|
|
110
110
|
|
|
111
111
|
return lastResult;
|
|
112
|
-
}) as (CanReturnUndefined<F> & Extras);
|
|
112
|
+
}) as (CanReturnUndefined<F> & Extras<RT>);
|
|
113
113
|
|
|
114
114
|
throttledFn.cancel = () => {
|
|
115
115
|
timeoutId !== null && clearTimeout(timeoutId);
|
|
@@ -130,11 +130,10 @@ const throttle = <RT, F extends (...args: any[]) => RT>( // eslint-disable-line
|
|
|
130
130
|
};
|
|
131
131
|
throttledFn.flush = () => {
|
|
132
132
|
if (timeoutId !== null) {
|
|
133
|
-
lastRun = Date.now();
|
|
134
|
-
lastResult = fn(...lastArgs);
|
|
135
133
|
clearTimeout(timeoutId);
|
|
136
134
|
timeoutId = null;
|
|
137
|
-
|
|
135
|
+
lastRun = Date.now();
|
|
136
|
+
lastResult = fn(...lastArgs);
|
|
138
137
|
}
|
|
139
138
|
return lastResult;
|
|
140
139
|
};
|
package/src/trimEnd.spec.ts
CHANGED
|
@@ -19,4 +19,9 @@ describe("trimEnd", () => {
|
|
|
19
19
|
must(trimEnd("abc", "abc")).equal("");
|
|
20
20
|
must(trimEnd("a", "a")).equal("");
|
|
21
21
|
});
|
|
22
|
+
|
|
23
|
+
it("should return the source unchanged when characters is empty", async () => {
|
|
24
|
+
must(trimEnd("hello", "")).equal("hello");
|
|
25
|
+
must(trimEnd("", "")).equal("");
|
|
26
|
+
});
|
|
22
27
|
});
|
package/src/trimEnd.ts
CHANGED
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
* trimEnd("!aaa!!", "!"); // "!aaa"
|
|
11
11
|
*/
|
|
12
12
|
const trimEnd = (source: string, characters: string): string => {
|
|
13
|
+
if (characters === "") {
|
|
14
|
+
return source;
|
|
15
|
+
}
|
|
13
16
|
let s = source;
|
|
14
17
|
while (s.endsWith(characters)) {
|
|
15
18
|
s = s.slice(0, -characters.length);
|
package/src/trimStart.spec.ts
CHANGED
|
@@ -19,4 +19,9 @@ describe("trimStart", () => {
|
|
|
19
19
|
must(trimStart("abc", "abc")).equal("");
|
|
20
20
|
must(trimStart("aaa", "a")).equal("");
|
|
21
21
|
});
|
|
22
|
+
|
|
23
|
+
it("should return the source unchanged when characters is empty", async () => {
|
|
24
|
+
must(trimStart("hello", "")).equal("hello");
|
|
25
|
+
must(trimStart("", "")).equal("");
|
|
26
|
+
});
|
|
22
27
|
});
|
package/src/trimStart.ts
CHANGED
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
* trimStart("!!aaa!", "!"); // "aaa!"
|
|
11
11
|
*/
|
|
12
12
|
const trimStart = (source: string, characters: string): string => {
|
|
13
|
+
if (characters === "") {
|
|
14
|
+
return source;
|
|
15
|
+
}
|
|
13
16
|
let s = source;
|
|
14
17
|
while (s.startsWith(characters)) {
|
|
15
18
|
s = s.slice(characters.length);
|
package/src/waitFor.spec.ts
CHANGED
|
@@ -53,6 +53,19 @@ describe("waitFor", () => {
|
|
|
53
53
|
must(Date.now() - start).be.gte(300);
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
+
it("falls back to the default interval when other options are given without `interval`", async () => {
|
|
57
|
+
const spy = createSpy(() => false);
|
|
58
|
+
|
|
59
|
+
// only `timeout` is provided, `interval` is omitted -> should still use the 50ms default.
|
|
60
|
+
// BUG: implementation reads the raw `options.interval` (undefined) instead of the merged
|
|
61
|
+
// `opts.interval`, so setTimeout fires at 0ms and the check busy-loops.
|
|
62
|
+
waitFor(spy, { timeout: 250 }).catch(() => { /* expected to reject on timeout */ });
|
|
63
|
+
await wait(120);
|
|
64
|
+
|
|
65
|
+
// with the default 50ms interval checks land at ~0/50/100ms => ~3 calls by now
|
|
66
|
+
must(spy.__spy.calls.length).be.lte(4);
|
|
67
|
+
});
|
|
68
|
+
|
|
56
69
|
it("time outs after given time", async () => {
|
|
57
70
|
const spy = createSpy(() => false);
|
|
58
71
|
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
@@ -100,6 +113,54 @@ describe("waitFor", () => {
|
|
|
100
113
|
});
|
|
101
114
|
});
|
|
102
115
|
|
|
116
|
+
describe("error stack traces point at the caller", () => {
|
|
117
|
+
// Helper with a recognizable name so we can assert it appears in the stack.
|
|
118
|
+
const callerOfWaitFor = <T>(...args: Parameters<typeof waitFor<T>>): Promise<T> => {
|
|
119
|
+
return waitFor<T>(...args);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
it("on timeout", async () => {
|
|
123
|
+
await callerOfWaitFor(() => false, { interval: 40, timeout: 100 }).then(() => {
|
|
124
|
+
throw new Error("Should not resolve");
|
|
125
|
+
}, (e: unknown) => {
|
|
126
|
+
must((e as Error).message).equal("[waitFor] Timeout");
|
|
127
|
+
must((e as Error).stack).include("callerOfWaitFor");
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("on max tries reached", async () => {
|
|
132
|
+
await callerOfWaitFor(() => false, { interval: 40, maxTries: 2 }).then(() => {
|
|
133
|
+
throw new Error("Should not resolve");
|
|
134
|
+
}, (e: unknown) => {
|
|
135
|
+
must((e as Error).message).equal("[waitFor] Max tries reached");
|
|
136
|
+
must((e as Error).stack).include("callerOfWaitFor");
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("on invalid maxTries", async () => {
|
|
141
|
+
await callerOfWaitFor(() => null, { maxTries: 0 }).then(() => {
|
|
142
|
+
throw new Error("Should not resolve");
|
|
143
|
+
}, (e: unknown) => {
|
|
144
|
+
must(e).instanceOf(TypeError);
|
|
145
|
+
must((e as Error).stack).include("callerOfWaitFor");
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("on check function throwing, and preserves original error as cause", async () => {
|
|
150
|
+
const original = new Error("boom");
|
|
151
|
+
await callerOfWaitFor(() => {
|
|
152
|
+
throw original;
|
|
153
|
+
}, { interval: 40 }).then(() => {
|
|
154
|
+
throw new Error("Should not resolve");
|
|
155
|
+
}, (e: unknown) => {
|
|
156
|
+
must((e as Error).message).equal("[waitFor] check function threw an error");
|
|
157
|
+
must((e as Error).stack).include("callerOfWaitFor");
|
|
158
|
+
must((e as Error & { cause?: unknown }).cause).equal(original);
|
|
159
|
+
must((e as Error & { details?: { error?: unknown } }).details!.error).equal(original);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
103
164
|
describe("treats most falsy values as succeeded check", () => {
|
|
104
165
|
it("numeric zero", async () => {
|
|
105
166
|
const result = await waitFor(() => 0);
|