1 // ♓🌟 Piscēs ∷ object.js
2 // ====================================================================
4 // Copyright © 2022–2023 Lady [@ Lady’s Computer].
6 // This Source Code Form is subject to the terms of the Mozilla Public
7 // License, v. 2.0. If a copy of the MPL was not distributed with this
8 // file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
21 const createArray
= Array
;
22 const { isArray
} = Array
;
23 const object
= Object
;
32 getOwnPropertyDescriptor
: objectGetOwnPropertyDescriptor
,
34 getOwnPropertySymbols
: objectGetOwnPropertySymbols
,
41 preventExtensions
: objectPreventExtensions
,
49 defineProperty
: reflectDefineProperty
,
51 getOwnPropertyDescriptor
: reflectGetOwnPropertyDescriptor
,
55 setPrototypeOf
: reflectSetPrototypeOf
,
59 * An object whose properties are lazy‐loaded from the methods on the
60 * own properties of the provided object.
62 * This is useful when you are looking to reference properties on
63 * objects which, due to module dependency graphs, cannot be guaranteed
64 * to have been initialized yet.
66 * The resulting properties will have the same attributes (regarding
67 * configurability, enumerability, and writability) as the
68 * corresponding properties on the methods object. If a property is
69 * marked as writable, the method will never be called if it is set
70 * before it is gotten. By necessity, the resulting properties are all
71 * configurable before they are accessed for the first time.
73 * Methods will be called with the resulting object as their this
76 * `LazyLoader` objects have the same prototype as the passed methods
79 export class LazyLoader
extends null {
81 * Constructs a new `LazyLoader` object.
83 * ☡ This function throws if the provided value is not an object.
85 constructor(loadMethods
) {
86 if (type(loadMethods
) !== "object") {
87 // The provided value is not an object; throw an error.
89 `Piscēs: Cannot construct LazyLoader: Provided argument is not an object: ${loadMethods}.`,
92 // The provided value is an object; process it and build the
94 const result
= create(getPrototypeOf(loadMethods
));
95 const methodKeys
= ownKeys(loadMethods
);
96 for (let index
= 0; index
< methodKeys
.length
; ++index
) {
97 // Iterate over the property keys of the provided object and
98 // define getters and setters appropriately on the result.
99 const methodKey
= methodKeys
[index
];
100 const { configurable
, enumerable
, writable
} =
101 getOwnPropertyDescriptor(loadMethods
, methodKey
);
105 assign(create(null), {
110 const value
= call(loadMethods
[methodKey
], result
, []);
114 assign(create(null), {
124 assign(create(null), {
125 value
: toFunctionName(methodKey
, "get"),
134 assign(create(null), {
142 assign(create(null), {
143 value
: toFunctionName(methodKey
, "set"),
156 * Defines an own enumerable data property on the provided object with
157 * the provided property key and value.
159 export const defineOwnDataProperty
= (O
, P
, V
) =>
163 assign(create(null), {
172 * Defines an own nonenumerable data property on the provided object
173 * with the provided property key and value.
175 export const defineOwnNonenumerableDataProperty
= (O
, P
, V
) =>
179 assign(create(null), {
188 * Defines an own property on the provided object on the provided
189 * property key using the provided property descriptor.
191 * ※ This is effectively an alias for `Object.defineProperty`.
193 export const defineOwnProperty
= (O
, P
, Desc
) =>
194 defineProperty(O
, P
, Desc
);
197 * Defines own properties on the provided object using the descriptors
198 * on the enumerable own properties of the provided additional objects.
200 * ※ This differs from `Object.defineProperties` in that it can take
201 * multiple source objects.
203 export const defineOwnProperties
= (O
, ...sources
) => {
204 const { length
} = sources
;
205 for (let k
= 0; k
< length
; ++k
) {
206 defineProperties(O
, sources
[k
]);
212 * Removes the provided property key from the provided object and
213 * returns the object.
215 * ※ This function differs from `Reflect.deleteProperty` and the
216 * `delete` operator in that it throws if the deletion is
219 * ☡ This function throws if the first argument is not an object.
221 export const deleteOwnProperty
= (O
, P
) => {
222 if (type(O
) !== "object") {
224 `Piscēs: Tried to set property but provided value was not an object: ${V}`,
226 } else if (!deleteProperty(O
, P
)) {
228 `Piscēs: Tried to delete property from object but [[Delete]] returned false: ${P}`,
236 * Marks the provided object as non·extensible and marks all its
237 * properties as nonconfigurable and (if data properties) nonwritable,
238 * and returns the object.
240 * ※ This is effectively an alias for `Object.freeze`.
242 export const freeze
= (O
) => objectFreeze(O
);
245 * Returns a new frozen shallow copy of the enumerable own properties
246 * of the provided object, according to the following rules :—
248 * - For data properties, create a nonconfigurable, nonwritable
249 * property with the same value.
251 * - For accessor properties, create a nonconfigurable accessor
252 * property with the same getter *and* setter.
254 * The prototype for the resulting object will be taken from the
255 * `.prototype` property of the provided constructor, or the
256 * `.prototype` of the `.constructor` of the provided object if the
257 * provided constructor is undefined. If the used constructor has a
258 * nonnullish `.[Symbol.species]`, that will be used instead. If the
259 * used constructor or species is nullish or does not have a
260 * `.prototype` property, the prototype is set to null.
262 * ※ The prototype of the provided object itself is ignored.
264 export const frozenCopy
= (O
, constructor = O
?.constructor) => {
266 // O is null or undefined.
268 "Piscēs: Cannot copy properties of null or undefined.",
271 // O is not null or undefined.
273 // (If not provided, the constructor will be the value of getting
274 // the `.constructor` property of O.)
275 const species
= constructor?.[SPECIES
] ?? constructor;
277 species
== null || !("prototype" in species
)
281 const keys
= ownKeys(O
);
282 for (let k
= 0; k
< keys
.length
; ++k
) {
284 const Desc
= getOwnPropertyDescriptor(O
, P
);
285 if (Desc
.enumerable
) {
286 // P is an enumerable property.
292 isAccessorDescriptor(Desc
)
308 // P is not an enumerable property.
312 return objectPreventExtensions(copy
);
317 * Returns the function on the provided value at the provided property
320 * ☡ This function throws if the provided property key does not have an
321 * associated value which is callable.
323 export const getMethod
= (V
, P
) => {
327 } else if (typeof func
!== "function") {
328 throw new TypeError(`Piscēs: Method not callable: ${P}`);
335 * Returns the property descriptor record for the own property with the
336 * provided property key on the provided value, or null if none exists.
338 * ※ This is effectively an alias for
339 * `Object.getOwnPropertyDescriptor`, but the return value is a proxied
340 * object with null prototype.
342 export const getOwnPropertyDescriptor
= (O
, P
) => {
343 const desc
= objectGetOwnPropertyDescriptor(O
, P
);
344 return desc
=== UNDEFINED
346 : toPropertyDescriptorRecord(desc
);
350 * Returns the property descriptors for the own properties on the
353 * ※ This is effectively an alias for
354 * `Object.getOwnPropertyDescriptors`, but the values on the resulting
355 * object are proxied objects with null prototypes.
357 export const getOwnPropertyDescriptors
= (O
) => {
358 const obj
= toObject(O
);
359 const keys
= ownKeys(obj
);
360 const descriptors
= {};
361 for (let k
= 0; k
< keys
.length
; ++k
) {
362 // Iterate over the keys of the provided object and collect its
365 defineOwnDataProperty(
368 getOwnPropertyDescriptor(O
, key
),
375 * Returns an array of own property entries on the provided value,
376 * using the provided receiver if given.
378 export const getOwnPropertyEntries
= (O
, Receiver
= O
) => {
379 const obj
= toObject(O
);
380 const keys
= ownKeys(obj
);
381 const target
= Receiver
=== UNDEFINED
? obj
: toObject(Receiver
);
382 const result
= createArray(keys
.length
);
383 for (let k
= 0; k
< keys
.length
; ++k
) {
384 // Iterate over each key and add the corresponding entry to the
387 defineOwnDataProperty(
390 [key
, getOwnPropertyValue(obj
, keys
[k
], target
)],
397 * Returns an array of own property keys on the provided value.
399 * ※ This is effectively an alias for `Reflect.ownKeys`, except that
400 * it does not require that the argument be an object.
402 export const getOwnPropertyKeys
= (O
) => ownKeys(toObject(O
));
405 * Returns an array of string‐valued own property keys on the
408 * ☡ This includes both enumerable and non·enumerable properties.
410 * ※ This is effectively an alias for `Object.getOwnPropertyNames`.
412 export const getOwnPropertyStrings
= (O
) => getOwnPropertyNames(O
);
415 * Returns an array of symbol‐valued own property keys on the
418 * ☡ This includes both enumerable and non·enumerable properties.
420 * ※ This is effectively an alias for
421 * `Object.getOwnPropertySymbols`.
423 export const getOwnPropertySymbols
= (O
) =>
424 objectGetOwnPropertySymbols(O
);
427 * Returns the value of the provided own property on the provided
428 * value using the provided receiver, or undefined if the provided
429 * property is not an own property on the provided value.
431 * ※ If the receiver is not provided, it defaults to the provided
434 export const getOwnPropertyValue
= (O
, P
, Receiver
= UNDEFINED
) => {
435 const obj
= toObject(O
);
436 const desc
= getOwnPropertyDescriptor(O
, P
);
437 if (desc
=== UNDEFINED
) {
438 // The provided property is not an own property on the provided
439 // value; return undefined.
442 if (isDataDescriptor(desc
)) {
443 // The provided property is a data property; return its value.
446 // The provided property is an accessor property; get its value
447 // using the appropriate receiver.
448 const target
= Receiver
=== UNDEFINED
? obj
: toObject(Receiver
);
449 return call(desc
.get, target
, []);
454 * Returns an array of own property values on the provided value, using
455 * the provided receiver if given.
457 export const getOwnPropertyValues
= (O
, Receiver
= O
) => {
458 const obj
= toObject(O
);
459 const keys
= ownKeys(obj
);
460 const target
= Receiver
=== UNDEFINED
? obj
: toObject(Receiver
);
461 const result
= createArray(keys
.length
);
462 for (let k
= 0; k
< keys
.length
; ++k
) {
463 // Iterate over each key and collect the values.
464 defineOwnDataProperty(
467 getOwnPropertyValue(obj
, keys
[k
], target
),
474 * Returns the value of the provided property key on the provided
477 * ※ This is effectively an alias for `Reflect.get`, except that it
478 * does not require that the argument be an object.
480 export const getPropertyValue
= (O
, P
, Receiver
= O
) =>
481 get(toObject(O
), P
, Receiver
);
484 * Returns the prototype of the provided value.
486 * ※ This is effectively an alias for `Object.getPrototypeOf`.
488 export const getPrototype
= (O
) => getPrototypeOf(O
);
491 * Returns whether the provided value has an own property with the
492 * provided property key.
494 * ※ This is effectively an alias for `Object.hasOwn`.
496 export const hasOwnProperty
= (O
, P
) => hasOwn(O
, P
);
499 * Returns whether the provided property key exists on the provided
502 * ※ This is effectively an alias for `Reflect.has`, except that it
503 * does not require that the argument be an object.
505 * ※ This includes properties present on the prototype chain.
507 export const hasProperty
= (O
, P
) => has(toObject(O
), P
);
509 /** Returns whether the provided value is an arraylike object. */
510 export const isArraylikeObject
= ($) => {
511 if (type($) !== "object") {
515 lengthOfArraylike($); // throws if not arraylike
524 * Returns whether the provided value is spreadable during array
527 * This is also used to determine which things should be treated as
530 export const isConcatSpreadableObject
= ($) => {
531 if (type($) !== "object") {
532 // The provided value is not an object.
535 // The provided value is an object.
536 const spreadable
= $[IS_CONCAT_SPREADABLE
];
537 return spreadable
!== UNDEFINED
? !!spreadable
: isArray($);
542 * Returns whether the provided value is an extensible object.
544 * ※ This function returns false for nonobjects.
546 * ※ This is effectively an alias for `Object.isExtensible`.
548 export const isExtensibleObject
= (O
) => isExtensible(O
);
552 * Returns whether the provided value is a property descriptor record
553 * as created by `toPropertyDescriptor`.
555 * ※ This function is provided to enable inspection of whether an
556 * object uses the property descriptor record proxy implementation,
557 * not as a general test of whether an object satisfies the
558 * requirements for property descriptors. In most cases, a more
559 * specific test, like `isAccessorDescriptor`, `isDataDescriptor`, or
560 * `isGenericDescriptor`, is preferrable.
562 isPropertyDescriptorRecord
,
565 * Converts the provided value to a property descriptor record.
567 * ※ The prototype of a property descriptor record is always `null`.
569 * ※ Actually constructing a property descriptor object using this
570 * class is only necessary if you need strict guarantees about the
571 * types of its properties; the resulting object is proxied to ensure
572 * the types match what one would expect from composing
573 * FromPropertyDescriptor and ToPropertyDescriptor in the Ecmascript
576 toPropertyDescriptorRecord
,
578 const proxyConstructor
= Proxy
;
579 const { add
: weakSetAdd
, has
: weakSetHas
} = WeakSet
.prototype;
580 const propertyDescriptorRecords
= new WeakSet();
581 const coercePropertyDescriptorValue
= (P
, V
) => {
590 if (V
!== undefined && typeof V
!== "function") {
592 "Piscēs: Getters must be callable.",
598 if (V
!== undefined && typeof V
!== "function") {
600 "Piscēs: Setters must be callable.",
609 const propertyDescriptorProxyHandler
= objectFreeze(
613 defineProperty(O
, P
, Desc
) {
615 P
=== "configurable" || P
=== "enumerable" ||
616 P
=== "writable" || P
=== "value" ||
617 P
=== "get" || P
=== "set"
619 // P is a property descriptor attribute.
620 const desc
= assign(objectCreate(null), Desc
);
621 if ("get" in desc
|| "set" in desc
) {
622 // Desc is an accessor property descriptor.
624 "Piscēs: Property descriptor attributes must be data properties.",
626 } else if ("value" in desc
|| !(P
in O
)) {
627 // Desc has a value or P does not already exist on O.
628 desc
.value
= coercePropertyDescriptorValue(
633 // Desc is not an accessor property descriptor and has no
634 // value, but an existing value is present on O.
637 const isAccessorDescriptor
= "get" === P
|| "set" === P
||
638 "get" in O
|| "set" in O
;
639 const isDataDescriptor
= "value" === P
||
641 "value" in O
|| "writable" in O
;
642 if (isAccessorDescriptor
&& isDataDescriptor
) {
643 // Both accessor and data attributes will be present on O
646 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
649 // P can be safely defined on O.
650 return reflectDefineProperty(O
, P
, desc
);
653 // P is not a property descriptor attribute.
654 return reflectDefineProperty(O
, P
, Desc
);
657 setPrototypeOf(O
, V
) {
659 // V is not the property descriptor prototype.
662 // V is the property descriptor prototype.
663 return reflectSetPrototypeOf(O
, V
);
671 isPropertyDescriptorRecord
: ($) =>
672 call(weakSetHas
, propertyDescriptorRecords
, [$]),
673 toPropertyDescriptorRecord
: (Obj
) => {
674 if (type(Obj
) !== "object") {
675 // The provided value is not an object.
677 `Piscēs: Cannot convert primitive to property descriptor: ${O}.`,
680 // The provided value is an object.
681 const desc
= create(null);
682 if ("enumerable" in Obj
) {
683 // An enumerable property is specified.
684 defineOwnDataProperty(desc
, "enumerable", !!Obj
.enumerable
);
686 // An enumerable property is not specified.
689 if ("configurable" in Obj
) {
690 // A configurable property is specified.
691 defineOwnDataProperty(
697 // A configurable property is not specified.
700 if ("value" in Obj
) {
701 // A value property is specified.
702 defineOwnDataProperty(desc
, "value", Obj
.value
);
704 // A value property is not specified.
707 if ("writable" in Obj
) {
708 // A writable property is specified.
709 defineOwnDataProperty(desc
, "writable", !!Obj
.writable
);
711 // A writable property is not specified.
715 // A get property is specified.
716 const getter
= Obj
.get;
717 if (getter
!== UNDEFINED
&& typeof getter
!== "function") {
718 // The getter is not callable.
719 throw new TypeError("Piscēs: Getters must be callable.");
721 // The getter is callable.
722 defineOwnDataProperty(desc
, "get", Obj
.get);
725 // A get property is not specified.
729 // A set property is specified.
730 const setter
= Obj
.set;
731 if (setter
!== UNDEFINED
&& typeof setter
!== "function") {
732 // The setter is not callable.
733 throw new TypeError("Piscēs: Setters must be callable.");
735 // The setter is callable.
736 defineOwnDataProperty(desc
, "set", Obj
.set);
739 // A set property is not specified.
743 ("get" in desc
|| "set" in desc
) &&
744 ("value" in desc
|| "writable" in desc
)
746 // Both accessor and data attributes have been defined.
748 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
751 // The property descriptor is valid.
752 const record
= new proxyConstructor(
754 propertyDescriptorProxyHandler
,
756 call(weakSetAdd
, propertyDescriptorRecords
, [record
]);
765 * Returns whether the provided value is an unfrozen object.
767 * ※ This function returns false for nonobjects.
769 * ※ This is effectively an alias for `!Object.isFrozen`.
771 export const isUnfrozenObject
= (O
) => !isFrozen(O
);
774 * Returns whether the provided value is an unsealed object.
776 * ※ This function returns false for nonobjects.
778 * ※ This is effectively an alias for `!Object.isSealed`.
780 export const isUnsealedObject
= (O
) => !isSealed(O
);
783 * Returns the length of the provided arraylike value.
785 * This can produce larger lengths than can actually be stored in
786 * arrays, because no such restrictions exist on arraylike methods.
788 * ☡ This function throws if the provided value is not arraylike.
790 export const lengthOfArraylike
= ({ length
}) => toLength(length
);
793 * Returns an array of key~value pairs for the enumerable,
794 * string‐valued property keys on the provided value.
796 * ※ This is effectively an alias for `Object.entries`.
798 export const namedEntries
= (O
) => entries(O
);
801 * Returns an array of the enumerable, string‐valued property keys on
802 * the provided value.
804 * ※ This is effectively an alias for `Object.keys`.
806 export const namedKeys
= (O
) => keys(O
);
809 * Returns an array of property values for the enumerable,
810 * string‐valued property keys on the provided value.
812 * ※ This is effectively an alias for `Object.values`.
814 export const namedValues
= (O
) => values(O
);
817 * Returns a new object with the provided prototype and property
820 * ※ This is effectively an alias for `Object.create`.
822 export const objectCreate
= (O
, Properties
) => create(O
, Properties
);
825 * Returns a new object with property keys and values from the provided
828 * ※ This is effectively an alias for `Object.fromEntries`.
830 export const objectFromEntries
= (iterable
) => fromEntries(iterable
);
833 * Marks the provided object as non·extensible, and returns the
836 * ※ This is effectively an alias for `Object.preventExtensions`.
838 export const preventExtensions
= (O
) => objectPreventExtensions(O
);
841 * Marks the provided object as non·extensible and marks all its
842 * properties as nonconfigurable, and returns the object.
844 * ※ This is effectively an alias for `Object.seal`.
846 export const seal
= (O
) => objectSeal(O
);
849 * Sets the provided property key to the provided value on the provided
850 * object and returns the object.
852 * ※ This function differs from `Reflect.set` in that it throws if the
853 * setting is unsuccessful.
855 * ☡ This function throws if the first argument is not an object.
857 export const setPropertyValue
= (O
, P
, V
, Receiver
= O
) => {
858 if (type(O
) !== "object") {
860 `Piscēs: Tried to set property but provided value was not an object: ${V}`,
862 } else if (!set(O
, P
, V
, Receiver
)) {
864 `Piscēs: Tried to set property on object but [[Set]] returned false: ${P}`,
872 * Sets the values of the enumerable own properties of the provided
873 * additional objects on the provided values.
875 * ※ This is effectively an alias for `Object.assign`.
877 export const setPropertyValues
= (target
, source
, ...sources
) => {
878 const to
= toObject(target
);
879 for (let i
= -1; i
< sources
.length
; ++i
) {
880 // Iterate over each source and set the appropriate property
882 const nextSource
= i
=== -1 ? source
: sources
[i
];
883 if (nextSource
!= null) {
884 // The current source is not nullish; handle its own properties.
885 const from = toObject(nextSource
);
886 const keys
= ownKeys(from);
887 for (let k
= 0; k
< keys
.length
; ++k
) {
888 // Iterate over each key in the current source and set it in
889 // the target object if it is enumerable.
890 const nextKey
= keys
[k
];
891 const desc
= reflectGetOwnPropertyDescriptor(from, nextKey
);
892 if (desc
!== UNDEFINED
&& desc
.enumerable
) {
893 // The current key is present and enumerable; set it to its
894 // corresponding value.
895 const propValue
= from[nextKey
];
896 to
[nextKey
] = propValue
;
898 // The current key is not present or not enumerable.
903 // The current source is nullish.
911 * Sets the prototype of the provided object to the provided value and
912 * returns the object.
914 * ※ This is effectively an alias for `Object.setPrototypeOf`, but it
915 * won’t throw when setting the prototype of a primitive to its current
918 export const setPrototype
= (O
, proto
) => {
919 const obj
= toObject(O
);
921 // The provided value is an object; set its prototype normally.
922 return setPrototypeOf(O
, proto
);
924 // The provided value is not an object; attempt to set the
925 // prototype on a coerced version with extensions prevented, then
926 // return the provided value.
928 // This will throw if the given prototype does not match the
929 // existing one on the coerced object.
930 setPrototypeOf(objectPreventExtensions(obj
), proto
);
936 * Returns the provided value converted to an object.
938 * Existing objects are returned with no modification.
940 * ☡ This function throws if its argument is null or undefined.
942 export const toObject
= ($) => {
944 // The provided value is nullish; this is an error.
946 `Piscēs: Cannot convert ${$} into an object.`,
949 // The provided value is not nullish; coerce it to an object.