@tim-code/my-util 0.1.0 → 0.1.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/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  This library includes common util functions. It does not have any dependencies.
4
4
 
5
- This may include rewritten functionality from Lodash in the future (i.e. orderBy). groupBy should be available when using Node 22.
5
+ In combination with Object.groupBy (available when using Node 22), this should be sufficient for replacing the most useful functionality from lodash.
6
6
 
7
7
  ## First Time Setup
8
8
 
9
- `npm install`
9
+ `npm install @tim-code/my-util`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tim-code/my-util",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "author": "",
package/src/array.js CHANGED
@@ -28,28 +28,6 @@ export function unique(array) {
28
28
  return [...new Set(array)]
29
29
  }
30
30
 
31
- /**
32
- * Mutates the passed in object by calling callback on each of its values.
33
- * @param {Object} object
34
- * @param {Function} callback (value, key, object) => newValue // note if not changing value, should return value
35
- * @returns {Object}
36
- */
37
- export function mutateValues(object, callback) {
38
- for (const key in object) {
39
- object[key] = callback(object[key], key, object)
40
- }
41
- return object
42
- }
43
-
44
- /**
45
- * Creates a function that accesses an object's value at key.
46
- * @param {string} key
47
- * @returns {any}
48
- */
49
- export function via(key) {
50
- return (object) => object[key]
51
- }
52
-
53
31
  // sorts undefined and null to the end if applicable
