@nejs/basic-extensions 1.2.0 → 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.
@@ -9,16 +9,45 @@ import { Patch } from '@nejs/extension';
9
9
  */
10
10
  export const ObjectExtensions = new Patch(Object, {
11
11
  /**
12
- * Checks if the given value is a valid key for an object. In JavaScript, a valid
13
- * key can be either a string or a symbol. This method is useful for validating
14
- * object keys before using them in operations like setting or getting object properties.
12
+ * Retrieves the string tag of an object. The string tag is a representation of
13
+ * the object's type, as defined by its `Object.prototype.toString` method. This
14
+ * utility method is helpful for getting a more descriptive type of an object than
15
+ * what is returned by the `typeof` operator, especially for custom objects.
15
16
  *
16
- * @param {*} value - The value to be checked.
17
- * @returns {boolean} - Returns `true` if the value is a valid object key (string or symbol),
18
- * otherwise `false`.
17
+ * @param {*} value - The object whose string tag is to be retrieved.
18
+ * @returns {string} - The string tag of the object, indicating its type.
19
19
  */
20
- isValidKey(value) {
21
- return (typeof value === 'string' || typeof value === 'symbol');
20
+ getStringTag(value) {
21
+ return /\s(.+)]/.exec(Object.prototype.toString.call(value))[1];
22
+ },
23
+ /**
24
+ * Determines the type of the given value based on its string tag. This method
25
+ * uses `Object.getStringTag` to obtain the string tag of the value, which
26
+ * represents its more specific type (e.g., Array, Map, Set) rather than just
27
+ * 'object'. The method then maps this string tag to the corresponding type
28
+ * present in the provided `owner` object, which defaults to `globalThis`.
29
+ * This utility method is especially useful for identifying the specific
30
+ * constructor or class of an object, beyond the basic types identified by
31
+ * the `typeof` operator.
32
+ *
33
+ * @param {any} value - The value whose type is to be determined.
34
+ * @param {object} [owner=globalThis] - The object in which to look up the
35
+ * constructor corresponding to the string tag. Defaults to `globalThis`, which
36
+ * covers global constructors like `Array`, `Object`, etc.
37
+ * @returns {Function|object|null|undefined} - Returns the constructor or type
38
+ * of the value based on its string tag. For 'Null' and 'Undefined', it returns
39
+ * `null` and `undefined`, respectively. For other types, it returns the
40
+ * corresponding constructor (e.g., `Array` for arrays) if available in the
41
+ * `owner` object.
42
+ */
43
+ getType(value, owner = globalThis) {
44
+ const stringTag = Object.getStringTag(value);
45
+ switch (stringTag) {
46
+ case 'Null': return null;
47
+ case 'Undefined': return undefined;
48
+ default:
49
+ return owner[stringTag];
50
+ }
22
51
  },
23
52
  /**
24
53
  * Determines if the provided value is an object. This method checks whether the
@@ -33,16 +62,42 @@ export const ObjectExtensions = new Patch(Object, {
33
62
  return value && (value instanceof Object || typeof value === 'object');
34
63
  },
35
64
  /**
36
- * Retrieves the string tag of an object. The string tag is a representation of
37
- * the object's type, as defined by its `Object.prototype.toString` method. This
38
- * utility method is helpful for getting a more descriptive type of an object than
39
- * what is returned by the `typeof` operator, especially for custom objects.
65
+ * Checks to see if the supplied value is a primitive value.
40
66
  *
41
- * @param {*} value - The object whose string tag is to be retrieved.
42
- * @returns {string} - The string tag of the object, indicating its type.
67
+ * @param {any} value the value to test to see if it is a primitive value type
68
+ * @returns true if the object is considered widely to be a primitive value,
69
+ * false otherwise.
43
70
  */
44
- getStringTag(value) {
45
- return /\s(.+)]/.exec(Object.prototype.toString.call(value))[1];
71
+ isPrimitive(value) {
72
+ // Check for null as a special case because typeof null
73
+ // is 'object'
74
+ if (value === null) {
75
+ return true;
76
+ }
77
+ // Check for other primitives
78
+ switch (typeof value) {
79
+ case 'string':
80
+ case 'number':
81
+ case 'bigint':
82
+ case 'boolean':
83
+ case 'undefined':
84
+ case 'symbol':
85
+ return true;
86
+ default:
87
+ return false;
88
+ }
89
+ },
90
+ /**
91
+ * Checks if the given value is a valid key for an object. In JavaScript, a valid
92
+ * key can be either a string or a symbol. This method is useful for validating
93
+ * object keys before using them in operations like setting or getting object properties.
94
+ *
95
+ * @param {*} value - The value to be checked.
96
+ * @returns {boolean} - Returns `true` if the value is a valid object key (string or symbol),
97
+ * otherwise `false`.
98
+ */
99
+ isValidKey(value) {
100
+ return (typeof value === 'string' || typeof value === 'symbol');
46
101
  },
47
102
  /**
48
103
  * Strips an object down to only the keys specified. Optionally, any
@@ -80,33 +135,4 @@ export const ObjectExtensions = new Patch(Object, {
80
135
  }
81
136
  return result;
82
137
  },
83
- /**
84
- * Determines the type of the given value based on its string tag. This method
85
- * uses `Object.getStringTag` to obtain the string tag of the value, which
86
- * represents its more specific type (e.g., Array, Map, Set) rather than just
87
- * 'object'. The method then maps this string tag to the corresponding type
88
- * present in the provided `owner` object, which defaults to `globalThis`.
89
- * This utility method is especially useful for identifying the specific
90
- * constructor or class of an object, beyond the basic types identified by
91
- * the `typeof` operator.
92
- *
93
- * @param {any} value - The value whose type is to be determined.
94
- * @param {object} [owner=globalThis] - The object in which to look up the
95
- * constructor corresponding to the string tag. Defaults to `globalThis`, which
96
- * covers global constructors like `Array`, `Object`, etc.
97
- * @returns {Function|object|null|undefined} - Returns the constructor or type
98
- * of the value based on its string tag. For 'Null' and 'Undefined', it returns
99
- * `null` and `undefined`, respectively. For other types, it returns the
100
- * corresponding constructor (e.g., `Array` for arrays) if available in the
101
- * `owner` object.
102
- */
103
- getType(value, owner = globalThis) {
104
- const stringTag = Object.getStringTag(value);
105
- switch (stringTag) {
106
- case 'Null': return null;
107
- case 'Undefined': return undefined;
108
- default:
109
- return owner[stringTag];
110
- }
111
- },
112
138
  });
