@bodil/monkey-business 0.3.4 → 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.4",
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,6 +20,36 @@
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": {
@@ -37,22 +67,44 @@
37
67
  "@bodil/opt-vitest": "^1.1.0",
38
68
  "@eslint/eslintrc": "^3.3.7",
39
69
  "@eslint/js": "^10.0.1",
40
- "@typescript-eslint/eslint-plugin": "^8.69.0",
41
- "@typescript-eslint/parser": "^8.69.0",
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": "^10.10.0",
74
+ "eslint": "^10.14.0",
44
75
  "eslint-config-prettier": "^10.1.8",
45
- "eslint-plugin-jsdoc": "^64.3.5",
46
- "globals": "^17.12.0",
47
- "npm-run-all2": "^9.0.3",
48
- "prettier": "^3.9.6",
76
+ "eslint-plugin-jsdoc": "^64.5.4",
77
+ "globals": "^17.14.0",
78
+ "npm-run-all2": "^9.0.4",
79
+ "prettier": "^3.9.10",
49
80
  "typedoc": "^0.28.20",
50
81
  "typedoc-plugin-extras": "^4.0.1",
51
82
  "typedoc-plugin-mdn-links": "^5.1.1",
52
83
  "typedoc-theme-fresh": "^0.2.3",
53
84
  "typescript": "^6.0.3",
54
- "vite": "^7.3.6",
55
- "vitest": "^4.1.11"
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
+ ]
56
108
  },
57
109
  "scripts": {
58
110
  "build": "tsc",
@@ -62,6 +114,6 @@
62
114
  "check": "run-p check:tsc check:eslint",
63
115
  "doc": "typedoc",
64
116
  "doc:readthedocs": "typedoc --out $READTHEDOCS_OUTPUT/html",
65
- "prepublish": "tsc"
117
+ "prepublish": "rm -rf dist && run-p check test build"
66
118
  }
67
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,6 +117,14 @@ 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
+
105
128
  test("Iterator.chunks", () => {
106
129
  expect(Array.from(Iterator.from([1, 2, 3, 4]).chunks(2))).toEqual([
107
130
  [1, 2],
@@ -112,12 +135,12 @@ test("Iterator.chunks", () => {
112
135
  });
113
136
 
114
137
  test("Iterator.partition", () => {
115
- 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([
116
139
  [1, 2],
117
140
  [3, 4],
118
141
  ]);
119
142
 
120
- 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]]);
121
144
  });
122
145
 
123
146
  test("Iterator.frontBiasedPartition", () => {
@@ -170,8 +193,8 @@ test("Iterator.equals", () => {
170
193
  expect(
171
194
  Iterator.from([1, 2, 3]).equals(
172
195
  ["1", "2", "3"],
173
- (a: number, b: string) => a.toString() === b
174
- )
196
+ (a: number, b: string) => a.toString() === b,
197
+ ),
175
198
  ).toBeTruthy();
176
199
  });
177
200