54
32
  function compareUndefinedNull(a, b) {
55
33
  if (b === undefined || b === null) {
package/src/array.test.js CHANGED
@@ -2,9 +2,7 @@
2
2
  import { describe, expect, it, jest } from "@jest/globals"
3
3
  import { findClosest } from "./array.js"
4
4
 
5
- const { chunk, unique, mutateValues, ascending, descending, multilevel, via } = await import(
6
- "./array.js"
7
- )
5
+ const { chunk, unique, ascending, descending, multilevel } = await import("./array.js")
8
6
 
9
7
  describe("chunk", () => {
10
8
  it("splits array into chunks of specified size", () => {
@@ -73,42 +71,6 @@ describe("unique", () => {
73
71
  })
74
72
  })
75
73
 
76
- describe("mutateValues", () => {
77
- it("mutates values in the object using the callback", () => {
78
- const obj = { a: 1, b: 2 }
79
- const result = mutateValues(obj, (v) => v * 2)
80
- expect(result).toEqual({ a: 2, b: 4 })
81
- expect(obj).toBe(result) // should mutate in place
82
- })
83
-
84
- it("callback receives value, key, and object", () => {
85
- const obj = { x: 1 }
86
- const cb = jest.fn((v) => v + 1)
87
- mutateValues(obj, cb)
88
- expect(cb).toHaveBeenCalledWith(1, "x", obj)
89
- })
90
-
91
- it("returns the same object reference", () => {
92
- const obj = { foo: "bar" }
93
- const returned = mutateValues(obj, (v) => v)
94
- expect(returned).toBe(obj)
95
- })
96
-
97
- it("handles empty object", () => {
98
- const obj = {}
99
- expect(mutateValues(obj, (v) => v)).toEqual({})
100
- })
101
-
102
- it("mutates inherited enumerable properties", () => {
103
- const proto = { inherited: 1 }
104
- const obj = Object.create(proto)
105
- obj.own = 2
106
- const result = mutateValues(obj, (v) => v + 1)
107
- expect(result.own).toBe(3)
108
- expect(result.inherited).toBe(2)
109
- })
110
- })
111
-
112
74
  describe("ascending", () => {
113
75
  it("sorts primitives ascending, undefined/null at end", () => {
114
76
  const arr = [undefined, null, 3, 1, 2]
@@ -258,32 +220,6 @@ describe("multilevel", () => {
258
220
  })
259
221
  })
260
222
 
261
- describe("via", () => {
262
- it("returns a function that accesses the given key", () => {
263
- const getFoo = via("foo")
264
- expect(getFoo({ foo: 42 })).toBe(42)
265
- expect(getFoo({ foo: "bar" })).toBe("bar")
266
- })
267
-
268
- it("returns undefined if the key does not exist", () => {
269
- const getX = via("x")
270
- expect(getX({})).toBeUndefined()
271
- expect(getX({ y: 1 })).toBeUndefined()
272
- })
273
-
274
- it("works with numeric keys", () => {
275
- const get0 = via(0)
276
- expect(get0([10, 20])).toBe(10)
277
- expect(get0({ 0: "zero" })).toBe("zero")
278
- })
279
-
280
- it("returns undefined if object is missing", () => {
281
- const getFoo = via("foo")
282
- expect(() => getFoo(undefined)).toThrow(TypeError)
283
- expect(() => getFoo(null)).toThrow(TypeError)
284
- })
285
- })
286
-
287
223
  describe("findClosest", () => {
288
224
  it("returns the closest value by absolute difference (default comparator)", () => {
289
225
  expect(findClosest([1, 5, 10], 6)).toBe(5)
package/src/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./array.js"
2
2
  export * from "./math.js"
3
+ export * from "./object.js"
3
4
  export * from "./promise.js"
4
5
  export * from "./time.js"
package/src/math.js CHANGED
@@ -9,6 +9,20 @@ export function mod(number, modulus) {
9
9
  return ((number % modulus) + modulus) % modulus
10
10
  }
11
11
 
12
+ /**
13
+ * Given two points, returns a function
14
+ * This function, given an "x" value, returns a "y" value that is on the same line as the first two points.
15
+ * @param {[number, number]} firstPoint
16
+ * @param {[number, number]} secondPoint
17
+ * @returns {Function}
18
+ */
19
+ export function line([x1, y1], [x2, y2]) {
20
+ const m = (y2 - y1) / (x2 - x1)
21
+ // m * x1 + b = y1
22
+ const b = y1 - m * x1
23
+ return (x) => m * x + b
24
+ }
25
+
12
26
  /**
13
27
  * Prepend a plus to a number or string if positive.
14
28
  * @param {number|string} number Or string
package/src/math.test.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "@jest/globals"
2
- const { mod, formatPlus } = await import("./math.js")
2
+ const { mod, formatPlus, line } = await import("./math.js")
3
3
 
4
4
  describe("mod", () => {
5
5
  it("returns n when n is less than m and n is non-negative", () => {
@@ -58,6 +58,52 @@ describe("mod", () => {
58
58
  })
59
59
  })
60
60
 
61
+ describe("line", () => {
62
+ it("returns a function that produces y for given x on the line through two points", () => {
63
+ const f = line([0, 0], [1, 1])
64
+ expect(f(0)).toBe(0)
65
+ expect(f(1)).toBe(1)
66
+ expect(f(2)).toBe(2)
67
+ expect(f(-1)).toBe(-1)
68
+ })
69
+
70
+ it("handles non-origin, non-unit slope", () => {
71
+ const f = line([1, 2], [3, 6]) // slope 2, intercept 0
72
+ expect(f(1)).toBe(2)
73
+ expect(f(2)).toBe(4)
74
+ expect(f(3)).toBe(6)
75
+ expect(f(0)).toBe(0)
76
+ })
77
+
78
+ it("handles negative slope", () => {
79
+ const f = line([0, 0], [2, -2]) // slope -1
80
+ expect(f(1)).toBe(-1)
81
+ expect(f(2)).toBe(-2)
82
+ expect(f(-2)).toBe(2)
83
+ })
84
+
85
+ it("handles horizontal line", () => {
86
+ const f = line([1, 5], [3, 5]) // slope 0
87
+ expect(f(0)).toBe(5)
88
+ expect(f(100)).toBe(5)
89
+ expect(f(-100)).toBe(5)
90
+ })
91
+
92
+ it("handles vertical line by returning NaN", () => {
93
+ const f = line([2, 1], [2, 5])
94
+ expect(f(2)).toBeNaN()
95
+ expect(f(3)).toBeNaN()
96
+ expect(f(1)).toBeNaN()
97
+ })
98
+
99
+ it("works with negative coordinates", () => {
100
+ const f = line([-1, -2], [1, 2]) // slope 2
101
+ expect(f(-1)).toBe(-2)
102
+ expect(f(0)).toBe(0)
103
+ expect(f(1)).toBe(2)
104
+ })
105
+ })
106
+
61
107
  describe("formatPlus", () => {
62
108
  it("prepends a plus for positive numbers", () => {
63
109
  expect(formatPlus(1)).toBe("+1")
package/src/object.js ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Mutates the passed in object by calling callback on each of its values.
3
+ * @param {Object} object
4
+ * @param {Function} callback (value, key, object) => newValue // note if not changing value, should return value
5
+ * @returns {Object}
6
+ */
7
+ export function mutateValues(object, callback) {
8
+ const keys = Object.keys(object)
9
+ for (const key of keys) {
10
+ object[key] = callback(object[key], key, object)
11
+ }
12
+ return object
13
+ }
14
+
15
+ /**
16
+ * Mutates the passed object by removing any keys (`key in object`) that have a value of undefined.
17
+ * This is useful when the presence of a key alone causes something else to happen, but an undefined value is unexpected.
18
+ * In general, having objects with undefined values should not be encouraged but can happen as a byproduct of a code flow.
19
+ * @param {Object} object
20
+ * @returns {Object}
21
+ */
22
+ export function deleteUndefinedValues(object) {
23
+ const keys = Object.keys(object)
24
+ for (const key of keys) {
25
+ if (key in object && object[key] === undefined) {
26
+ delete object[key]
27
+ }
28
+ }
29
+ return object
30
+ }
31
+
32
+ /**
33
+ * Creates a function that accesses an object's value at key.
34
+ * @param {string} key
35
+ * @returns {Function}
36
+ */
37
+ export function via(key) {
38
+ return (object) => object[key]
39
+ }
40
+
41
+ /**
42
+ * Creates a function that checks if the passed object contains the initial template.
43
+ * This means for each key in the template, the passed object has the same (===) value.
44
+ * @param {Object} template
45
+ * @returns {Function}
46
+ */
47
+ export function like(template) {
48
+ const keys = Object.keys(template)
49
+ return (object) => {
50
+ for (const key of keys) {
51
+ if (object[key] !== template[key]) {
52
+ return false
53
+ }
54
+ }
55
+ return true
56
+ }
57
+ }
@@ -0,0 +1,161 @@
1
+ /* eslint-disable no-restricted-syntax */
2
+ import { jest } from "@jest/globals"
3
+ import { deleteUndefinedValues, like, mutateValues, via } from "./object.js"
4
+
5
+ describe("mutateValues", () => {
6
+ it("mutates values in the object using the callback", () => {
7
+ const obj = { a: 1, b: 2 }
8
+ const result = mutateValues(obj, (v) => v * 2)
9
+ expect(result).toEqual({ a: 2, b: 4 })
10
+ expect(obj).toBe(result) // should mutate in place
11
+ })
12
+
13
+ it("callback receives value, key, and object", () => {
14
+ const obj = { x: 1 }
15
+ const cb = jest.fn((v) => v + 1)
16
+ mutateValues(obj, cb)
17
+ expect(cb).toHaveBeenCalledWith(1, "x", obj)
18
+ })
19
+
20
+ it("returns the same object reference", () => {
21
+ const obj = { foo: "bar" }
22
+ const returned = mutateValues(obj, (v) => v)
23
+ expect(returned).toBe(obj)
24
+ })
25
+
26
+ it("handles empty object", () => {
27
+ const obj = {}
28
+ expect(mutateValues(obj, (v) => v)).toEqual({})
29
+ })
30
+
31
+ it("does not mutate inherited enumerable properties", () => {
32
+ const proto = { inherited: 1 }
33
+ const obj = Object.create(proto)
34
+ obj.own = 2
35
+ const result = mutateValues(obj, (v) => v + 1)
36
+ expect(result.own).toBe(3)
37
+ expect(result.inherited).toBe(1)
38
+ })
39
+ })
40
+
41
+ describe("deleteUndefinedValues", () => {
42
+ it("removes keys with undefined values", () => {
43
+ const obj = { a: 1, b: undefined, c: 3 }
44
+ const result = deleteUndefinedValues(obj)
45
+ expect(result).toEqual({ a: 1, c: 3 })
46
+ expect(obj).toBe(result)
47
+ expect("b" in result).toBe(false)
48
+ })
49
+
50
+ it("does not remove keys with null or falsy non-undefined values", () => {
51
+ const obj = { a: null, b: 0, c: false, d: "", e: undefined }
52
+ deleteUndefinedValues(obj)
53
+ expect(obj).toEqual({ a: null, b: 0, c: false, d: "" })
54
+ })
55
+
56
+ it("does nothing if no undefined values are present", () => {
57
+ const obj = { a: 1, b: 2 }
58
+ const result = deleteUndefinedValues(obj)
59
+ expect(result).toEqual({ a: 1, b: 2 })
60
+ })
61
+
62
+ it("handles empty object", () => {
63
+ const obj = {}
64
+ expect(deleteUndefinedValues(obj)).toEqual({})
65
+ })
66
+
67
+ it("removes own properties set to undefined but not inherited ones", () => {
68
+ const proto = { inherited: undefined }
69
+ const obj = Object.create(proto)
70
+ obj.own = undefined
71
+ deleteUndefinedValues(obj)
72
+ expect("own" in obj).toBe(false)
73
+ // inherited property remains accessible via prototype
74
+ expect(obj.inherited).toBeUndefined()
75
+ // but is not an own property
76
+ expect(Object.hasOwn(obj, "inherited")).toBe(false)
77
+ })
78
+ })
79
+
80
+ describe("via", () => {
81
+ it("returns a function that accesses the given key", () => {
82
+ const getFoo = via("foo")
83
+ expect(getFoo({ foo: 42 })).toBe(42)
84
+ expect(getFoo({ foo: "bar" })).toBe("bar")
85
+ })
86
+
87
+ it("returns undefined if the key does not exist", () => {
88
+ const getX = via("x")
89
+ expect(getX({})).toBeUndefined()
90
+ expect(getX({ y: 1 })).toBeUndefined()
91
+ })
92
+
93
+ it("works with numeric keys", () => {
94
+ const get0 = via(0)
95
+ expect(get0([10, 20])).toBe(10)
96
+ expect(get0({ 0: "zero" })).toBe("zero")
97
+ })
98
+
99
+ it("returns undefined if object is missing", () => {
100
+ const getFoo = via("foo")
101
+ expect(() => getFoo(undefined)).toThrow(TypeError)
102
+ expect(() => getFoo(null)).toThrow(TypeError)
103
+ })
104
+ })
105
+
106
+ describe("contains", () => {
107
+ it("returns true when object contains all template keys with same values", () => {
108
+ const template = { a: 1, b: 2 }
109
+ const fn = like(template)
110
+ expect(fn({ a: 1, b: 2, c: 3 })).toBe(true)
111
+ expect(fn({ a: 1, b: 2 })).toBe(true)
112
+ })
113
+
114
+ it("returns false if any template key is missing", () => {
115
+ const template = { a: 1, b: 2 }
116
+ const fn = like(template)
117
+ expect(fn({ a: 1 })).toBe(false)
118
+ expect(fn({ b: 2 })).toBe(false)
119
+ expect(fn({})).toBe(false)
120
+ })
121
+
122
+ it("returns false if any template key has a different value", () => {
123
+ const template = { a: 1, b: 2 }
124
+ const fn = like(template)
125
+ expect(fn({ a: 1, b: 3 })).toBe(false)
126
+ expect(fn({ a: 2, b: 2 })).toBe(false)
127
+ expect(fn({ a: 2, b: 3 })).toBe(false)
128
+ })
129
+
130
+ it("works with empty template (always true)", () => {
131
+ const fn = like({})
132
+ expect(fn({})).toBe(true)
133
+ expect(fn({ a: 1 })).toBe(true)
134
+ expect(fn({ a: undefined })).toBe(true)
135
+ })
136
+
137
+ it("does not require object to have only template keys", () => {
138
+ const template = { x: 5 }
139
+ const fn = like(template)
140
+ expect(fn({ x: 5, y: 10 })).toBe(true)
141
+ })
142
+
143
+ it("uses strict equality (===) for comparison", () => {
144
+ const template = { a: 0 }
145
+ const fn = like(template)
146
+ expect(fn({ a: false })).toBe(false)
147
+ expect(fn({ a: "0" })).toBe(false)
148
+ expect(fn({ a: 0 })).toBe(true)
149
+ })
150
+
151
+ // ISSUE: contains() does not check for own properties only; it will match inherited properties.
152
+ it("matches inherited properties in the object", () => {
153
+ const template = { foo: 1 }
154
+ const fn = like(template)
155
+ const proto = { foo: 1 }
156
+ const obj = Object.create(proto)
157
+ expect(fn(obj)).toBe(true)
158
+ obj.foo = 2
159
+ expect(fn(obj)).toBe(false)
160
+ })
161
+ })