package/package.json CHANGED
@@ -46,9 +46,9 @@
46
46
  "test": "jest"
47
47
  },
48
48
  "type": "module",
49
- "version": "1.2.0",
49
+ "version": "1.4.0",
50
50
  "dependencies": {
51
51
  "@nejs/extension": "^1.2.1"
52
52
  },
53
- "browser": "dist/@nejs/basic-extensions.bundle.1.0.0.js"
53
+ "browser": "dist/@nejs/basic-extensions.bundle.1.3.0.js"
54
54
  }
package/src/descriptor.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import { Extension } from '@nejs/extension'
2
2
  import { ObjectExtensions } from './objectextensions.js'
3
+ import { StringExtensions } from './stringextensions.js'
3
4
  import { ReflectExtensions } from './reflectextensions.js'
4
5
 
5
- const isObject = ObjectExtensions.patchEntries.isObject.computed
6
- const isValidKey = ObjectExtensions.patchEntries.isValidKey.computed
7
- const isString = ObjectExtensions.patchEntries.isString.computed
8
- const hasSome = ReflectExtensions.patchEntries.hasSome.computed
6
+ const isObject = ObjectExtensions.patchEntries?.isObject?.computed
7
+ const isValidKey = ObjectExtensions.patchEntries?.isValidKey?.computed
8
+ const isString = StringExtensions.patchEntries?.isString?.computed
9
+ const hasSome = ReflectExtensions.patchEntries?.hasSome?.computed
9
10
 
