@naman_deep_singh/js-extensions 1.3.2 → 1.4.0

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 (38) hide show
  1. package/README.md +174 -425
  2. package/dist/cjs/array/array-extensions.js +84 -50
  3. package/dist/cjs/core/performance.d.ts +1 -0
  4. package/dist/cjs/core/performance.js +6 -0
  5. package/dist/cjs/core/version.d.ts +1 -0
  6. package/dist/cjs/core/version.js +9 -0
  7. package/dist/cjs/index.d.ts +1 -0
  8. package/dist/cjs/index.js +1 -0
  9. package/dist/cjs/number/number-extensions.js +85 -97
  10. package/dist/cjs/object/object-extensions.js +102 -105
  11. package/dist/cjs/string/string-extensions.js +66 -43
  12. package/dist/cjs/types/global-augmentations.d.ts +1 -0
  13. package/dist/cjs/utils/defineExtension.d.ts +1 -0
  14. package/dist/cjs/utils/defineExtension.js +13 -0
  15. package/dist/cjs/utils/index.d.ts +1 -0
  16. package/dist/cjs/utils/index.js +1 -0
  17. package/dist/esm/array/array-extensions.js +84 -50
  18. package/dist/esm/core/performance.d.ts +1 -0
  19. package/dist/esm/core/performance.js +5 -0
  20. package/dist/esm/core/version.d.ts +1 -0
  21. package/dist/esm/core/version.js +5 -0
  22. package/dist/esm/index.d.ts +1 -0
  23. package/dist/esm/index.js +1 -0
  24. package/dist/esm/number/number-extensions.js +86 -98
  25. package/dist/esm/object/object-extensions.js +102 -105
  26. package/dist/esm/string/string-extensions.js +66 -43
  27. package/dist/esm/types/global-augmentations.d.ts +1 -0
  28. package/dist/esm/utils/defineExtension.d.ts +1 -0
  29. package/dist/esm/utils/defineExtension.js +10 -0
  30. package/dist/esm/utils/index.d.ts +1 -0
  31. package/dist/esm/utils/index.js +1 -0
  32. package/dist/types/core/performance.d.ts +1 -0
  33. package/dist/types/core/version.d.ts +1 -0
  34. package/dist/types/index.d.ts +1 -0
  35. package/dist/types/types/global-augmentations.d.ts +1 -0
  36. package/dist/types/utils/defineExtension.d.ts +1 -0
  37. package/dist/types/utils/index.d.ts +1 -0
  38. package/package.json +8 -4
@@ -1,3 +1,4 @@
1
+ import { getPackageVersion } from "./version";
1
2
  const defaultConfig = {
2
3
  enableCaching: false,
3
4
  maxCacheSize: 100,
@@ -54,6 +55,10 @@ function getOrCreateCache() {
54
55
  }
55
56
  return cache;
56
57
  }
