@bodil/monkey-business 0.3.3 → 0.3.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bodil/monkey-business",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "Let's give JS core types a polish",
5
5
  "homepage": "https://codeberg.org/bodil/monkey-business",
6
6
  "repository": {
@@ -20,38 +20,91 @@
20
20
  "types": "./dist/index.d.ts",
21
21
  "import": "./dist/index.js"
22
22
  }
23
+ },
24
+ "./array": {
25
+ "import": {
26
+ "types": "./dist/array.d.ts",
27
+ "import": "./dist/array.js"
28
+ }
29
+ },
30
+ "./iterator": {
31
+ "import": {
32
+ "types": "./dist/iterator.d.ts",
33
+ "import": "./dist/iterator.js"
34
+ }
35
+ },
36
+ "./map": {
37
+ "import": {
38
+ "types": "./dist/map.d.ts",
39
+ "import": "./dist/map.js"
40
+ }
41
+ },
42
+ "./object": {
43
+ "import": {
44
+ "types": "./dist/object.d.ts",
45
+ "import": "./dist/object.js"
46
+ }
47
+ },
48
+ "./weakmap": {
49
+ "import": {
50
+ "types": "./dist/weakmap.d.ts",
51
+ "import": "./dist/weakmap.js"
52
+ }
23
53
  }
24
54
  },
25
55
  "publishConfig": {
26
56
  "access": "public"
27
57
  },
28
58
  "dependencies": {
29
- "@bodil/core": "^0.5.2",
30
- "@bodil/opt": "^0.4.3",
59
+ "@bodil/core": "^0.5.7",
60
+ "@bodil/opt": "^1.0.0",
31
61
  "@types/define-properties": "^1.1.5",
32
62
  "define-properties": "^1.2.1",
33
- "es-iterator-helpers": "^1.2.2",
63
+ "es-iterator-helpers": "^1.4.0",
34
64
  "es-shim-unscopables": "^1.1.0"
35
65
  },
36
66
  "devDependencies": {
37
67
  "@bodil/opt-vitest": "^1.1.0",
38
- "@eslint/eslintrc": "^3.3.3",
39
- "@eslint/js": "^9.39.2",
40
- "@typescript-eslint/eslint-plugin": "^8.52.0",
41
- "@typescript-eslint/parser": "^8.52.0",
68
+ "@eslint/eslintrc": "^3.3.7",
69
+ "@eslint/js": "^10.0.1",
70
+ "@ianvs/prettier-plugin-sort-imports": "^4.7.1",
71
+ "@typescript-eslint/eslint-plugin": "^8.72.2",
72
+ "@typescript-eslint/parser": "^8.72.2",
42
73
  "@typhonjs-typedoc/ts-lib-docs": "^2024.12.25",
43
- "eslint": "^9.39.2",
74
+ "eslint": "^10.14.0",
44
75
  "eslint-config-prettier": "^10.1.8",
45
- "eslint-plugin-jsdoc": "^61.5.0",
46
- "globals": "^17.0.0",
47
- "npm-run-all2": "^8.0.4",
48
- "prettier": "^3.7.4",
49
- "typedoc": "^0.28.15",
76
+ "eslint-plugin-jsdoc": "^64.5.4",
77
+ "globals": "^17.14.0",
78
+ "npm-run-all2": "^9.0.4",
79
+ "prettier": "^3.9.10",
80
+ "typedoc": "^0.28.20",
50
81
  "typedoc-plugin-extras": "^4.0.1",
51
- "typedoc-plugin-mdn-links": "^5.0.10",
82
+ "typedoc-plugin-mdn-links": "^5.1.1",
52
83
  "typedoc-theme-fresh": "^0.2.3",
53
- "typescript": "^5.9.3",
54
- "vitest": "^4.0.16"
84
+ "typescript": "^6.0.3",
85
+ "vite": "^7.3.8",
86
+ "vitest": "^4.1.12"
87
+ },
88
+ "prettier": {
89
+ "editorconfig": true,
90
+ "plugins": [
91
+ "@ianvs/prettier-plugin-sort-imports"
92
+ ],
93
+ "importOrderParserPlugins": [
94
+ "typescript",
95
+ "decorators"
96
+ ],
97
+ "importOrderTypeScriptVersion": "5.0.0",
98
+ "importOrder": [
99
+ "",
100
+ "<BUILTIN_MODULES>",
101
+ "",
102
+ "<THIRD_PARTY_MODULES>",
103
+ "",
104
+ "^[.](?!.*[?]inline$)",
105
+ "",
106
+ "[?]inline$"
107
+ ]
55
108
  },
56
109
  "scripts": {
57
110
  "build": "tsc",
@@ -61,6 +114,6 @@
61
114
  "check": "run-p check:tsc check:eslint",
62
115
  "doc": "typedoc",
63
116
  "doc:readthedocs": "typedoc --out $READTHEDOCS_OUTPUT/html",
64
- "prepublish": "tsc"
117
+ "prepublish": "rm -rf dist && run-p check test build"
65
118
  }
66
119
  }
