@3dsource/utils 0.0.1

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.
Files changed (94) hide show
  1. package/README.md +64 -0
  2. package/eslint.config.js +37 -0
  3. package/ng-package.json +7 -0
  4. package/package.json +11 -0
  5. package/src/lib/color/CMYKtoRGB.ts +20 -0
  6. package/src/lib/color/HEXtoRGB.ts +9 -0
  7. package/src/lib/color/HSVtoRGB.ts +82 -0
  8. package/src/lib/color/RGBtoCMYK.ts +50 -0
  9. package/src/lib/color/RGBtoHEX.ts +17 -0
  10. package/src/lib/color/RGBtoHSV.ts +53 -0
  11. package/src/lib/color/hsv.ts +14 -0
  12. package/src/lib/color/index.ts +16 -0
  13. package/src/lib/color/max.ts +18 -0
  14. package/src/lib/color/min.ts +18 -0
  15. package/src/lib/color/overlay.ts +25 -0
  16. package/src/lib/color/rgb.ts +10 -0
  17. package/src/lib/color/sub.ts +18 -0
  18. package/src/lib/color/subtract.ts +27 -0
  19. package/src/lib/color/sum.ts +19 -0
  20. package/src/lib/color/toRGB.ts +14 -0
  21. package/src/lib/color/toRGBA.ts +8 -0
  22. package/src/lib/constants/color-codes.constant.ts +9 -0
  23. package/src/lib/constants/index.ts +1 -0
  24. package/src/lib/csv/CSV2Array.ts +66 -0
  25. package/src/lib/csv/CSV2Records.ts +56 -0
  26. package/src/lib/csv/ObjectToCSV.ts +21 -0
  27. package/src/lib/csv/index.ts +3 -0
  28. package/src/lib/csv/test/Csv.spec.ts +51 -0
  29. package/src/lib/dev/dev3d.ts +1 -0
  30. package/src/lib/dev/index.ts +3 -0
  31. package/src/lib/dev/logger.ts +94 -0
  32. package/src/lib/dev/timeToString.ts +16 -0
  33. package/src/lib/filenaming/cleanupFileName.ts +18 -0
  34. package/src/lib/filenaming/index.ts +3 -0
  35. package/src/lib/filenaming/makePath.ts +5 -0
  36. package/src/lib/filenaming/normalizePath.ts +9 -0
  37. package/src/lib/filenaming/test/cleanupFileName.spec.ts +9 -0
  38. package/src/lib/filenaming/test/makePath.spec.ts +7 -0
  39. package/src/lib/filenaming/test/normalizePath.spec.ts +9 -0
  40. package/src/lib/geom/expandOverRectangle.ts +17 -0
  41. package/src/lib/geom/fitIntoRectangle.ts +43 -0
  42. package/src/lib/geom/index.ts +3 -0
  43. package/src/lib/geom/interfaces/area.interface.ts +5 -0
  44. package/src/lib/geom/interfaces/index.ts +4 -0
  45. package/src/lib/geom/interfaces/rect.interface.ts +4 -0
  46. package/src/lib/geom/interfaces/size.interface.ts +4 -0
  47. package/src/lib/geom/interfaces//321/201oords.interface.ts +4 -0
  48. package/src/lib/geom/test/fitRectangle.spec.ts +54 -0
  49. package/src/lib/helpers/BatchLoader.ts +243 -0
  50. package/src/lib/helpers/KeyboardNumericCode.ts +118 -0
  51. package/src/lib/helpers/index.ts +6 -0
  52. package/src/lib/helpers/serialize.ts +11 -0
  53. package/src/lib/helpers/sleep.ts +3 -0
  54. package/src/lib/helpers/test/sleep.spec.ts +11 -0
  55. package/src/lib/helpers/trimLastSlashFromUrl.ts +9 -0
  56. package/src/lib/image/SaveImage.ts +65 -0
  57. package/src/lib/image/getCanvasCached.ts +16 -0
  58. package/src/lib/image/getSnapshot.ts +99 -0
  59. package/src/lib/image/index.ts +4 -0
  60. package/src/lib/image/loadImage.ts +13 -0
  61. package/src/lib/interfaces/image-output.ts +8 -0
  62. package/src/lib/interfaces/index.ts +3 -0
  63. package/src/lib/interfaces/load-args-tmp.interface.ts +5 -0
  64. package/src/lib/interfaces/load-args.interface.ts +15 -0
  65. package/src/lib/math/baseSortedIndex.ts +43 -0
  66. package/src/lib/math/calculateMedian.ts +33 -0
  67. package/src/lib/math/circularIndex.ts +39 -0
  68. package/src/lib/math/clampf.ts +14 -0
  69. package/src/lib/math/degrees.ts +7 -0
  70. package/src/lib/math/floatCompare.ts +69 -0
  71. package/src/lib/math/index.ts +8 -0
  72. package/src/lib/math/inverseLerp.ts +38 -0
  73. package/src/lib/math/lerp.ts +12 -0
  74. package/src/lib/math/test/baseSortedIndex.spec.ts +43 -0
  75. package/src/lib/math/test/circularIndex.spec.ts +38 -0
  76. package/src/lib/mutex/Mutex.ts +50 -0
  77. package/src/lib/mutex/Semaphore.ts +62 -0
  78. package/src/lib/mutex/TaskRunner.ts +26 -0
  79. package/src/lib/mutex/index.ts +3 -0
  80. package/src/lib/predicates/BooleanPredictors.ts +47 -0
  81. package/src/lib/predicates/index.ts +3 -0
  82. package/src/lib/predicates/test/BooleanPredictors.spec.ts +71 -0
  83. package/src/lib/predicates/test/where.spec.ts +94 -0
  84. package/src/lib/predicates/textForSearch.ts +34 -0
  85. package/src/lib/predicates/where.ts +76 -0
  86. package/src/lib/rxjs/index.ts +3 -0
  87. package/src/lib/rxjs/leadingTrailingDebounceTime.ts +86 -0
  88. package/src/lib/rxjs/smoothTransition.ts +29 -0
  89. package/src/lib/rxjs/tapLog.ts +13 -0
  90. package/src/lib/strings/index.ts +1 -0
  91. package/src/lib/strings/pad.ts +18 -0
  92. package/src/public-api.ts +14 -0
  93. package/tsconfig.lib.json +13 -0
  94. package/tsconfig.lib.prod.json +11 -0
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Calculates the median of an array of numbers.
3
+ *
4
+ * The median is the middle value of a sorted list of numbers. If the list has an even number
5
+ * of elements, the median is the average of the two middle numbers.
6
+ *
7
+ * @param numbers - An array of numbers for which the median will be calculated.
8
+ * @returns The median value as a number, or `null` if the array is empty.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * const numbers = [3, 1, 4, 2];
13
+ * const median = calculateMedian(numbers);
14
+ * console.log(median); // Output: 2.5
15
+ * ```
16
+ */
17
+ export function calculateMedian(numbers: number[]): number {
18
+ if (numbers.length === 0) return 0;
19
+
20
+ // Sort the array in ascending order
21
+ const sorted = [...numbers].sort((a, b) => a - b);
22
+
23
+ const length = sorted.length;
24
+ const middle = Math.floor(length / 2);
25
+
26
+ // If the length is even, return the average of the two middle numbers
27
+ if (length % 2 === 0) {
28
+ return (sorted[middle - 1] + sorted[middle]) / 2;
29
+ }
30
+
31
+ // If the length is odd, return the middle number
32
+ return sorted[middle];
33
+ }
@@ -0,0 +1,39 @@
1
+ import { clampf } from './clampf';
2
+
3
+ /**
4
+ * Computes a circular index within a given range.
5
+ *
6
+ * This function ensures that the index wraps around within the range of 0 to `totalItems - 1`.
7
+ * It uses the `clampf` function to clamp the result within the valid range.
8
+ *
9
+ * @param index - The current index to be wrapped.
10
+ * @param totalItems - The total number of items in the range.
11
+ * @returns The wrapped index within the range of 0 to `totalItems - 1`.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { circularIndex } from './circularIndex';
16
+ *
17
+ * // Example 1: Basic usage
18
+ * const index1 = circularIndex(5, 5);
19
+ * console.log(index1); // Output: 0
20
+ *
21
+ * // Example 2: Index greater than totalItems
22
+ * const index2 = circularIndex(6, 5);
23
+ * console.log(index2); // Output: 1
24
+ *
25
+ * // Example 3: Negative index
26
+ * const index3 = circularIndex(-2, 5);
27
+ * console.log(index3); // Output: 3
28
+ *
29
+ * // Example 4: Large Negative index
30
+ * const index4 = circularIndex(-6, 5);
31
+ * console.log(index4); // Output: 4
32
+ *
33
+ * // Example 5: Index equal to totalItems
34
+ * const index5 = circularIndex(1, 1);
35
+ * console.log(index5); // Output: 0
36
+ */
37
+ export function circularIndex(index: number, totalItems: number): number {
38
+ return clampf(0, totalItems - 1, (totalItems * 2 + index) % totalItems);
39
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Clamps value between min and max;
3
+ * @param min minValue
4
+ * @param max maxValue
5
+ * @param value inputValue
6
+ * @returns number
7
+ */
8
+
9
+ export function clampf(min: number, max: number, value: number): number {
10
+ if (min > max) {
11
+ return min;
12
+ }
13
+ return Math.min(max, Math.max(min, value));
14
+ }
@@ -0,0 +1,7 @@
1
+ export function degreesToRadians(degrees: number): number {
2
+ return degrees * (Math.PI / 180);
3
+ }
4
+
5
+ export function radiansToDegrees(radians: number): number {
6
+ return radians * (180 / Math.PI);
7
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * A small epsilon value used for floating-point comparisons.
3
+ *
4
+ * @remarks
5
+ * The value of `EPSILON` is `0.01`. This can be overridden in the comparison functions if needed.
6
+ */
7
+ const EPSILON = 0.01;
8
+
9
+ /**
10
+ * Checks if floating-point number `A` is less than `B` considering a small margin of error.
11
+ *
12
+ * @param A - The first floating-point number to compare.
13
+ * @param B - The second floating-point number to compare.
14
+ * @param Epsilon - Optional custom epsilon value for comparison. Defaults to `EPSILON` if not provided.
15
+ * @returns `true` if `A` is less than `B` considering the epsilon margin, otherwise `false`.
16
+ *
17
+ * @example
18
+ * ```
19
+ * const result = fpIsALessThanB(0.0034, 0.0066); // true
20
+ * ```
21
+ */
22
+ export function fpIsALessThanB(
23
+ A: number,
24
+ B: number,
25
+ Epsilon?: number,
26
+ ): boolean {
27
+ Epsilon = Epsilon || EPSILON;
28
+ return A - B < Epsilon && Math.abs(A - B) > Epsilon;
29
+ }
30
+
31
+ /**
32
+ * Checks if floating-point number `A` is greater than `B` considering a small margin of error.
33
+ *
34
+ * @param A - The first floating-point number to compare.
35
+ * @param B - The second floating-point number to compare.
36
+ * @param Epsilon - Optional custom epsilon value for comparison. Defaults to `EPSILON` if not provided.
37
+ * @returns `true` if `A` is greater than `B` considering the epsilon margin, otherwise `false`.
38
+ *
39
+ * @example
40
+ * ```
41
+ * const result = fpIsAGreaterThanB(0.0066, 0.0034); // true
42
+ * ```
43
+ */
44
+ export function fpIsAGreaterThanB(
45
+ A: number,
46
+ B: number,
47
+ Epsilon?: number,
48
+ ): boolean {
49
+ Epsilon = Epsilon || EPSILON;
50
+ return A - B > Epsilon && Math.abs(A - B) > Epsilon;
51
+ }
52
+
53
+ /**
54
+ * Checks if floating-point number `A` is approximately the same as `B` considering a small margin of error.
55
+ *
56
+ * @param A - The first floating-point number to compare.
57
+ * @param B - The second floating-point number to compare.
58
+ * @param Epsilon - Optional custom epsilon value for comparison. Defaults to `EPSILON` if not provided.
59
+ * @returns `true` if `A` is approximately equal to `B` considering the epsilon margin, otherwise `false`.
60
+ *
61
+ * @example
62
+ * ```
63
+ * const result = fpIsASameAsB(0.005, 0.0051); // true
64
+ * ```
65
+ */
66
+ export function fpIsASameAsB(A: number, B: number, Epsilon?: number): boolean {
67
+ Epsilon = Epsilon || EPSILON;
68
+ return Math.abs(A - B) < Epsilon;
69
+ }
@@ -0,0 +1,8 @@
1
+ export * from './baseSortedIndex';
2
+ export * from './calculateMedian';
3
+ export * from './circularIndex';
4
+ export * from './clampf';
5
+ export * from './degrees';
6
+ export * from './floatCompare';
7
+ export * from './inverseLerp';
8
+ export * from './lerp';
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Performs an inverse linear interpolation.
3
+ *
4
+ * Given a range defined by `min` and `max`, this function calculates the
5
+ * interpolation factor (or percentage) of `value` within that range. Optionally,
6
+ * it can clamp the result to be between 0 and 1 to ensure the return value is
7
+ * within the range of a standard lerp operation.
8
+ *
9
+ * @param {number} min - The minimum value of the interpolation range.
10
+ * @param {number} max - The maximum value of the interpolation range.
11
+ * @param {number} value - The value to interpolate.
12
+ * @param {boolean} [clampToNormal=false] - Whether to clamp the result between 0 and 1.
13
+ * @returns {number} The interpolation factor of `value` between `min` and `max`.
14
+ * If `clampToNormal` is true, the result is clamped between 0 and 1.
15
+ * If the difference between `min` and `max` is 0, returns 1 to avoid division by zero.
16
+ */
17
+ export function inverseLerp(
18
+ min: number,
19
+ max: number,
20
+ value: number,
21
+ clampToNormal = false,
22
+ ): number {
23
+ if (clampToNormal) {
24
+ value = Math.min(max, value);
25
+ value = Math.max(min, value);
26
+ }
27
+ const difference = max - min;
28
+
29
+ // Avoid JS division error
30
+ if (difference === 0) {
31
+ return 1;
32
+ }
33
+ const result = (value - min) / difference;
34
+ if (clampToNormal) {
35
+ return Math.min(1, Math.max(0, result));
36
+ }
37
+ return result;
38
+ }
@@ -0,0 +1,12 @@
1
+ import { clampf } from './clampf';
2
+
3
+ /**
4
+ * @param min minimums
5
+ * @param max maximums
6
+ * @param value current float value in 0-1 range
7
+ * @returns clamped value
8
+ */
9
+ export function lerp(min: number, max: number, value: number): number {
10
+ value = clampf(0, 1, value);
11
+ return value * (max - min) + min;
12
+ }
@@ -0,0 +1,43 @@
1
+ import { baseSortedIndex } from '../baseSortedIndex';
2
+
3
+ describe('baseSortedIndex', () => {
4
+ it('should return the correct insertion index in a sorted array', () => {
5
+ const array = [10, 20, 30, 40, 50];
6
+ const value = 35;
7
+ const index = baseSortedIndex(array, value);
8
+ expect(index).toBe(3); // 35 should be inserted at index 3 to maintain sorted order
9
+ });
10
+
11
+ it('should handle empty arrays', () => {
12
+ const array: number[] = [];
13
+ const value = 10;
14
+ const index = baseSortedIndex(array, value);
15
+ expect(index).toBe(0); // In an empty array, the insertion index is always 0
16
+ });
17
+
18
+ it('should place value at the start if less than all elements', () => {
19
+ const array = [10, 20, 30, 40, 50];
20
+ const value = 5;
21
+ const index = baseSortedIndex(array, value);
22
+ expect(index).toBe(0); // 5 should be inserted at index 0
23
+ });
24
+
25
+ it('should place value at the end if greater than all elements', () => {
26
+ const array = [10, 20, 30, 40, 50];
27
+ const value = 60;
28
+ const index = baseSortedIndex(array, value);
29
+ expect(index).toBe(5); // 60 should be inserted at index 5, which is the array's length
30
+ });
31
+
32
+ it('should handle arrays with the maximum safe size', () => {
33
+ // Assuming a simplified scenario for demonstration, as actually filling an array to max length would be impractical
34
+ const array = [1, 2]; // Simulate a sorted array
35
+ const value = 2;
36
+ // Manually set the array length to mimic a large array scenario
37
+ Object.defineProperty(array, 'length', { value: Math.pow(2, 31) - 1 });
38
+ const index = baseSortedIndex(array, value);
39
+ // The expected index would depend on the actual implementation and purpose of handling large arrays
40
+ // For this example, we'll assume it should return the position based on the 'value' in a sorted array logic
41
+ expect(index).not.toBeUndefined(); // Adjust this based on the actual intended behavior for large arrays
42
+ });
43
+ });
@@ -0,0 +1,38 @@
1
+ import { circularIndex } from '../circularIndex';
2
+
3
+ describe('circularIndex', () => {
4
+ it('should return 0', () => {
5
+ expect(circularIndex(5, 5)).toBe(0);
6
+ });
7
+
8
+ it('should return 1', () => {
9
+ expect(circularIndex(6, 5)).toBe(1);
10
+ });
11
+
12
+ it('should return 4', () => {
13
+ expect(circularIndex(-1, 5)).toBe(4);
14
+ });
15
+
16
+ it('should return 3', () => {
17
+ expect(circularIndex(-2, 5)).toBe(3);
18
+ });
19
+ it('should return 4', () => {
20
+ expect(circularIndex(-6, 5)).toBe(4);
21
+ });
22
+ it('should return 1', () => {
23
+ expect(circularIndex(11, 5)).toBe(1);
24
+ });
25
+
26
+ it('should return 0', () => {
27
+ expect(circularIndex(1, 1)).toBe(0);
28
+ });
29
+ it('should return 0', () => {
30
+ expect(circularIndex(2, 1)).toBe(0);
31
+ });
32
+ it('should return 0', () => {
33
+ expect(circularIndex(-2, 1)).toBe(0);
34
+ });
35
+ it('should return 0', () => {
36
+ expect(circularIndex(10, 0)).toBe(0);
37
+ });
38
+ });
@@ -0,0 +1,50 @@
1
+ /**
2
+ * A Mutex class to handle mutual exclusion locks, ensuring that only one asynchronous task can execute a critical section at a time.
3
+ *
4
+ * @example
5
+ * const mutex = new Mutex();
6
+ *
7
+ * async function task(name: string, duration: number) {
8
+ * console.log(`${name} waiting to acquire mutex`);
9
+ * const release = await mutex.acquire();
10
+ * console.log(`${name} acquired mutex`);
11
+ * await new Promise(resolve => setTimeout(resolve, duration));
12
+ * console.log(`${name} releasing mutex`);
13
+ * release();
14
+ * }
15
+ *
16
+ * task('Task 1', 1000);
17
+ * task('Task 2', 2000);
18
+ * task('Task 3', 1500);
19
+ * task('Task 4', 500);
20
+ *
21
+ * Outputs
22
+ * [LOG]: "Task 1 waiting to acquire mutex"
23
+ * [LOG]: "Task 2 waiting to acquire mutex"
24
+ * [LOG]: "Task 3 waiting to acquire mutex"
25
+ * [LOG]: "Task 4 waiting to acquire mutex"
26
+ * [LOG]: "Task 1 acquired mutex"
27
+ * [LOG]: "Task 1 releasing mutex"
28
+ * [LOG]: "Task 2 acquired mutex"
29
+ * [LOG]: "Task 2 releasing mutex"
30
+ * [LOG]: "Task 3 acquired mutex"
31
+ * [LOG]: "Task 3 releasing mutex"
32
+ * [LOG]: "Task 4 acquired mutex"
33
+ * [LOG]: "Task 4 releasing mutex"
34
+ */
35
+ export class Mutex {
36
+ private mutex = Promise.resolve();
37
+
38
+ /**
39
+ * Acquires the mutex, returning a promise that resolves when the mutex is acquired.
40
+ * The returned promise provides a release function that should be called to release the mutex.
41
+ * @returns A promise that resolves with a release function.
42
+ */
43
+ async acquire(): Promise<() => void> {
44
+ let release: () => void;
45
+ const previous = this.mutex;
46
+ this.mutex = new Promise<void>((resolve) => (release = resolve));
47
+ await previous;
48
+ return release!;
49
+ }
50
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * A Semaphore controls access to a resource that can handle a limited number of concurrent operations.
3
+ *
4
+ * @example
5
+ * const semaphore = new Semaphore(2);
6
+ *
7
+ * async function task(name: string, duration: number) {
8
+ * console.log(`${name} waiting to acquire semaphore`);
9
+ * await semaphore.acquire();
10
+ * console.log(`${name} acquired semaphore`);
11
+ * await new Promise(resolve => setTimeout(resolve, duration));
12
+ * console.log(`${name} releasing semaphore`);
13
+ * semaphore.release();
14
+ * }
15
+ *
16
+ * task('Task 1', 1000);
17
+ * task('Task 2', 2000);
18
+ * task('Task 3', 1500);
19
+ * task('Task 4', 500);
20
+ *
21
+ * Outputs
22
+ * [LOG]: "Task 1 waiting to acquire semaphore"
23
+ * [LOG]: "Task 2 waiting to acquire semaphore"
24
+ * [LOG]: "Task 3 waiting to acquire semaphore"
25
+ * [LOG]: "Task 4 waiting to acquire semaphore"
26
+ * [LOG]: "Task 1 acquired semaphore"
27
+ * [LOG]: "Task 2 acquired semaphore"
28
+ * [LOG]: "Task 1 releasing semaphore"
29
+ * [LOG]: "Task 3 acquired semaphore"
30
+ * [LOG]: "Task 2 releasing semaphore"
31
+ * [LOG]: "Task 4 acquired semaphore"
32
+ * [LOG]: "Task 4 releasing semaphore"
33
+ * [LOG]: "Task 3 releasing semaphore"
34
+ */
35
+ export class Semaphore {
36
+ private tasks: (() => void)[] = [];
37
+ private currentCount = 0;
38
+
39
+ constructor(private readonly maxConcurrency: number) {}
40
+
41
+ public acquire(): Promise<void> {
42
+ return new Promise((resolve) => {
43
+ if (this.currentCount < this.maxConcurrency) {
44
+ this.currentCount++;
45
+ resolve();
46
+ } else {
47
+ this.tasks.push(resolve);
48
+ }
49
+ });
50
+ }
51
+
52
+ public release(): void {
53
+ if (this.tasks.length > 0) {
54
+ const nextTask = this.tasks.shift();
55
+ if (nextTask) {
56
+ nextTask();
57
+ }
58
+ } else if (this.currentCount > 0) {
59
+ this.currentCount--;
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,26 @@
1
+ import type { Observable } from 'rxjs';
2
+ import { finalize, from, switchMap } from 'rxjs';
3
+ import { Logger } from '../dev';
4
+ import { Mutex } from './Mutex';
5
+
6
+ export class TaskRunner {
7
+ private mutex = new Mutex();
8
+
9
+ runTask(name: string, task: () => Observable<string | null>) {
10
+ // Log that the task is waiting to acquire the mutex
11
+ Logger.log(`${name} waiting to acquire mutex`);
12
+
13
+ return from(this.mutex.acquire()).pipe(
14
+ switchMap((release) => {
15
+ // Log that the mutex has been acquired
16
+ Logger.log(`${name} acquired mutex`);
17
+ return task().pipe(
18
+ finalize(() => {
19
+ Logger.log(`${name} releasing mutex`);
20
+ release();
21
+ }),
22
+ );
23
+ }),
24
+ );
25
+ }
26
+ }
@@ -0,0 +1,3 @@
1
+ export * from './Mutex';
2
+ export * from './Semaphore';
3
+ export * from './TaskRunner';
@@ -0,0 +1,47 @@
1
+ type TruthyTypesOf<T> = T extends false | '' | 0 | null | undefined ? never : T;
2
+ type FalsyTypesOf<T> = T extends false | '' | 0 | null | undefined ? T : never;
3
+ type EmptyTypesOf<T> = T extends undefined | null | object | '' ? T : never;
4
+ type NonEmptyTypesOf<T> = T extends undefined | null | object | '' ? never : T;
5
+ /**
6
+ * The Boolean object represents a truth value: true or false.
7
+ * @param value
8
+ * @constructor
9
+ * Returns:boolean
10
+ */
11
+ export const Truthy: <T>(value: T) => value is TruthyTypesOf<T> = <T>(
12
+ value?: T,
13
+ ): value is TruthyTypesOf<T> =>
14
+ !!value && value !== 'false' && value !== 'undefined' && value !== 'null';
15
+
16
+ /**
17
+ * The Boolean object represents a INVERSE truth value: true or false.
18
+ * @param value
19
+ * @constructor
20
+ * Returns:boolean
21
+ */
22
+ export const Falsy: <T>(value: T) => value is FalsyTypesOf<T> = <T>(
23
+ value?: T,
24
+ ): value is FalsyTypesOf<T> => !Truthy(value);
25
+
26
+ /**
27
+ * Checks if a value is empty.
28
+ *
29
+ * @param {any} value - The value to check.
30
+ * @return {boolean} Returns true if the value is empty, otherwise false.
31
+ */
32
+ export const IsEmpty: <T>(value: T) => value is EmptyTypesOf<T> = <T>(
33
+ value: T,
34
+ ): value is EmptyTypesOf<T> => {
35
+ return (
36
+ value === undefined ||
37
+ value === null ||
38
+ (typeof value === 'object' && Object.keys(value).length === 0) ||
39
+ (typeof value === 'string' && value.trim().length === 0)
40
+ );
41
+ };
42
+
43
+ export const NotEmpty: <T>(value: T) => value is NonEmptyTypesOf<T> = <T>(
44
+ value: T,
45
+ ): value is NonEmptyTypesOf<T> => {
46
+ return !IsEmpty(value);
47
+ };
@@ -0,0 +1,3 @@
1
+ export * from './BooleanPredictors';
2
+ export * from './textForSearch';
3
+ export * from './where';
@@ -0,0 +1,71 @@
1
+ import { Falsy, IsEmpty, Truthy } from '../BooleanPredictors';
2
+
3
+ describe('Truthy function', () => {
4
+ it('should return true for truthy values', () => {
5
+ expect(Truthy(true)).toBeTruthy();
6
+ expect(Truthy('true')).toBeTruthy();
7
+ expect(Truthy(1)).toBeTruthy();
8
+ expect(Truthy('non-empty')).toBeTruthy();
9
+ expect(Truthy({ key: 'value' })).toBeTruthy();
10
+ expect(Truthy([1, 2, 3])).toBeTruthy();
11
+ expect(Truthy([])).toBeTruthy();
12
+ expect(Truthy({})).toBeTruthy();
13
+ });
14
+
15
+ it('should return false for falsy values', () => {
16
+ expect(Truthy('undefined')).toBeFalsy();
17
+ expect(Truthy(undefined)).toBeFalsy();
18
+ expect(Truthy(null)).toBeFalsy();
19
+ expect(Truthy('null')).toBeFalsy();
20
+ expect(Truthy('false')).toBeFalsy();
21
+ expect(Truthy(false)).toBeFalsy();
22
+ expect(Truthy(0)).toBeFalsy();
23
+ expect(Truthy(-0)).toBeFalsy();
24
+ expect(Truthy(-0)).toBeFalsy();
25
+ expect(Truthy(NaN)).toBeFalsy();
26
+ expect(Truthy('')).toBeFalsy();
27
+ });
28
+ });
29
+
30
+ describe('Falsy function', () => {
31
+ it('should return false for truthy values', () => {
32
+ expect(Falsy(1)).toBeFalsy();
33
+ expect(Falsy('non-empty')).toBeFalsy();
34
+ expect(Falsy({ key: 'value' })).toBeFalsy();
35
+ expect(Falsy([1, 2, 3])).toBeFalsy();
36
+ expect(Falsy([])).toBeFalsy();
37
+ expect(Falsy({})).toBeFalsy();
38
+ expect(Falsy('true')).toBeFalsy();
39
+ });
40
+
41
+ it('should return true for falsy values', () => {
42
+ expect(Falsy('false')).toBeTruthy();
43
+ expect(Falsy('undefined')).toBeTruthy();
44
+ expect(Falsy('null')).toBeTruthy();
45
+ expect(Falsy(false)).toBeTruthy();
46
+ expect(Falsy(undefined)).toBeTruthy();
47
+ expect(Falsy(null)).toBeTruthy();
48
+ expect(Falsy(0)).toBeTruthy();
49
+ expect(Falsy(-0)).toBeTruthy();
50
+ expect(Falsy(-0)).toBeTruthy();
51
+ expect(Falsy(NaN)).toBeTruthy();
52
+ expect(Falsy('')).toBeTruthy();
53
+ });
54
+ });
55
+
56
+ describe('IsEmpty function', () => {
57
+ it('should return true for empty values', () => {
58
+ expect(IsEmpty('')).toBeTruthy();
59
+ expect(IsEmpty(' ')).toBeTruthy();
60
+ expect(IsEmpty(null)).toBeTruthy();
61
+ expect(IsEmpty(undefined)).toBeTruthy();
62
+ expect(IsEmpty({})).toBeTruthy();
63
+ expect(IsEmpty([])).toBeTruthy();
64
+ });
65
+
66
+ it('should return false for non-empty values', () => {
67
+ expect(IsEmpty('non-empty')).toBeFalsy();
68
+ expect(IsEmpty({ key: 'value' })).toBeFalsy();
69
+ expect(IsEmpty([1])).toBeFalsy();
70
+ });
71
+ });
@@ -0,0 +1,94 @@
1
+ // Mocking the textForSearch if necessary, or import if available
2
+ import { where, whereNot } from '../where';
3
+
4
+ describe('where', () => {
5
+ it('should filter records based on a single criteria match', () => {
6
+ const records = [{ name: 'John Doe' }, { name: 'Jane Doe' }];
7
+ const criteria = { name: 'John Doe' };
8
+ const filter = where(criteria);
9
+ expect(records.filter(filter)).toEqual([{ name: 'John Doe' }]);
10
+ });
11
+
12
+ it('should be case sensitive when specified', () => {
13
+ const records = [{ name: 'john doe' }, { name: 'Jane Doe' }];
14
+ const criteria = { name: 'John Doe' };
15
+ const filter = where(criteria, true);
16
+ expect(records.filter(filter)).toEqual([]);
17
+ });
18
+
19
+ it('should handle multiple criteria', () => {
20
+ const records = [
21
+ { name: 'John Doe', age: 30 },
22
+ { name: 'Jane Doe', age: 25 },
23
+ ];
24
+ const criteria = { name: 'Jane Doe', age: 25 };
25
+ const filter = where(criteria);
26
+ expect(records.filter(filter)).toEqual([{ name: 'Jane Doe', age: 25 }]);
27
+ });
28
+
29
+ it('should return empty array if criteria is empty', () => {
30
+ const records = [{ name: 'John Doe' }, { name: 'Jane Doe' }];
31
+ const criteria = {};
32
+ const filter = where(criteria);
33
+ expect(records.filter(filter)).toEqual([]);
34
+ });
35
+
36
+ it('should handle non-string values correctly', () => {
37
+ const records = [{ age: 20 }, { age: 30 }];
38
+ const criteria = { age: 20 };
39
+ const filter = where(criteria);
40
+ expect(records.filter(filter)).toEqual([{ age: 20 }]);
41
+ });
42
+ it('should handle undefined values correctly', () => {
43
+ const records = [{ age: 20 }, { age: 30 }];
44
+ const criteria = { age: undefined };
45
+ const filter = where(criteria);
46
+ expect(records.filter(filter)).toEqual([]);
47
+ });
48
+ });
49
+
50
+ describe('whereNot', () => {
51
+ it('should filter records not matching the criteria', () => {
52
+ const records = [{ name: 'John Doe' }, { name: 'Jane Doe' }];
53
+ const criteria = { name: 'john doe' };
54
+ const filter = whereNot(criteria);
55
+ expect(records.filter(filter)).toEqual([{ name: 'Jane Doe' }]);
56
+ });
57
+
58
+ it('should filter records not matching the criteria', () => {
59
+ const records = [{ name: 'John Doe' }, { name: 'Jane Doe' }];
60
+ const criteria = { name: 'john Doe' };
61
+ const filter = whereNot(criteria, true);
62
+ expect(records.filter(filter)).toEqual([
63
+ { name: 'John Doe' },
64
+ { name: 'Jane Doe' },
65
+ ]);
66
+ });
67
+
68
+ it('should respect case sensitivity', () => {
69
+ const records = [{ name: 'john doe' }, { name: 'Jane Doe' }];
70
+ const criteria = { name: 'Jane Doe' };
71
+ const filter = whereNot(criteria);
72
+ expect(records.filter(filter)).toEqual([{ name: 'john doe' }]);
73
+ });
74
+
75
+ it('should process empty values', () => {
76
+ const records = [{ name: 'john doe' }, { name: 'Jane Doe' }];
77
+ const criteria = { name: undefined };
78
+ const filter = whereNot(criteria);
79
+ expect(records.filter(filter)).toEqual([
80
+ { name: 'john doe' },
81
+ { name: 'Jane Doe' },
82
+ ]);
83
+ });
84
+
85
+ it('should process empty values', () => {
86
+ const records = [{ name: 'john doe' }, { name: 'Jane Doe' }];
87
+ const criteria = {};
88
+ const filter = whereNot(criteria);
89
+ expect(records.filter(filter)).toEqual([
90
+ { name: 'john doe' },
91
+ { name: 'Jane Doe' },
92
+ ]);
93
+ });
94
+ });