58
+ export function makeInternalCacheKey(domain, key) {
59
+ const INTERNAL_CACHE_PREFIX = '@js-ext' + getPackageVersion();
60
+ return `${INTERNAL_CACHE_PREFIX}:${domain}:${key}`;
61
+ }
57
62
  export function withCache(key, fn) {
58
63
  if (!config.enableCaching) {
59
64
  return fn();
@@ -0,0 +1 @@
1
+ export declare const getPackageVersion: () => string;
@@ -0,0 +1,5 @@
1
+ import { createRequire } from 'node:module';
2
+ const requireFn = createRequire("../../package.json");
3
+ export const getPackageVersion = () => {
4
+ return requireFn('../package.json').version;
5
+ };
@@ -21,6 +21,7 @@ export declare function initializeExtensions(options?: ExtensionOptions): void;
21
21
  */
22
22
  export { extendAll };
23
23
  /**
24
+ * Selective prototype extension helpers
24
25
  * Initialize only specific extensions
25
26
  */
26
27
  export declare const extend: {
package/dist/esm/index.js CHANGED
@@ -28,6 +28,7 @@ export function initializeExtensions(options = {}) {
28
28
  */
29
29
  export { extendAll };
30
30
  /**
31
+ * Selective prototype extension helpers
31
32
  * Initialize only specific extensions
32
33
  */
33
34
  export const extend = {
@@ -1,87 +1,68 @@
1
- // Number prototype extensions
2
- import { withCache } from '../core/performance';
1
+ import { defineExtension } from 'src/utils';
2
+ import { makeInternalCacheKey, withCache } from '../core/performance';
3
+ let numberExtended = false;
3
4
  export function extendNumber() {
4
- Number.prototype.toPercent = function (decimals = 2) {
5
- return `${(this.valueOf() * 100).toFixed(decimals)}%`;
6
- };
7
- Number.prototype.toCurrency = function (currency = 'USD', locale = 'en-US') {
8
- return new Intl.NumberFormat(locale, {
9
- style: 'currency',
10
- currency,
11
- }).format(this.valueOf());
12
- };
13
- Number.prototype.clamp = function (min, max) {
14
- if (min > max) {
5
+ if (numberExtended)
6
+ return;
7
+ numberExtended = true;
8
+ defineExtension(Number.prototype, 'toPercent', function (decimals = 2) {
9
+ return `${(this * 100).toFixed(decimals)}%`;
10
+ });
11
+ defineExtension(Number.prototype, 'toCurrency', function (currency = 'USD', locale = 'en-US') {
12
+ return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(this);
13
+ });
14
+ defineExtension(Number.prototype, 'clamp', function (min, max) {
15
+ if (min > max)
15
16
  throw new RangeError(`clamp: min (${min}) cannot be greater than max (${max})`);
16
- }
17
- return Math.min(Math.max(this.valueOf(), min), max);
18
- };
19
- Number.prototype.isEven = function () {
20
- return this.valueOf() % 2 === 0;
21
- };
22
- Number.prototype.isOdd = function () {
23
- return this.valueOf() % 2 !== 0;
24
- };
25
- Number.prototype.isPrime = function () {
26
- const num = this.valueOf();
27
- return withCache(`prime_${num}`, () => {
17
+ return Math.min(Math.max(this, min), max);
18
+ });
19
+ defineExtension(Number.prototype, 'isEven', function () {
20
+ return this % 2 === 0;
21
+ });
22
+ defineExtension(Number.prototype, 'isOdd', function () {
23
+ return this % 2 !== 0;
24
+ });
25
+ defineExtension(Number.prototype, 'isPrime', function () {
26
+ const num = this;
27
+ return withCache(makeInternalCacheKey('prime', num), () => {
28
28
  if (num < 2)
29
29
  return false;
30
- for (let i = 2; i <= Math.sqrt(num); i++) {
30
+ for (let i = 2; i <= Math.sqrt(num); i++)
31
31
  if (num % i === 0)
32
32
  return false;
33
- }
34
33
  return true;
35
34
  });
36
- };
37
- Number.prototype.factorial = function () {
38
- const num = Math.floor(this.valueOf());
39
- return withCache(`factorial_${num}`, () => {
35
+ });
36
+ defineExtension(Number.prototype, 'factorial', function () {
37
+ const num = Math.floor(this);
38
+ return withCache(makeInternalCacheKey('factorial', num), () => {
40
39
  if (num < 0)
41
- return Number.NaN;
42
- if (num === 0 || num === 1)
40
+ return NaN;
41
+ if (num <= 1)
43
42
  return 1;
44
43
  let result = 1;
45
- for (let i = 2; i <= num; i++) {
44
+ for (let i = 2; i <= num; i++)
46
45
  result *= i;
47
- }
48
46
  return result;
49
47
  });
50
- };
51
- Number.prototype.toOrdinal = function () {
52
- const num = Math.floor(this.valueOf());
48
+ });
49
+ defineExtension(Number.prototype, 'toOrdinal', function () {
50
+ const num = Math.floor(this);
53
51
  const suffix = ['th', 'st', 'nd', 'rd'];
54
52
  const v = num % 100;
55
53
  return num + (suffix[(v - 20) % 10] || suffix[v] || suffix[0]);
56
- };
57
- Number.prototype.toRoman = function () {
58
- const num = Math.floor(this.valueOf());
59
- // Better validation for roman numerals
60
- if (num <= 0) {
54
+ });
55
+ defineExtension(Number.prototype, 'toRoman', function () {
56
+ const num = Math.floor(this);
57
+ if (num <= 0)
61
58
  throw new RangeError('toRoman: number must be positive');
62
- }
63
- if (num >= 4000) {
59
+ if (num >= 4000)
64
60
  throw new RangeError('toRoman: number must be less than 4000');
65
- }
66
- return withCache(`roman_${num}`, () => {
61
+ return withCache(makeInternalCacheKey('roman', num), () => {
67
62
  const values = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1];
68
- const symbols = [
69
- 'M',
70
- 'CM',
71
- 'D',
72
- 'CD',
73
- 'C',
74
- 'XC',
75
- 'L',
76
- 'XL',
77
- 'X',
78
- 'IX',
79
- 'V',
80
- 'IV',
81
- 'I',
82
- ];
83
- let result = '';
63
+ const symbols = ['M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I'];
84
64
  let n = num;
65
+ let result = '';
85
66
  for (let i = 0; i < values.length; i++) {
86
67
  while (n >= values[i]) {
87
68
  result += symbols[i];
@@ -90,45 +71,52 @@ export function extendNumber() {
90
71
  }
91
72
  return result;
92
73
  });
93
- };
94
- Number.prototype.inRange = function (min, max) {
95
- const num = this.valueOf();
96
- return num >= min && num <= max;
97
- };
98
- Number.prototype.round = function (decimals = 0) {
99
- if (!Number.isInteger(decimals) || decimals < 0) {
100
- throw new TypeError(`round: decimals must be a non-negative integer, got ${decimals}`);
101
- }
74
+ });
75
+ defineExtension(Number.prototype, 'inRange', function (min, max) {
76
+ return this >= min && this <= max;
77
+ });
78
+ defineExtension(Number.prototype, 'round', function (decimals = 0) {
79
+ if (!Number.isInteger(decimals) || decimals < 0)
80
+ throw new TypeError('round: decimals must be non-negative integer');
102
81
  const factor = Math.pow(10, decimals);
103
- return Math.round(this.valueOf() * factor) / factor;
104
- };
105
- Number.prototype.ceil = function (decimals = 0) {
106
- if (!Number.isInteger(decimals) || decimals < 0) {
107
- throw new TypeError(`ceil: decimals must be a non-negative integer, got ${decimals}`);
108
- }
82
+ return Math.round(this * factor) / factor;
83
+ });
84
+ defineExtension(Number.prototype, 'ceil', function (decimals = 0) {
85
+ if (!Number.isInteger(decimals) || decimals < 0)
86
+ throw new TypeError('ceil: decimals must be non-negative integer');
87
+ const factor = Math.pow(10, decimals);
88
+ return Math.ceil(this * factor) / factor;
89
+ });
90
+ defineExtension(Number.prototype, 'floor', function (decimals = 0) {
91
+ if (!Number.isInteger(decimals) || decimals < 0)
92
+ throw new TypeError('floor: decimals must be non-negative integer');
109
93
  const factor = Math.pow(10, decimals);
110
- return Math.ceil(this.valueOf() * factor) / factor;
111
- };
112
- Number.prototype.floor = function (decimals = 0) {
94
+ return Math.floor(this * factor) / factor;
95
+ });
96
+ defineExtension(Number.prototype, 'abs', function () {
97
+ return Math.abs(this);
98
+ });
99
+ defineExtension(Number.prototype, 'sign', function () {
100
+ return Math.sign(this);
101
+ });
102
+ defineExtension(Number.prototype, 'times', function (callback) {
103
+ if (typeof callback !== 'function')
104
+ throw new TypeError('times: callback must be a function');
105
+ for (let i = 0; i < Math.floor(this); i++)
106
+ callback(i);
107
+ });
108
+ defineExtension(Number.prototype, 'toFixedNumber', function (decimals = 0) {
113
109
  if (!Number.isInteger(decimals) || decimals < 0) {
114
- throw new TypeError(`floor: decimals must be a non-negative integer, got ${decimals}`);
110
+ throw new TypeError(`toFixedNumber: decimals must be a non-negative integer, got ${decimals}`);
115
111
  }
116
112
  const factor = Math.pow(10, decimals);
117
- return Math.floor(this.valueOf() * factor) / factor;
118
- };
119
- Number.prototype.abs = function () {
120
- return Math.abs(this.valueOf());
121
- };
122
- Number.prototype.sign = function () {
123
- return Math.sign(this.valueOf());
124
- };
125
- Number.prototype.times = function (callback) {
126
- if (typeof callback !== 'function') {
127
- throw new TypeError(`times: callback must be a function, got ${typeof callback}`);
128
- }
129
- const num = Math.floor(this.valueOf());
130
- for (let i = 0; i < num; i++) {
131
- callback(i);
113
+ return Math.round(this.valueOf() * factor) / factor;
114
+ });
115
+ defineExtension(Number.prototype, 'randomUpTo', function () {
116
+ const max = this.valueOf();
117
+ if (!Number.isFinite(max)) {
118
+ throw new TypeError(`randomUpTo: number must be finite, got ${max}`);
132
119
  }
133
- };
120
+ return Math.random() * max;
121
+ });
134
122
  }
@@ -1,71 +1,40 @@
1
- // Object prototype extensions
1
+ import { defineExtension } from "src/utils";
2
+ let objectExtended = false;
2
3
  export function extendObject() {
3
- Object.prototype.isEmpty = function () {
4
+ if (objectExtended)
5
+ return;
6
+ objectExtended = true;
7
+ defineExtension(Object.prototype, 'isEmpty', function () {
4
8
  return Object.keys(this).length === 0;
5
- };
6
- Object.prototype.pick = function (keys) {
7
- if (this === null || this === undefined) {
8
- throw new TypeError('pick: cannot be called on null or undefined');
9
- }
10
- if (!Array.isArray(keys)) {
11
- throw new TypeError(`pick: keys must be an array, got ${typeof keys}`);
12
- }
13
- if (keys.length === 0) {
9
+ });
10
+ defineExtension(Object.prototype, 'pick', function (keys) {
11
+ if (!Array.isArray(keys))
12
+ throw new TypeError('pick: keys must be an array');
13
+ if (!keys.length)
14
14
  throw new TypeError('pick: keys array cannot be empty');
15
- }
16
15
  const result = {};
17
- const obj = this;
18
16
  keys.forEach((key) => {
19
- if (key in obj) {
20
- result[key] = obj[key];
21
- }
17
+ if (key in this)
18
+ result[key] = this[key];
22
19
  });
23
20
  return result;
24
- };
25
- Object.prototype.omit = function (keys) {
26
- if (this === null || this === undefined) {
27
- throw new TypeError('omit: cannot be called on null or undefined');
28
- }
29
- if (!Array.isArray(keys)) {
30
- throw new TypeError(`omit: keys must be an array, got ${typeof keys}`);
31
- }
32
- if (keys.length === 0) {
21
+ });
22
+ defineExtension(Object.prototype, 'omit', function (keys) {
23
+ if (!Array.isArray(keys))
24
+ throw new TypeError('omit: keys must be an array');
25
+ if (!keys.length)
33
26
  throw new TypeError('omit: keys array cannot be empty');
34
- }
35
27
  const result = { ...this };
36
- keys.forEach((key) => {
37
- delete result[key];
38
- });
28
+ keys.forEach((key) => delete result[key]);
39
29
  return result;
40
- };
41
- Object.prototype.deepClone = function () {
42
- // Create a more robust cache key using WeakMap for cycle detection
43
- const cloneId = Symbol('clone');
44
- // Simple cycle detection without caching key generation
45
- if (this === null || typeof this !== 'object')
46
- return this;
47
- // Handle Date objects
48
- if (this instanceof Date)
49
- return new Date(this.getTime());
50
- // Handle Array objects
51
- if (Array.isArray(this)) {
52
- return this.map((item) => {
53
- if (item &&
54
- typeof item === 'object' &&
55
- typeof item.deepClone === 'function') {
56
- return item.deepClone();
57
- }
58
- return item;
59
- });
60
- }
61
- // Handle regular objects with better cycle detection
30
+ });
31
+ defineExtension(Object.prototype, 'deepClone', function () {
62
32
  const visited = new WeakSet();
63
33
  function deepCloneSafe(obj) {
64
34
  if (obj === null || typeof obj !== 'object')
65
35
  return obj;
66
- if (visited.has(obj)) {
36
+ if (visited.has(obj))
67
37
  throw new Error('Circular reference detected in deepClone');
68
- }
69
38
  visited.add(obj);
70
39
  if (obj instanceof Date)
71
40
  return new Date(obj.getTime());
@@ -78,69 +47,97 @@ export function extendObject() {
78
47
  return cloned;
79
48
  }
80
49
  return deepCloneSafe(this);
81
- };
82
- Object.prototype.merge = function (other) {
50
+ });
51
+ defineExtension(Object.prototype, 'merge', function (other) {
83
52
  return { ...this, ...other };
84
- };
85
- Object.prototype.deepFreeze = function () {
86
- const propNames = Object.getOwnPropertyNames(this);
87
- for (const name of propNames) {
53
+ });
54
+ defineExtension(Object.prototype, 'deepFreeze', function () {
55
+ Object.getOwnPropertyNames(this).forEach((name) => {
88
56
  const value = this[name];
89
- if (value && typeof value === 'object') {
90
- value.deepFreeze();
91
- }
92
- }
57
+ if (value && typeof value === 'object')
58
+ value.deepFreeze?.();
59
+ });
93
60
  return Object.freeze(this);
94
- };
95
- Object.prototype.hasPath = function (path) {
96
- if (typeof path !== 'string') {
97
- throw new TypeError(`hasPath: path must be a string, got ${typeof path}`);
98
- }
99
- if (path.trim() === '') {
100
- throw new TypeError('hasPath: path cannot be empty or whitespace');
101
- }
61
+ });
62
+ defineExtension(Object.prototype, 'hasPath', function (path) {
63
+ if (!path.trim())
64
+ throw new TypeError('hasPath: path cannot be empty');
65
+ return path.split('.').every((key) => {
66
+ if (this == null || !(key in this))
67
+ return false;
68
+ // @ts-ignore
69
+ this = this[key];
70
+ return true;
71
+ });
72
+ });
73
+ defineExtension(Object.prototype, 'getPath', function (path, defaultValue) {
74
+ if (!path.trim())
75
+ throw new TypeError('getPath: path cannot be empty');
76
+ return path.split('.').reduce((acc, key) => (acc && key in acc ? acc[key] : defaultValue), this);
77
+ });
78
+ defineExtension(Object.prototype, 'setPath', function (path, value) {
79
+ if (!path.trim())
80
+ throw new TypeError('setPath: path cannot be empty');
102
81
  const keys = path.split('.');
103
82
  let current = this;
104
- for (const key of keys) {
105
- if (current == null || !(key in current))
106
- return false;
107
- current = current[key];
83
+ for (let i = 0; i < keys.length - 1; i++) {
84
+ if (!(keys[i] in current) || typeof current[keys[i]] !== 'object')
85
+ current[keys[i]] = {};
86
+ current = current[keys[i]];
108
87
  }
109
- return true;
110
- };
111
- Object.prototype.getPath = function (path, defaultValue) {
112
- if (typeof path !== 'string') {
113
- throw new TypeError(`getPath: path must be a string, got ${typeof path}`);
88
+ current[keys[keys.length - 1]] = value;
89
+ return this;
90
+ });
91
+ defineExtension(Object.prototype, 'mapValues', function (fn) {
92
+ if (typeof fn !== 'function') {
93
+ throw new TypeError(`mapValues: fn must be a function, got ${typeof fn}`);
114
94
  }
115
- if (path.trim() === '') {
116
- throw new TypeError('getPath: path cannot be empty or whitespace');
95
+ const result = {};
96
+ for (const key in this) {
97
+ if (Object.prototype.hasOwnProperty.call(this, key)) {
98
+ result[key] = fn(this[key], key);
99
+ }
117
100
  }
118
- const keys = path.split('.');
119
- let current = this;
120
- for (const key of keys) {
121
- if (current == null || !(key in current))
122
- return defaultValue;
123
- current = current[key];
101
+ return result;
102
+ });
103
+ defineExtension(Object.prototype, 'mapKeys', function (fn) {
104
+ if (typeof fn !== 'function') {
105
+ throw new TypeError(`mapKeys: fn must be a function, got ${typeof fn}`);
124
106
  }
125
- return current;
126
- };
127
- Object.prototype.setPath = function (path, value) {
128
- if (typeof path !== 'string') {
129
- throw new TypeError(`setPath: path must be a string, got ${typeof path}`);
107
+ const result = {};
108
+ for (const key in this) {
109
+ if (Object.prototype.hasOwnProperty.call(this, key)) {
110
+ const newKey = fn(key);
111
+ result[newKey] = this[key];
112
+ }
130
113
  }
131
- if (path.trim() === '') {
132
- throw new TypeError('setPath: path cannot be empty or whitespace');
114
+ return result;
115
+ });
116
+ defineExtension(Object.prototype, 'filterKeys', function (keys) {
117
+ if (!Array.isArray(keys)) {
118
+ throw new TypeError(`filterKeys: keys must be an array, got ${typeof keys}`);
133
119
  }
134
- const keys = path.split('.');
135
- let current = this;
136
- for (let i = 0; i < keys.length - 1; i++) {
137
- const key = keys[i];
138
- if (!(key in current) || typeof current[key] !== 'object') {
139
- current[key] = {};
120
+ const result = {};
121
+ for (const key of keys) {
122
+ if (key in this) {
123
+ result[key] = this[key];
140
124
  }
141
- current = current[key];
142
125
  }
143
- current[keys[keys.length - 1]] = value;
144
- return this;
145
- };
126
+ return result;
127
+ });
128
+ defineExtension(Object.prototype, 'filterValues', function (fn) {
129
+ if (typeof fn !== 'function') {
130
+ throw new TypeError(`filterValues: fn must be a function, got ${typeof fn}`);
131
+ }
132
+ const result = {};
133
+ for (const key in this) {
134
+ if (Object.prototype.hasOwnProperty.call(this, key)) {
135
+ const val = this[key];
136
+ if (fn(val, key)) {
137
+ result[key] = val;
138
+ }
139
+ }
140
+ }
141
+ return result;
142
+ });
146
143
  }