package/src/array.ts ADDED
@@ -0,0 +1,128 @@
1
+ import { bisectHigh, bisectLow, type OrderFn } from "@bodil/core/order";
2
+ import type { Present } from "@bodil/core/types";
3
+ import { None, Option, type Some } from "@bodil/opt";
4
+
5
+ import { get as objectGet } from "./object";
6
+
7
+ /**
8
+ * Insert `value` at `index` in the array.
9
+ */
10
+ export function insert<T>(this: Array<T>, value: T, index: number): Array<T> {
11
+ if (!Number.isInteger(index)) {
12
+ throw new TypeError(`Array.insert(): index ${index} is not an integer`);
13
+ }
14
+ if (index > this.length || index < 0) {
15
+ throw new RangeError(`Array.insert: index ${index} out of bounds`);
16
+ }
17
+ this.splice(index, 0, value);
18
+ return this;
19
+ }
20
+
21
+ /**
22
+ * Insert `value` into the array at the correct position according to
23
+ * the ordering function `cmp`. The array must already be ordered
24
+ * accordingly.
25
+ *
26
+ * If `duplicates` is `"low"`, the correct position in case of
27
+ * duplicates is considered to be the index of the first occurrence of a
28
+ * duplicate. If it is `"high"`, which is the default, the correct
29
+ * position is considered to be the index after the last occurrence of a
30
+ * duplicate.
31
+ */
32
+ export function insertOrdered<T>(
33
+ this: Array<T>,
34
+ value: T,
35
+ cmp: OrderFn<T>,
36
+ duplicates: "low" | "high" = "high",
37
+ ): Array<T> {
38
+ return (insert<T>).call(
39
+ this,
40
+ value,
41
+ (duplicates === "low" ? bisectLow : bisectHigh)(cmp, this, value),
42
+ );
43
+ }
44
+
45
+ /**
46
+ * Remove the first occurrence of a given value from an array.
47
+ *
48
+ * Returns an {@link Option} containing the removed value, or {@link None} if
49
+ * no matches were found.
50
+ */
51
+ export function remove<T>(this: Array<T>, value: T): Option<T> {
52
+ return (removeIndex<T>).call(this, this.indexOf(value));
53
+ }
54
+
55
+ /**
56
+ * Remove the first value for which the provided predicate function
57
+ * returns `true` from the array.
58
+ *
59
+ * Returns an {@link Option} containing the removed value, or {@link None} if
60
+ * no matches were found.
61
+ */
62
+ export function removeFn<T>(
63
+ this: Array<T>,
64
+ predicate: (value: T, index: number, obj: Array<T>) => boolean,
65
+ ): Option<T> {
66
+ return (removeIndex<T>).call(this, this.findIndex(predicate));
67
+ }
68
+
69
+ /**
70
+ * Remove the value at the given index from the array.
71
+ *
72
+ * Returns an {@link Option} containing the removed value, or {@link None} if
73
+ * the index was out of bounds.
74
+ */
75
+ export function removeIndex<T>(this: Array<T>, index: number): Option<T> {
76
+ if (!Number.isInteger(index)) {
77
+ throw new TypeError(`Array.removeIndex(): index ${index} is not an integer`);
78
+ }
79
+ if (index < 0 || index >= this.length) {
80
+ return None;
81
+ }
82
+ const removed = this.splice(index, 1).pop();
83
+ return Option.from(removed);
84
+ }
85
+
86
+ /**
87
+ * Return a copy of the array with any `undefined` values removed.
88
+ */
89
+ export function present<T>(this: Array<T>): Array<Present<T>> {
90
+ return this.filter((value: T): value is Present<T> => value !== undefined);
91
+ }
92
+
93
+ /**
94
+ * Return a copy of the array with any `null` or `undefined` values removed.
95
+ */
96
+ export function nonNullable<T>(this: Array<T>): Array<NonNullable<T>> {
97
+ return this.filter(
98
+ (value: T): value is NonNullable<T> => value !== undefined && value !== null,
99
+ );
100
+ }
101
+
102
+ /**
103
+ * Checked index operator. Returns {@link None} if an index isn't
104
+ * present, even for sparse arrays. Returns a {@link Some} if the index
105
+ * is present, even if the value is `undefined`.
106
+ */
107
+ export function get<T>(this: Array<T>, index: number): Option<T> {
108
+ return objectGet(this, index);
109
+ }
110
+
111
+ /**
112
+ * Test whether a value is an array with the same contents as this one.
113
+ */
114
+ export function equals<T, U = unknown>(
115
+ this: Array<T>,
116
+ other: unknown,
117
+ equalsFn: (a: T, b: U) => boolean = Object.is,
118
+ ): other is Array<T> {
119
+ if (!Array.isArray(other) || this.length !== other.length) {
120
+ return false;
121
+ }
122
+ for (let index = 0; index < this.length; index++) {
123
+ if (!equalsFn(this[index], other[index])) {
124
+ return false;
125
+ }
126
+ }
127
+ return true;
128
+ }
@@ -0,0 +1,24 @@
1
+ declare module "es-iterator-helpers/Iterator.concat/polyfill" {
2
+ function getPolyfill(): <T>(...inputs: Array<Iterable<T>>) => IteratorObject<T>;
3
+ export default getPolyfill;
4
+ }
5
+
6
+ declare module "es-iterator-helpers/Iterator.zip/polyfill" {
7
+ function getPolyfill(): <T>(inputs: Iterable<Iterable<T>>) => IteratorObject<Array<T>>;
8
+ export default getPolyfill;
9
+ }
10
+
11
+ declare module "es-iterator-helpers/Iterator.zipKeyed/polyfill" {
12
+ function getPolyfill(): <T>(
13
+ inputs: Record<string, Iterable<T>>,
14
+ ) => IteratorObject<Record<string, T>>;
15
+ export default getPolyfill;
16
+ }
17
+
18
+ declare module "es-iterator-helpers/Iterator.prototype.chunks/polyfill" {
19
+ function getPolyfill(): <T>(
20
+ this: Iterator<T>,
21
+ size: number,
22
+ ) => IteratorObject<Array<T>, undefined>;
23
+ export default getPolyfill;
24
+ }
package/src/index.test.ts CHANGED
@@ -1,9 +1,8 @@
1
- import { test, expect, expectTypeOf } from "vitest";
2
- import type { Option } from "@bodil/opt";
3
1
  import { order as Order } from "@bodil/core";
