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/>.
20 const { isArray
} = Array
;
21 const object
= Object
;
30 getOwnPropertyDescriptor
: objectGetOwnPropertyDescriptor
,
32 getOwnPropertySymbols
: objectGetOwnPropertySymbols
,
39 preventExtensions
: objectPreventExtensions
,
47 defineProperty
: reflectDefineProperty
,
49 getOwnPropertyDescriptor
: reflectGetOwnPropertyDescriptor
,
53 setPrototypeOf
: reflectSetPrototypeOf
,
57 * An object whose properties are lazy‐loaded from the methods on the
58 * own properties of the provided object.
60 * This is useful when you are looking to reference properties on
61 * objects which, due to module dependency graphs, cannot be guaranteed
62 * to have been initialized yet.
64 * The resulting properties will have the same attributes (regarding
65 * configurability, enumerability, and writability) as the
66 * corresponding properties on the methods object. If a property is
67 * marked as writable, the method will never be called if it is set
68 * before it is gotten. By necessity, the resulting properties are all
69 * configurable before they are accessed for the first time.
71 * Methods will be called with the resulting object as their this
74 * `LazyLoader` objects have the same prototype as the passed methods
77 export class LazyLoader
extends null {
79 * Constructs a new `LazyLoader` object.
81 * ☡ This function throws if the provided value is not an object.
83 constructor(loadMethods
) {
84 if (type(loadMethods
) !== "object") {
85 // The provided value is not an object; throw an error.
87 `Piscēs: Cannot construct LazyLoader: Provided argument is not an object: ${loadMethods}.`,
90 // The provided value is an object; process it and build the
92 const result
= create(getPrototypeOf(loadMethods
));
93 const methodKeys
= ownKeys(loadMethods
);
94 for (let index
= 0; index
< methodKeys
.length
; ++index
) {
95 // Iterate over the property keys of the provided object and
96 // define getters and setters appropriately on the result.
97 const methodKey
= methodKeys
[index
];
98 const { configurable
, enumerable
, writable
} =
99 getOwnPropertyDescriptor(loadMethods
, methodKey
);
103 assign(create(null), {
108 const value
= call(loadMethods
[methodKey
], result
, []);
112 assign(create(null), {
122 assign(create(null), {
123 value
: toFunctionName(methodKey
, "get"),
132 assign(create(null), {
140 assign(create(null), {
141 value
: toFunctionName(methodKey
, "set"),
154 * Defines an own enumerable data property on the provided object with
155 * the provided property key and value.
157 export const defineOwnDataProperty
= (O
, P
, V
) =>
161 assign(create(null), {
170 * Defines an own nonenumerable data property on the provided object
171 * with the provided property key and value.
173 export const defineOwnNonenumerableDataProperty
= (O
, P
, V
) =>
177 assign(create(null), {
186 * Defines an own property on the provided object on the provided
187 * property key using the provided property descriptor.
189 * ※ This is effectively an alias for `Object.defineProperty`.
191 export const defineOwnProperty
= (O
, P
, Desc
) =>
192 defineProperty(O
, P
, Desc
);
195 * Defines own properties on the provided object using the descriptors
196 * on the enumerable own properties of the provided additional objects.
198 * ※ This differs from `Object.defineProperties` in that it can take
199 * multiple source objects.
201 export const defineOwnProperties
= (O
, ...sources
) => {
202 const { length
} = sources
;
203 for (let k
= 0; k
< length
; ++k
) {
204 defineProperties(O
, sources
[k
]);
210 * Removes the provided property key from the provided object and
211 * returns the object.
213 * ※ This function differs from `Reflect.deleteProperty` and the
214 * `delete` operator in that it throws if the deletion is
217 * ☡ This function throws if the first argument is not an object.
219 export const deleteOwnProperty
= (O
, P
) => {
220 if (type(O
) !== "object") {
222 `Piscēs: Tried to set property but provided value was not an object: ${V}`,
224 } else if (!deleteProperty(O
, P
)) {
226 `Piscēs: Tried to delete property from object but [[Delete]] returned false: ${P}`,
234 * Marks the provided object as non·extensible and marks all its
235 * properties as nonconfigurable and (if data properties) nonwritable,
236 * and returns the object.
238 * ※ This is effectively an alias for `Object.freeze`.
240 export const freeze
= (O
) => objectFreeze(O
);
243 * Returns a new frozen shallow copy of the enumerable own properties
244 * of the provided object, according to the following rules :—
246 * - For data properties, create a nonconfigurable, nonwritable
247 * property with the same value.
249 * - For accessor properties, create a nonconfigurable accessor
250 * property with the same getter *and* setter.
252 * The prototype for the resulting object will be taken from the
253 * `.prototype` property of the provided constructor, or the
254 * `.prototype` of the `.constructor` of the provided object if the
255 * provided constructor is undefined. If the used constructor has a
256 * nonnullish `.[Symbol.species]`, that will be used instead. If the
257 * used constructor or species is nullish or does not have a
258 * `.prototype` property, the prototype is set to null.
260 * ※ The prototype of the provided object itself is ignored.
262 export const frozenCopy
= (O
, constructor = O
?.constructor) => {
264 // O is null or undefined.
266 "Piscēs: Cannot copy properties of null or undefined.",
269 // O is not null or undefined.
271 // (If not provided, the constructor will be the value of getting
272 // the `.constructor` property of O.)
273 const species
= constructor?.[SPECIES
] ?? constructor;
275 species
== null || !("prototype" in species
)
279 const keys
= ownKeys(O
);
280 for (let k
= 0; k
< keys
.length
; ++k
) {
282 const Desc
= getOwnPropertyDescriptor(O
, P
);
283 if (Desc
.enumerable
) {
284 // P is an enumerable property.
290 isAccessorDescriptor(Desc
)
306 // P is not an enumerable property.
310 return objectPreventExtensions(copy
);
315 * Returns the function on the provided value at the provided property
318 * ☡ This function throws if the provided property key does not have an
319 * associated value which is callable.
321 export const getMethod
= (V
, P
) => {
325 } else if (typeof func
!== "function") {
326 throw new TypeError(`Piscēs: Method not callable: ${P}`);
333 * Returns the property descriptor record for the own property with the
334 * provided property key on the provided value, or null if none exists.
336 * ※ This is effectively an alias for
337 * `Object.getOwnPropertyDescriptor`, but the return value is a proxied
338 * object with null prototype.
340 export const getOwnPropertyDescriptor
= (O
, P
) => {
341 const desc
= objectGetOwnPropertyDescriptor(O
, P
);
342 return desc
=== UNDEFINED
344 : toPropertyDescriptorRecord(desc
);
348 * Returns the property descriptors for the own properties on the
351 * ※ This is effectively an alias for
352 * `Object.getOwnPropertyDescriptors`, but the values on the resulting
353 * object are proxied objects with null prototypes.
355 export const getOwnPropertyDescriptors
= (O
) => {
356 const obj
= toObject(O
);
357 const keys
= ownKeys(obj
);
358 const descriptors
= {};
359 for (let k
= 0; k
< keys
.length
; ++k
) {
361 defineOwnDataProperty(
364 getOwnPropertyDescriptor(O
, key
),
371 * Returns an array of property keys on the provided value.
373 * ※ This is effectively an alias for `Reflect.ownKeys`, except that
374 * it does not require that the argument be an object.
376 export const getOwnPropertyKeys
= (O
) => ownKeys(toObject(O
));
379 * Returns an array of string‐valued own property keys on the
382 * ☡ This includes both enumerable and non·enumerable properties.
384 * ※ This is effectively an alias for `Object.getOwnPropertyNames`.
386 export const getOwnPropertyStrings
= (O
) => getOwnPropertyNames(O
);
389 * Returns an array of symbol‐valued own property keys on the
392 * ☡ This includes both enumerable and non·enumerable properties.
394 * ※ This is effectively an alias for
395 * `Object.getOwnPropertySymbols`.
397 export const getOwnPropertySymbols
= (O
) =>
398 objectGetOwnPropertySymbols(O
);
401 * Returns the value of the provided property key on the provided
404 * ※ This is effectively an alias for `Reflect.get`, except that it
405 * does not require that the argument be an object.
407 export const getPropertyValue
= (O
, P
, Receiver
= O
) =>
408 get(toObject(O
), P
, Receiver
);
411 * Returns the prototype of the provided value.
413 * ※ This is effectively an alias for `Object.getPrototypeOf`.
415 export const getPrototype
= (O
) => getPrototypeOf(O
);
418 * Returns whether the provided value has an own property with the
419 * provided property key.
421 * ※ This is effectively an alias for `Object.hasOwn`.
423 export const hasOwnProperty
= (O
, P
) => hasOwn(O
, P
);
426 * Returns whether the provided property key exists on the provided
429 * ※ This is effectively an alias for `Reflect.has`, except that it
430 * does not require that the argument be an object.
432 * ※ This includes properties present on the prototype chain.
434 export const hasProperty
= (O
, P
) => has(toObject(O
), P
);
436 /** Returns whether the provided value is an arraylike object. */
437 export const isArraylikeObject
= ($) => {
438 if (type($) !== "object") {
442 lengthOfArraylike($); // throws if not arraylike
451 * Returns whether the provided value is spreadable during array
454 * This is also used to determine which things should be treated as
457 export const isConcatSpreadableObject
= ($) => {
458 if (type($) !== "object") {
459 // The provided value is not an object.
462 // The provided value is an object.
463 const spreadable
= $[IS_CONCAT_SPREADABLE
];
464 return spreadable
!== UNDEFINED
? !!spreadable
: isArray($);
469 * Returns whether the provided value is an extensible object.
471 * ※ This function returns false for nonobjects.
473 * ※ This is effectively an alias for `Object.isExtensible`.
475 export const isExtensibleObject
= (O
) => isExtensible(O
);
479 * Returns whether the provided value is a property descriptor record
480 * as created by `toPropertyDescriptor`.
482 * ※ This function is provided to enable inspection of whether an
483 * object uses the property descriptor record proxy implementation,
484 * not as a general test of whether an object satisfies the
485 * requirements for property descriptors. In most cases, a more
486 * specific test, like `isAccessorDescriptor`, `isDataDescriptor`, or
487 * `isGenericDescriptor`, is preferrable.
489 isPropertyDescriptorRecord
,
492 * Converts the provided value to a property descriptor record.
494 * ※ The prototype of a property descriptor record is always `null`.
496 * ※ Actually constructing a property descriptor object using this
497 * class is only necessary if you need strict guarantees about the
498 * types of its properties; the resulting object is proxied to ensure
499 * the types match what one would expect from composing
500 * FromPropertyDescriptor and ToPropertyDescriptor in the Ecmascript
503 toPropertyDescriptorRecord
,
505 const proxyConstructor
= Proxy
;
506 const { add
: weakSetAdd
, has
: weakSetHas
} = WeakSet
.prototype;
507 const propertyDescriptorRecords
= new WeakSet();
508 const coercePropertyDescriptorValue
= (P
, V
) => {
517 if (V
!== undefined && typeof V
!== "function") {
519 "Piscēs: Getters must be callable.",
525 if (V
!== undefined && typeof V
!== "function") {
527 "Piscēs: Setters must be callable.",
536 const propertyDescriptorProxyHandler
= objectFreeze(
540 defineProperty(O
, P
, Desc
) {
542 P
=== "configurable" || P
=== "enumerable" ||
543 P
=== "writable" || P
=== "value" ||
544 P
=== "get" || P
=== "set"
546 // P is a property descriptor attribute.
547 const desc
= assign(objectCreate(null), Desc
);
548 if ("get" in desc
|| "set" in desc
) {
549 // Desc is an accessor property descriptor.
551 "Piscēs: Property descriptor attributes must be data properties.",
553 } else if ("value" in desc
|| !(P
in O
)) {
554 // Desc has a value or P does not already exist on O.
555 desc
.value
= coercePropertyDescriptorValue(
560 // Desc is not an accessor property descriptor and has no
561 // value, but an existing value is present on O.
564 const isAccessorDescriptor
= "get" === P
|| "set" === P
||
565 "get" in O
|| "set" in O
;
566 const isDataDescriptor
= "value" === P
||
568 "value" in O
|| "writable" in O
;
569 if (isAccessorDescriptor
&& isDataDescriptor
) {
570 // Both accessor and data attributes will be present on O
573 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
576 // P can be safely defined on O.
577 return reflectDefineProperty(O
, P
, desc
);
580 // P is not a property descriptor attribute.
581 return reflectDefineProperty(O
, P
, Desc
);
584 setPrototypeOf(O
, V
) {
586 // V is not the property descriptor prototype.
589 // V is the property descriptor prototype.
590 return reflectSetPrototypeOf(O
, V
);
598 isPropertyDescriptorRecord
: ($) =>
599 call(weakSetHas
, propertyDescriptorRecords
, [$]),
600 toPropertyDescriptorRecord
: (Obj
) => {
601 if (type(Obj
) !== "object") {
602 // The provided value is not an object.
604 `Piscēs: Cannot convert primitive to property descriptor: ${O}.`,
607 // The provided value is an object.
608 const desc
= create(null);
609 if ("enumerable" in Obj
) {
610 // An enumerable property is specified.
611 defineOwnDataProperty(desc
, "enumerable", !!Obj
.enumerable
);
613 // An enumerable property is not specified.
616 if ("configurable" in Obj
) {
617 // A configurable property is specified.
618 defineOwnDataProperty(
624 // A configurable property is not specified.
627 if ("value" in Obj
) {
628 // A value property is specified.
629 defineOwnDataProperty(desc
, "value", Obj
.value
);
631 // A value property is not specified.
634 if ("writable" in Obj
) {
635 // A writable property is specified.
636 defineOwnDataProperty(desc
, "writable", !!Obj
.writable
);
638 // A writable property is not specified.
642 // A get property is specified.
643 const getter
= Obj
.get;
644 if (getter
!== UNDEFINED
&& typeof getter
!== "function") {
645 // The getter is not callable.
646 throw new TypeError("Piscēs: Getters must be callable.");
648 // The getter is callable.
649 defineOwnDataProperty(desc
, "get", Obj
.get);
652 // A get property is not specified.
656 // A set property is specified.
657 const setter
= Obj
.set;
658 if (setter
!== UNDEFINED
&& typeof setter
!== "function") {
659 // The setter is not callable.
660 throw new TypeError("Piscēs: Setters must be callable.");
662 // The setter is callable.
663 defineOwnDataProperty(desc
, "set", Obj
.set);
666 // A set property is not specified.
670 ("get" in desc
|| "set" in desc
) &&
671 ("value" in desc
|| "writable" in desc
)
673 // Both accessor and data attributes have been defined.
675 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
678 // The property descriptor is valid.
679 const record
= new proxyConstructor(
681 propertyDescriptorProxyHandler
,
683 call(weakSetAdd
, propertyDescriptorRecords
, [record
]);
692 * Returns whether the provided value is an unfrozen object.
694 * ※ This function returns false for nonobjects.
696 * ※ This is effectively an alias for `!Object.isFrozen`.
698 export const isUnfrozenObject
= (O
) => !isFrozen(O
);
701 * Returns whether the provided value is an unsealed object.
703 * ※ This function returns false for nonobjects.
705 * ※ This is effectively an alias for `!Object.isSealed`.
707 export const isUnsealedObject
= (O
) => !isSealed(O
);
710 * Returns the length of the provided arraylike value.
712 * This can produce larger lengths than can actually be stored in
713 * arrays, because no such restrictions exist on arraylike methods.
715 * ☡ This function throws if the provided value is not arraylike.
717 export const lengthOfArraylike
= ({ length
}) => toLength(length
);
720 * Returns an array of key~value pairs for the enumerable,
721 * string‐valued property keys on the provided value.
723 * ※ This is effectively an alias for `Object.entries`.
725 export const namedEntries
= (O
) => entries(O
);
728 * Returns an array of the enumerable, string‐valued property keys on
729 * the provided value.
731 * ※ This is effectively an alias for `Object.keys`.
733 export const namedKeys
= (O
) => keys(O
);
736 * Returns an array of property values for the enumerable,
737 * string‐valued property keys on the provided value.
739 * ※ This is effectively an alias for `Object.values`.
741 export const namedValues
= (O
) => values(O
);
744 * Returns a new object with the provided prototype and property
747 * ※ This is effectively an alias for `Object.create`.
749 export const objectCreate
= (O
, Properties
) => create(O
, Properties
);
752 * Returns a new object with property keys and values from the provided
755 * ※ This is effectively an alias for `Object.fromEntries`.
757 export const objectFromEntries
= (iterable
) => fromEntries(iterable
);
760 * Marks the provided object as non·extensible, and returns the
763 * ※ This is effectively an alias for `Object.preventExtensions`.
765 export const preventExtensions
= (O
) => objectPreventExtensions(O
);
768 * Marks the provided object as non·extensible and marks all its
769 * properties as nonconfigurable, and returns the object.
771 * ※ This is effectively an alias for `Object.seal`.
773 export const seal
= (O
) => objectSeal(O
);
776 * Sets the provided property key to the provided value on the provided
777 * object and returns the object.
779 * ※ This function differs from `Reflect.set` in that it throws if the
780 * setting is unsuccessful.
782 * ☡ This function throws if the first argument is not an object.
784 export const setPropertyValue
= (O
, P
, V
, Receiver
= O
) => {
785 if (type(O
) !== "object") {
787 `Piscēs: Tried to set property but provided value was not an object: ${V}`,
789 } else if (!set(O
, P
, V
, Receiver
)) {
791 `Piscēs: Tried to set property on object but [[Set]] returned false: ${P}`,
799 * Sets the values of the enumerable own properties of the provided
800 * additional objects on the provided values.
802 * ※ This is effectively an alias for `Object.assign`.
804 export const setPropertyValues
= (target
, source
, ...sources
) => {
805 const to
= toObject(target
);
806 for (let i
= -1; i
< sources
.length
; ++i
) {
807 // Iterate over each source and set the appropriate property
809 const nextSource
= i
=== -1 ? source
: sources
[i
];
810 if (nextSource
!= null) {
811 // The current source is not nullish; handle its own properties.
812 const from = toObject(nextSource
);
813 const keys
= ownKeys(from);
814 for (let k
= 0; k
< keys
.length
; ++k
) {
815 // Iterate over each key in the current source and set it in
816 // the target object if it is enumerable.
817 const nextKey
= keys
[k
];
818 const desc
= reflectGetOwnPropertyDescriptor(from, nextKey
);
819 if (desc
!== UNDEFINED
&& desc
.enumerable
) {
820 // The current key is present and enumerable; set it to its
821 // corresponding value.
822 const propValue
= from[nextKey
];
823 to
[nextKey
] = propValue
;
825 // The current key is not present or not enumerable.
830 // The current source is nullish.
838 * Sets the prototype of the provided object to the provided value and
839 * returns the object.
841 * ※ This is effectively an alias for `Object.setPrototypeOf`, but it
842 * won’t throw when setting the prototype of a primitive to its current
845 export const setPrototype
= (O
, proto
) => {
846 const obj
= toObject(O
);
848 // The provided value is an object; set its prototype normally.
849 return setPrototypeOf(O
, proto
);
851 // The provided value is not an object; attempt to set the
852 // prototype on a coerced version with extensions prevented, then
853 // return the provided value.
855 // This will throw if the given prototype does not match the
856 // existing one on the coerced object.
857 setPrototypeOf(objectPreventExtensions(obj
), proto
);
863 * Returns the provided value converted to an object.
865 * Existing objects are returned with no modification.
867 * ☡ This function throws if its argument is null or undefined.
869 export const toObject
= ($) => {
871 // The provided value is nullish; this is an error.
873 `Piscēs: Cannot convert ${$} into an object.`,
876 // The provided value is not nullish; coerce it to an object.