10
11
  class Descriptor {
11
12
  #desc = Descriptor.enigmatic
@@ -22,15 +23,44 @@ class Descriptor {
22
23
  constructor(object, key) {
23
24
  this.#desc = object
24
25
 
25
- if (object && key) {
26
+ if (isObject(object) && isValidKey(key)) {
26
27
  this.#desc = Object.getOwnPropertyDescriptor(object, key)
27
28
  }
28
29
 
29
- if (!Descriptor.isDescriptor(this.#desc)) {
30
+ if (!this.isDescriptor) {
30
31
  throw new Error(`Not a valid descriptor:`, this.#desc)
31
32
  }
32
33
  }
33
34
 
35
+ /**
36
+ * Detects whether or not this instance is an accessor object descriptor
37
+ *
38
+ * @returns {boolean} true if this object has a getter or setter and is not
39
+ * a data descriptor
40
+ */
41
+ get isAccessor() {
42
+ return Descriptor.isAccessor(this.#desc)
43
+ }
44
+
45
+ /**
46
+ * Detects whether or not this instance is an data object descriptor
47
+ *
48
+ * @returns {boolean} true if this object has a value property and is not
49
+ * an accessor descriptor
50
+ */
51
+ get isData() {
52
+ return Descriptor.isData(this.#desc)
53
+ }
54
+
55
+ /**
56
+ * Detects whether or not this instance is a valid object descriptor
57
+ *
58
+ * @returns {boolean} true if this descriptor store is a valid descriptor
59
+ */
60
+ get isDescriptor() {
61
+ return Descriptor.isDescriptor(this.#desc)
62
+ }
63
+
34
64
  /**
35
65
  * Getter around the `configurable` object descriptor property of
36
66
  * this instance of Descriptor.
@@ -39,7 +69,7 @@ class Descriptor {
39
69
  * descriptor store is invalid.
40
70
  */
41
71
  get configurable() {
42
- return !!this.#desc?.configurable ?? undefined
72
+ return !!this.#desc?.configurable
43
73
  }
44
74
 
45
75
  /**
@@ -162,6 +192,22 @@ class Descriptor {
162
192
  (this.#desc || {}).set = value
163
193
  }
164
194
 
195
+ /**
196
+ * Shorthand for Object.getOwnPropertyDescriptor()
197
+ *
198
+ * @param {object} object a non-null object instance
199
+ * @param {string|symbol} key a symbol or string referencing which key on the
200
+ * object to return a descriptor for.
201
+ * @returns an object descriptor for the requested field or null
202
+ */
203
+ static for(object, key) {
204
+ if (!isObject(object) && !isValidKey(key)) {
205
+ return null
206
+ }
207
+
208
+ return Object.getOwnPropertyDescriptor(object, key)
209
+ }
210
+
165
211
  /**
166
212
  * Take the descriptor defined by this objects values and apply them to
167
213
  * the specified object using the specified key.
@@ -175,7 +221,7 @@ class Descriptor {
175
221
  throw new Error(`Cannot apply descriptor to non-object or invalid key`)
176
222
  }
177
223
 
178
- Object.defineProperty(object, forKey, this.#desc)
224
+ return Object.defineProperty(object, forKey, this.#desc)
179
225
  }
180
226
 
181
227
  /**
@@ -383,13 +429,7 @@ class Descriptor {
383
429
  ...Descriptor.DATA_KEYS,
384
430
  ]
385
431
 
386
- let isa = (hasSome(knownKeys) && (
387
- Descriptor.isAccessor(object) ||
388
- Descriptor.isData(object)
389
- )
390
- )
391
-
392
- return isa
432
+ return hasSome(object, knownKeys)
393
433
  }
394
434
 
395
435
  /**
@@ -423,7 +463,7 @@ class Descriptor {
423
463
  validData = true
424
464
  }
425
465
 
426
- return false
466
+ return validData
427
467
  }
428
468
 
429
469
  /**
@@ -18,7 +18,7 @@ export const FunctionExtensions = new Patch(Function, {
18
18
  * @returns {boolean} Returns `true` if the value is a class, otherwise `false`.
19
19
  */
20
20
  isClass(value) {
21
- return value instanceof Function && String(value).includes('class');
21
+ return value instanceof Function && !!/^class\s/.exec(String(value))
22
22
  },
23
23
 
24
24
  /**
package/src/globals.js ADDED
@@ -0,0 +1,216 @@
1
+ import { Patch } from '@nejs/extension'
2
+ import { FunctionExtensions } from './functionextensions.js'
3
+
4
+ const { isClass, isFunction } = FunctionExtensions.patchEntries.isClass.computed
5
+ const CustomInspect = Symbol.for('nodejs.util.inspect.custom')
6
+
7
+ export const GlobalFunctionsAndProps = new Patch(globalThis, {
8
+ asBigIntObject(
9
+ bigIntPrimitive
10
+ ) {
11
+ const base = { configurable: true, enumerable: false }
12
+ const object = { value: bigIntPrimitive }
13
+
14
+ Object.defineProperties(object, {
15
+ // @ts-ignore
16
+ [Symbol.toPrimitive]: { value: function() { return bigIntPrimitive }, ...base },
17
+ [Symbol.toStringTag]: { value: BigInt.name, ...base },
18
+ [Symbol.species]: { get() { return BigInt }, ...base },
19
+ [CustomInspect]: { ...base, value(depth, opts, inspect) {
20
+ return inspect(this[Symbol.toPrimitive](), { ...opts, depth })
21
+ }}
22
+ })
23
+
24
+ Object.setPrototypeOf(object, BigInt.prototype)
25
+
26
+ Reflect.ownKeys(BigInt.prototype).forEach(key => {
27
+ if (typeof object[key] !== 'function') {
28
+ return
29
+ }
30
+
31
+ object[key] = (function (...args) {
32
+ return BigInt.prototype[key].apply(this, args)
33
+ }).bind(object.value)
34
+ })
35
+
36
+ return object
37
+ },
38
+
39
+ /**
40
+ * Transforms an object to mimic a specified prototype, altering its type conversion
41
+ * and inspection behaviors. This function is especially useful for creating objects
42
+ * that need to behave like different primitive types under various operations.
43
+ *
44
+ * @param {Object} object - The object to be transformed.
45
+ * @param {Function|Object} [prototype=String.prototype] - The prototype or class to
46
+ * emulate. If a function is provided, its prototype is used. Defaults to
47
+ * String.prototype.
48
+ * @param {Function} [toPrimitive=(hint, val) => String(val)] - A function defining how
49
+ * the object should be converted to a primitive value. It receives a type hint
50
+ * ('number', 'string', or 'default') and the object, returning the primitive value.
51
+ * @returns {Object|null} The transformed object, or null if neither a class nor a
52
+ * prototype could be derived from the provided prototype parameter.
53
+ */
54
+ maskAs(object, classPrototype, options) {
55
+ const {
56
+ prototype,
57
+ toPrimitive
58
+ } = GenericMask({...options, prototype: classPrototype})
59
+
60
+ const base = { configurable: true, enumerable: false }
61
+ const proto = isFunction(prototype) ? prototype.prototype : prototype
62
+ const klass = isClass(prototype) ? prototype : proto?.constructor
63
+
64
+ if (!klass && !proto) {
65
+ return null
66
+ }
67
+
68
+ Object.setPrototypeOf(object, proto)
69
+ Object.defineProperties(object, {
70
+ valueOf: { value() { return String(toPrimitive('default', object)) }, ...base },
71
+
72
+ [Symbol.toPrimitive]: { value(hint) { return toPrimitive(hint, object) }, ...base },
73
+ [Symbol.toStringTag]: { value: klass.name, ...base },
74
+ [Symbol.species]: { get() { return klass }, ...base },
75
+ [CustomInspect]: { ...base, value(depth, opts, inspect) {
76
+ return inspect(this[Symbol.toPrimitive](), { ...opts, depth })
77
+ }}
78
+ })
79
+
80
+ return object
81
+ },
82
+
83
+ /**
84
+ * Masks an object as a string-like object by setting its prototype to String and
85
+ * defining how it converts to primitive types. This is particularly useful when an
86
+ * object needs to behave like a string in certain contexts, such as type coercion or
87
+ * logging.
88
+ *
89
+ * @param {Object} object - The object to be masked as a string.
90
+ * @param {string} [stringKey='value'] - The object property key used for the string
91
+ * representation. Defaults to 'value'.
92
+ * @param {Function} [toPrimitive] - Optional custom function for primitive conversion.
93
+ * If omitted, a default function handling various conversion hints is used.
94
+ * @returns {Object|null} The string-masked object, or null if the object doesn't have
95
+ * the specified stringKey property.
96
+ */
97
+ maskAsString(
98
+ object,
99
+ stringKey,
100
+ toPrimitive
101
+ ) {
102
+ if (object && Reflect.has(object, stringKey)) {
103
+ return maskAs(object, StringMask(stringKey ?? 'value', toPrimitive))
104
+ }
105
+
106
+ return null
107
+ },
108
+
109
+ /**
110
+ * Masks an object as a number-like object. This allows the object to behave like a
111
+ * number in operations like arithmetic and type coercion. It sets the prototype to
112
+ * Number and defines custom conversion behavior.
113
+ *
114
+ * @param {Object} object - The object to be masked as a number representation.
115
+ * Defaults to 'value'.
116
+ * @param {Function} [toPrimitive] - Optional custom function for primitive
117
+ * conversion. If not provided, a default function handling different conversion
118
+ * hints is used.
119
+ * @returns {Object|null} The number-masked object, or null if the object doesn't
120
+ * have the specified numberKey property.
121
+ */
122
+ maskAsNumber(
123
+ object,
124
+ numberKey,
125
+ toPrimitive
126
+ ) {
127
+ if (object && Reflect.has(object, numberKey)) {
128
+ return maskAs(object, NumberMask(numberKey ?? 'value', toPrimitive))
129
+ }
130
+
131
+ return null
132
+ },
133
+
134
+ /**
135
+ * Generates options for generic masking of an object, providing defaults for
136
+ * prototype and toPrimitive function if not specified.
137
+ *
138
+ * @param {Object} options - The options object including prototype, targetKey,
139
+ * and toPrimitive function.
140
+ * @returns {Object} The options object with defaults applied as necessary.
141
+ */
142
+ GenericMask({ prototype, targetKey = 'value', toPrimitive }) {
143
+ const options = { targetKey, toPrimitive, prototype };
144
+
145
+ if (!isFunction(toPrimitive)) {
146
+ options.toPrimitive = (hint, object) => {
147
+ let property = object[targetKey];
148
+ let isNum = (typeof property === 'number' && Number.isFinite(property)) ||
149
+ (typeof property === 'string' &&
150
+ !isNaN(parseFloat(property)) && isFinite(property)
151
+ );
152
+
153
+ switch (hint) {
154
+ case 'string': return isNum ? String(property) : (property ?? String(object));
155
+ case 'number': return isNum ? Number(property) : NaN;
156
+ case 'default':
157
+ default:
158
+ return isNum ? Number(property) : property;
159
+ }
160
+ }
161
+ }
162
+
163
+ return options;
164
+ },
165
+
166
+ /**
167
+ * Generates options for string masking of an object, providing a default toPrimitive
168
+ * function if not specified.
169
+ *
170
+ * @param {string} targetKey - The object property key for string representation.
171
+ * @param {Function} toPrimitive - Custom function for primitive conversion.
172
+ * @returns {Object} Options for string masking.
173
+ */
174
+ StringMask(targetKey, toPrimitive) {
175
+ const options = { targetKey, toPrimitive, prototype: String.prototype }
176
+
177
+ if (!isFunction(toPrimitive)) {
178
+ options.toPrimitive = function toPrimitive(hint, object) {
179
+ switch (hint) {
180
+ case 'default': return object[targetKey]
181
+ case 'number': return parseInt(object[targetKey], 36)
182
+ case 'string': return String(object[targetKey])
183
+ default: return object
184
+ }
185
+ }
186
+ }
187
+
188
+ return options
189
+ },
190
+
191
+ /**
192
+ * Generates options for number masking of an object, providing a default toPrimitive
193
+ * function if not specified.
194
+ *
195
+ * @param {string} targetKey - The object property key for number representation.
196
+ * @param {Function} toPrimitive - Custom function for primitive conversion.
197
+ * @returns {Object} Options for number masking.
198
+ */
199
+ NumberMask(targetKey, toPrimitive) {
200
+ const options = { targetKey, toPrimitive, prototype: Number.prototype }
201
+
202
+ if (!isFunction(toPrimitive)) {
203
+ options.toPrimitive = function toPrimitive(hint, object) {
204
+ switch (hint) {
205
+ case 'default': return object[targetKey]
206
+ case 'number': return Number(object[targetKey])
207
+ case 'string': return String(object[targetKey])
208
+ default: return object
209
+ }
210
+ }
211
+ }
212
+
213
+ return options
214
+ },
215
+
216
+ })
package/src/index.js CHANGED
@@ -5,6 +5,7 @@ import { StringExtensions } from './stringextensions.js'
5
5
  import { SymbolExtensions } from './symbolextensions.js'
6
6
  import { ArrayPrototypeExtensions } from './arrayextensions.js'
7
7
  import { DescriptorExtension } from './descriptor.js'
8
+ import { GlobalFunctionsAndProps } from './globals.js'
8
9
 
9
10
  import { Patch } from '@nejs/extension'
10
11
 
@@ -19,29 +20,68 @@ const Owners = [
19
20
  ]
20
21
 
21
22
  const NetNew = [
23
+ GlobalFunctionsAndProps,
22
24
  DescriptorExtension,
23
25
  ]
24
26
 
25
27
  export function enableAll(owners) {
26
- (owners || Owners).forEach(owner => {
28
+ const list = owners || Owners
29
+
30
+ if (!list) {
31
+ throw new Error('Unable to enable features without owners list')
32
+ }
33
+
34
+ list.forEach(owner => {
27
35
  Patch.enableFor(owner)
28
36
  })
29
37
 
30
- (NetNew).forEach(extension => {
38
+ NetNew.forEach(extension => {
31
39
  extension.apply()
32
40
  })
33
41
  }
34
42
 
35
43
  export function disableAll(owners) {
36
- (owners || Owners).forEach(owner => {
44
+ const list = owners || Owners
45
+
46
+ if (!list) {
47
+ throw new Error('Unable to disable features without owners list')
48
+ }
49
+
50
+ list.forEach(owner => {
37
51
  Patch.disableFor(owner)
38
52
  })
39
53
 
40
- (NetNew).forEach(extension => {
54
+ NetNew.forEach(extension => {
41
55
  extension.revert()
42
56
  })
43
57
  }
44
58
 
59
+ export const all = (() => {
60
+ let extensions = [
61
+ ObjectExtensions,
62
+ FunctionExtensions,
63
+ ReflectExtensions,
64
+ StringExtensions,
65
+ SymbolExtensions,
66
+ ArrayPrototypeExtensions,
67
+
68
+ GlobalFunctionsAndProps,
69
+ DescriptorExtension,
70
+ ]
71
+
72
+ const dest = extensions.reduce((accumulator, extension) => {
73
+ Reflect.ownKeys(extension.patchEntries).reduce((_, key) => {
74
+ accumulator[key] = extension.patchEntries[key].computed
75
+ return accumulator
76
+ }, accumulator)
77
+
78
+ return accumulator
79
+ }, {})
80
+
81
+ return dest
82
+ })()
83
+
84
+
45
85
  export {
46
86
  ObjectExtensions,
47
87
  FunctionExtensions,
@@ -49,4 +89,7 @@ export {
49
89
  StringExtensions,
50
90
  SymbolExtensions,
51
91
  ArrayPrototypeExtensions,
92
+
93
+ GlobalFunctionsAndProps,
94
+ DescriptorExtension,
52
95
  }