2
+ import type { Option } from "@bodil/opt";
3
+ import { expect, expectTypeOf, test } from "vitest";
4
4
 
5
5
  import ".";
6
-
7
6
  import "@bodil/opt-vitest";
8
7
 
9
8
  const Iterator = globalThis.Iterator;
@@ -52,6 +51,22 @@ test("Map.getOrSet", () => {
52
51
  expect(map.getOrInsertComputed("bar", defaultValue)).toEqual("Robert");
53
52
  expect(map.getOrInsertComputed("Mike", defaultValueCat)).toEqual("RobertMike");
54
53
  expect(map.get("bar")).toEqual("Robert");
54
+ expect(map.getOrSet("foo", defaultValue)).toEqual("Joe");
55
+ expect(map.getOrSet("baz", defaultValue)).toEqual("Robert");
56
+ expect(map.get("baz")).toEqual("Robert");
57
+ });
58
+
59
+ test("WeakMap.getOrSet", () => {
60
+ const defaultValue = () => "Robert";
61
+ const weakmap = new WeakMap();
62
+ const foo = {};
63
+ const bar = {};
64
+ weakmap.set(foo, "Joe");
65
+ expect(weakmap.getOrInsertComputed(foo, defaultValue)).toEqual("Joe");
66
+ expect(weakmap.getOrInsertComputed(bar, defaultValue)).toEqual("Robert");
67
+ expect(weakmap.getOrSet(foo, defaultValue)).toEqual("Joe");
68
+ expect(weakmap.getOrSet(bar, defaultValue)).toEqual("Robert");
69
+ expect(weakmap.get(bar)).toEqual("Robert");
55
70
  });
56
71
 
57
72
  test("Map.remove", () => {
@@ -102,13 +117,30 @@ test("Array.filterPresent", () => {
102
117
  expectTypeOf(cleanArray).toExtend<Array<number>>();
103
118
  });
104
119
 
120
+ test("Array.filterNonNullable", () => {
121
+ const array: Array<number | null | undefined> = [1, null, 2, undefined, 3, null, 4];
122
+ const cleanArray = array.nonNullable();
123
+ expect(cleanArray.some((v) => v === undefined || v === null)).toBeFalsy();
124
+ expect(cleanArray).toEqual([1, 2, 3, 4]);
125
+ expectTypeOf(cleanArray).toExtend<Array<number>>();
126
+ });
127
+
128
+ test("Iterator.chunks", () => {
129
+ expect(Array.from(Iterator.from([1, 2, 3, 4]).chunks(2))).toEqual([
130
+ [1, 2],
131
+ [3, 4],
132
+ ]);
133
+
134
+ expect(Array.from(Iterator.from([1, 2, 3]).chunks(2))).toEqual([[1, 2], [3]]);
135
+ });
136
+
105
137
  test("Iterator.partition", () => {
106
- expect(Array.from(Iterator.from([1, 2, 3, 4]).partition(2))).toEqual([
138
+ expect(Array.from(Iterator.from([1, 2, 3, 4]).chunks(2))).toEqual([
107
139
  [1, 2],
108
140
  [3, 4],
109
141
  ]);
110
142
 
111
- expect(Array.from(Iterator.from([1, 2, 3]).partition(2))).toEqual([[1, 2], [3]]);
143
+ expect(Array.from(Iterator.from([1, 2, 3]).chunks(2))).toEqual([[1, 2], [3]]);
112
144
  });
113
145
 
114
146
  test("Iterator.frontBiasedPartition", () => {
@@ -161,8 +193,8 @@ test("Iterator.equals", () => {
161
193
  expect(
162
194
  Iterator.from([1, 2, 3]).equals(
163
195
  ["1", "2", "3"],
164
- (a: number, b: string) => a.toString() === b
165
- )
196
+ (a: number, b: string) => a.toString() === b,
197
+ ),
166
198
  ).toBeTruthy();
167
199
  });
168
200