1 // SPDX-FileCopyrightText: 2022, 2023, 2025 Lady <https://www.ladys.computer/about/#lady>
2 // SPDX-License-Identifier: MPL-2.0
4 * ⁌ ♓🧩 Piscēs ∷ object.js
6 * Copyright © 2022–2023, 2025 Lady [@ Ladys Computer].
8 * This Source Code Form is subject to the terms of the Mozilla Public
9 * License, v. 2.0. If a copy of the MPL was not distributed with this
10 * file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
24 const PISC
ĒS
= "♓🧩 Piscēs";
26 const createArray
= Array
;
27 const { isArray
} = Array
;
28 const object
= Object
;
37 getOwnPropertyDescriptor: objectGetOwnPropertyDescriptor
,
39 getOwnPropertySymbols: objectGetOwnPropertySymbols
,
46 preventExtensions: objectPreventExtensions
,
54 defineProperty: reflectDefineProperty
,
56 getOwnPropertyDescriptor: reflectGetOwnPropertyDescriptor
,
60 setPrototypeOf: reflectSetPrototypeOf
,
64 * An object whose properties are lazy‐loaded from the methods on the
65 * own properties of the provided object.
67 * This is useful when you are looking to reference properties on
68 * objects which, due to module dependency graphs, cannot be guaranteed
69 * to have been initialized yet.
71 * The resulting properties will have the same attributes (regarding
72 * configurability, enumerability, and writability) as the
73 * corresponding properties on the methods object. If a property is
74 * marked as writable, the method will never be called if it is set
75 * before it is gotten. By necessity, the resulting properties are all
76 * configurable before they are accessed for the first time.
78 * Methods will be called with the resulting object as their this
81 * `LazyLoader´ objects have the same prototype as the passed methods
84 export class LazyLoader
extends null {
86 * Constructs a new `LazyLoader´ object.
88 * ☡ This function throws if the provided value is not an object.
90 constructor(loadMethods
) {
91 if (type(loadMethods
) !== "object") {
92 // The provided value is not an object; this is an error.
94 `${PISCĒS}: Cannot construct LazyLoader: Provided argument is not an object: ${loadMethods}.`,
97 // The provided value is an object.
99 // Process it and build the result.
100 const result
= create(getPrototypeOf(loadMethods
));
101 const methodKeys
= ownKeys(loadMethods
);
102 for (let index
= 0; index
< methodKeys
.length
; ++index
) {
103 // Iterate over the property keys of the provided object and
104 // define getters and setters appropriately on the result.
105 const methodKey
= methodKeys
[index
];
106 const { configurable
, enumerable
, writable
} =
107 getOwnPropertyDescriptor(loadMethods
, methodKey
);
111 assign(create(null), {
116 const value
= call(loadMethods
[methodKey
], result
, []);
120 assign(create(null), {
130 assign(create(null), {
131 value: toFunctionName(methodKey
, "get"),
140 assign(create(null), {
148 assign(create(null), {
149 value: toFunctionName(methodKey
, "set"),
162 * Defines an own enumerable data property on the provided object with
163 * the provided property key and value.
165 export const defineOwnDataProperty
= (O
, P
, V
) =>
169 assign(create(null), {
178 * Defines an own nonenumerable data property on the provided object
179 * with the provided property key and value.
181 export const defineOwnNonenumerableDataProperty
= (O
, P
, V
) =>
185 assign(create(null), {
194 * Defines an own property on the provided object on the provided
195 * property key using the provided property descriptor.
197 * ※ This is effectively an alias for `Object.defineProperty´.
199 export const defineOwnProperty
= (O
, P
, Desc
) =>
200 defineProperty(O
, P
, Desc
);
203 * Defines own properties on the provided object using the descriptors
204 * on the enumerable own properties of the provided additional objects.
206 * ※ This function differs from `Object.defineProperties´ in that it
207 * can take multiple source objects.
209 export const defineOwnProperties
= (O
, ...sources
) => {
210 const { length
} = sources
;
211 for (let k
= 0; k
< length
; ++k
) {
212 // Iterate over each source and define the appropriate properties
213 // on the provided object.
214 defineProperties(O
, sources
[k
]);
220 * Removes the provided property key from the provided object and
221 * returns the object.
223 * ※ This function differs from `Reflect.deleteProperty´ and the
224 * `delete´ operator in that it throws if the deletion is
227 * ☡ This function throws if the first argument is not an object.
229 export const deleteOwnProperty
= (O
, P
) => {
230 if (type(O
) !== "object") {
231 // The provided value is not an object; this is an error.
233 `${PISCĒS}: Tried to set property but provided value was not an object: ${V}`,
235 } else if (!deleteProperty(O
, P
)) {
236 // The provided property could not be deleted on the provided
237 // value; this is an error.
239 `${PISCĒS}: Tried to delete property from object but [[Delete]] returned false: ${P}`,
242 // The provided property was successfully deleted.
244 // Return the provided value.
250 * Marks the provided object as non·extensible and marks all its
251 * properties as nonconfigurable and (if data properties) nonwritable,
252 * and returns the object.
254 * ※ This is effectively an alias for `Object.freeze´.
256 export const freeze
= (O
) => objectFreeze(O
);
259 * Returns a new frozen shallow copy of the enumerable own properties
260 * of the provided object, according to the following rules :—
262 * - For data properties, create a nonconfigurable, nonwritable
263 * property with the same value.
265 * - For accessor properties, create a nonconfigurable accessor
266 * property with the same getter ⹐and⹑ setter.
268 * The prototype for the resulting object will be taken from the
269 * `.prototype´ property of the provided constructor, or the
270 * `.prototype´ of the `.constructor´ of the provided object if the
271 * provided constructor is undefined. If the used constructor has a
272 * nonnullish `.[Symbol.species]´, that will be used instead. If the
273 * used constructor or species is nullish or does not have a
274 * `.prototype´ property, the prototype is set to null.
276 * ※ The prototype of the provided object itself is ignored.
278 export const frozenCopy
= (O
, constructor = O
?.constructor) => {
280 // O is null or undefined.
282 `${PISCĒS}: Cannot copy properties of null or undefined.`,
285 // O is not null or undefined.
287 // (If not provided, the constructor will be the value of getting
288 // the `.constructor´ property of O.)
289 const species
= constructor?.[SPECIES
] ?? constructor;
291 species
== null || !("prototype" in species
)
295 const keys
= ownKeys(O
);
296 for (let k
= 0; k
< keys
.length
; ++k
) {
297 // Iterate over each key and define the appropriate value on the
300 const Desc
= getOwnPropertyDescriptor(O
, P
);
301 if (Desc
.enumerable
) {
302 // P is an enumerable property.
308 isAccessorDescriptor(Desc
)
324 // P is not an enumerable property.
328 return objectPreventExtensions(copy
);
333 * Returns the function on the provided value at the provided property
336 * ☡ This function throws if the provided property key does not have an
337 * associated value which is either nullish or callable.
339 export const getMethod
= (V
, P
) => {
342 // The value of the provided property is nullish.
346 } else if (typeof func
!== "function") {
347 // The value of the provided property is not callable; this is an
349 throw new TypeError(`${PISCĒS}: Method not callable: ${P}`);
351 // The value of the provided property is callable.
359 * Returns the property descriptor record for the own property with the
360 * provided property key on the provided value, or null if none exists.
362 * ※ This is effectively an alias for
363 * `Object.getOwnPropertyDescriptor´, but the return value is a proxied
364 * object with null prototype.
366 export const getOwnPropertyDescriptor
= (O
, P
) => {
367 const desc
= objectGetOwnPropertyDescriptor(O
, P
);
368 return desc
=== UNDEFINED
370 : toPropertyDescriptorRecord(desc
);
374 * Returns the property descriptors for the own properties on the
377 * ※ This is effectively an alias for
378 * `Object.getOwnPropertyDescriptors´, but the values on the resulting
379 * object are proxied objects with null prototypes.
381 export const getOwnPropertyDescriptors
= (O
) => {
382 const obj
= toObject(O
);
383 const keys
= ownKeys(obj
);
384 const descriptors
= {};
385 for (let k
= 0; k
< keys
.length
; ++k
) {
386 // Iterate over the keys of the provided object and collect its
389 defineOwnDataProperty(
392 getOwnPropertyDescriptor(O
, key
),
399 * Returns an array of own property entries on the provided value,
400 * using the provided receiver if given.
402 export const getOwnPropertyEntries
= (O
, Receiver
= O
) => {
403 const obj
= toObject(O
);
404 const keys
= ownKeys(obj
);
405 const target
= Receiver
=== UNDEFINED
? obj : toObject(Receiver
);
406 const result
= createArray(keys
.length
);
407 for (let k
= 0; k
< keys
.length
; ++k
) {
408 // Iterate over each key and add the corresponding entry to the
411 defineOwnDataProperty(
414 [key
, getOwnPropertyValue(obj
, keys
[k
], target
)],
421 * Returns an array of own property keys on the provided value.
423 * ※ This is effectively an alias for `Reflect.ownKeys´, except that
424 * it does not require that the argument be an object.
426 export const getOwnPropertyKeys
= (O
) => ownKeys(toObject(O
));
429 * Returns an array of string‐valued own property keys on the
432 * ☡ This includes both enumerable and non·enumerable properties.
434 * ※ This is effectively an alias for `Object.getOwnPropertyNames´.
436 export const getOwnPropertyStrings
= (O
) => getOwnPropertyNames(O
);
439 * Returns an array of symbol‐valued own property keys on the
442 * ☡ This includes both enumerable and non·enumerable properties.
444 * ※ This is effectively an alias for
445 * `Object.getOwnPropertySymbols´.
447 export const getOwnPropertySymbols
= (O
) =>
448 objectGetOwnPropertySymbols(O
);
451 * Returns the value of the provided own property on the provided
452 * value using the provided receiver, or undefined if the provided
453 * property is not an own property on the provided value.
455 * ※ If the receiver is not provided, it defaults to the provided
458 export const getOwnPropertyValue
= (O
, P
, Receiver
= UNDEFINED
) => {
459 const obj
= toObject(O
);
460 const desc
= getOwnPropertyDescriptor(O
, P
);
461 if (desc
=== UNDEFINED
) {
462 // The provided property is not an own property on the provided
468 if (isDataDescriptor(desc
)) {
469 // The provided property is a data property.
474 // The provided property is an accessor property.
476 // Get its value using the appropriate receiver.
477 const target
= Receiver
=== UNDEFINED
? obj : toObject(Receiver
);
478 return call(desc
.get, target
, []);
483 * Returns an array of own property values on the provided value, using
484 * the provided receiver if given.
486 export const getOwnPropertyValues
= (O
, Receiver
= O
) => {
487 const obj
= toObject(O
);
488 const keys
= ownKeys(obj
);
489 const target
= Receiver
=== UNDEFINED
? obj : toObject(Receiver
);
490 const result
= createArray(keys
.length
);
491 for (let k
= 0; k
< keys
.length
; ++k
) {
492 // Iterate over each key and collect the values.
493 defineOwnDataProperty(
496 getOwnPropertyValue(obj
, keys
[k
], target
),
503 * Returns the value of the provided property key on the provided
506 * ※ This is effectively an alias for `Reflect.get´, except that it
507 * does not require that the argument be an object.
509 export const getPropertyValue
= (O
, P
, Receiver
= O
) =>
510 get(toObject(O
), P
, Receiver
);
513 * Returns the prototype of the provided value.
515 * ※ This is effectively an alias for `Object.getPrototypeOf´.
517 export const getPrototype
= (O
) => getPrototypeOf(O
);
520 * Returns whether the provided value has an own property with the
521 * provided property key.
523 * ※ This is effectively an alias for `Object.hasOwn´.
525 export const hasOwnProperty
= (O
, P
) => hasOwn(O
, P
);
528 * Returns whether the provided property key exists on the provided
531 * ※ This is effectively an alias for `Reflect.has´, except that it
532 * does not require that the argument be an object.
534 * ※ This includes properties present on the prototype chain.
536 export const hasProperty
= (O
, P
) => has(toObject(O
), P
);
538 /** Returns whether the provided value is an arraylike object. */
539 export const isArraylikeObject
= ($) => {
540 if (type($) !== "object") {
541 // The provided value is not an object.
544 // The provided value is an object.
546 // Try to get the length and return true.
548 // ※ If this throws, the object is not arraylike.
549 lengthOfArraylike($);
552 // Getting the length failed; return false.
559 * Returns whether the provided value is spreadable during array
562 * This is also used to determine which things should be treated as
565 export const isConcatSpreadableObject
= ($) => {
566 if (type($) !== "object") {
567 // The provided value is not an object.
570 // The provided value is an object.
571 const spreadable
= $[IS_CONCAT_SPREADABLE
];
572 return spreadable
!== UNDEFINED
? !!spreadable : isArray($);
577 * Returns whether the provided value is an extensible object.
579 * ※ This function returns false for nonobjects.
581 * ※ This is effectively an alias for `Object.isExtensible´.
583 export const isExtensibleObject
= (O
) => isExtensible(O
);
587 * Returns whether the provided value is a property descriptor record
588 * as created by `toPropertyDescriptor´.
590 * ※ This function is provided to enable inspection of whether an
591 * object uses the ♓🧩 Piscēs property descriptor record proxy
592 * implementation, not as a general test of whether an object
593 * satisfies the requirements for property descriptors. In most
594 * cases, a more specific test, like `isAccessorDescriptor´,
595 * `isDataDescriptor´, or `isGenericDescriptor´, is preferrable.
597 isPropertyDescriptorRecord
,
600 * Converts the provided value to a property descriptor record.
602 * ※ The prototype of a property descriptor record is always `null´.
604 * ※ Actually constructing a property descriptor object using this
605 * class is only necessary if you need strict guarantees about the
606 * types of its properties; the resulting object is proxied to ensure
607 * the types match what one would expect from composing
608 * `FromPropertyDescriptor´ and `ToPropertyDescriptor´ in the
609 * Ecmascript specification.
611 toPropertyDescriptorRecord
,
613 const proxyConstructor
= Proxy
;
614 const { add: weakSetAdd
, has: weakSetHas
} = WeakSet
.prototype;
615 const propertyDescriptorRecords
= new WeakSet();
616 const coercePropertyDescriptorValue
= (P
, V
) => {
625 if (V
!== UNDEFINED
&& typeof V
!== "function") {
626 // The provided value is not callable; this is an error.
628 `${PISCĒS}: Getters must be callable.`,
631 // The provided value is callable.
635 if (V
!== UNDEFINED
&& typeof V
!== "function") {
636 // The provided value is not callable; this is an error.
638 `${PISCĒS}: Setters must be callable.`,
641 // The provided value is callable.
648 const propertyDescriptorProxyHandler
= objectFreeze(
652 defineProperty(O
, P
, Desc
) {
654 P
=== "configurable" || P
=== "enumerable"
655 || P
=== "writable" || P
=== "value"
656 || P
=== "get" || P
=== "set"
658 // `P´ is a property descriptor attribute.
659 const desc
= assign(objectCreate(null), Desc
);
660 if ("get" in desc
|| "set" in desc
) {
661 // `Desc´ is an accessor property descriptor.
663 `${PISCĒS}: Property descriptor attributes must be data properties.`,
665 } else if ("value" in desc
|| !(P
in O
)) {
666 // `Desc´ has a value or `P´ does not already exist on
668 desc
.value
= coercePropertyDescriptorValue(
673 // `Desc´ is not an accessor property descriptor and has
674 // no value, but an existing value is present on `O´.
677 const isAccessorDescriptor
= "get" === P
|| "set" === P
678 || "get" in O
|| "set" in O
;
679 const isDataDescriptor
= "value" === P
681 || "value" in O
|| "writable" in O
;
682 if (isAccessorDescriptor
&& isDataDescriptor
) {
683 // Both accessor and data attributes will be present on
684 // `O´ after defining `P´; this is an error.
686 `${PISCĒS}: Property descriptors cannot specify both accessor and data attributes.`,
689 // `P´ can be safely defined on `O´.
690 return reflectDefineProperty(O
, P
, desc
);
693 // `P´ is not a property descriptor attribute.
694 return reflectDefineProperty(O
, P
, Desc
);
697 setPrototypeOf(O
, V
) {
699 // `V´ is not the property descriptor prototype.
702 // `V´ is the property descriptor prototype.
703 return reflectSetPrototypeOf(O
, V
);
711 isPropertyDescriptorRecord: ($) =>
712 call(weakSetHas
, propertyDescriptorRecords
, [$]),
713 toPropertyDescriptorRecord: (Obj
) => {
714 if (type(Obj
) !== "object") {
715 // The provided value is not an object; this is an error.
717 `${PISCĒS}: Cannot convert primitive to property descriptor: ${O}.`,
720 // The provided value is an object.
721 const desc
= create(null);
722 if ("enumerable" in Obj
) {
723 // An enumerable property is specified.
724 defineOwnDataProperty(desc
, "enumerable", !!Obj
.enumerable
);
726 // An enumerable property is not specified.
729 if ("configurable" in Obj
) {
730 // A configurable property is specified.
731 defineOwnDataProperty(
737 // A configurable property is not specified.
740 if ("value" in Obj
) {
741 // A value property is specified.
742 defineOwnDataProperty(desc
, "value", Obj
.value
);
744 // A value property is not specified.
747 if ("writable" in Obj
) {
748 // A writable property is specified.
749 defineOwnDataProperty(desc
, "writable", !!Obj
.writable
);
751 // A writable property is not specified.
755 // A get property is specified.
756 const getter
= Obj
.get;
757 if (getter
!== UNDEFINED
&& typeof getter
!== "function") {
758 // The getter is not callable; this is an error.
760 `${PISCĒS}: Getters must be callable.`,
763 // The getter is callable.
764 defineOwnDataProperty(desc
, "get", Obj
.get);
767 // A get property is not specified.
771 // A set property is specified.
772 const setter
= Obj
.set;
773 if (setter
!== UNDEFINED
&& typeof setter
!== "function") {
774 // The setter is not callable; this is an error.
776 `${PISCĒS}: Setters must be callable.`,
779 // The setter is callable.
780 defineOwnDataProperty(desc
, "set", Obj
.set);
783 // A set property is not specified.
787 ("get" in desc
|| "set" in desc
)
788 && ("value" in desc
|| "writable" in desc
)
790 // Both accessor and data attributes have been defined; this
793 `${PISCĒS}: Property descriptors cannot specify both accessor and data attributes.`,
796 // The property descriptor is valid.
797 const record
= new proxyConstructor(
799 propertyDescriptorProxyHandler
,
801 call(weakSetAdd
, propertyDescriptorRecords
, [record
]);
810 * Returns whether the provided value is an unfrozen object.
812 * ※ This function returns false for nonobjects.
814 * ※ This is effectively an alias for `!Object.isFrozen´.
816 export const isUnfrozenObject
= (O
) => !isFrozen(O
);
819 * Returns whether the provided value is an unsealed object.
821 * ※ This function returns false for nonobjects.
823 * ※ This is effectively an alias for `!Object.isSealed´.
825 export const isUnsealedObject
= (O
) => !isSealed(O
);
828 * Returns the length of the provided arraylike value.
830 * This can produce larger lengths than can actually be stored in
831 * arrays, because no such restrictions exist on arraylike methods.
833 * ☡ This function throws if the provided value is not arraylike.
835 export const lengthOfArraylike
= ({ length
}) => toLength(length
);
838 * Returns an array of key~value pairs for the enumerable,
839 * string‐valued property keys on the provided value.
841 * ※ This is effectively an alias for `Object.entries´.
843 export const namedEntries
= (O
) => entries(O
);
846 * Returns an array of the enumerable, string‐valued property keys on
847 * the provided value.
849 * ※ This is effectively an alias for `Object.keys´.
851 export const namedKeys
= (O
) => keys(O
);
854 * Returns an array of property values for the enumerable,
855 * string‐valued property keys on the provided value.
857 * ※ This is effectively an alias for `Object.values´.
859 export const namedValues
= (O
) => values(O
);
862 * Returns a new object with the provided prototype and property
865 * ※ This is effectively an alias for `Object.create´.
867 export const objectCreate
= (O
, Properties
) => create(O
, Properties
);
870 * Returns a new object with property keys and values from the provided
873 * ※ This is effectively an alias for `Object.fromEntries´.
875 export const objectFromEntries
= (iterable
) => fromEntries(iterable
);
878 * Marks the provided object as non·extensible, and returns the
881 * ※ This is effectively an alias for `Object.preventExtensions´.
883 export const preventExtensions
= (O
) => objectPreventExtensions(O
);
886 * Marks the provided object as non·extensible and marks all its
887 * properties as nonconfigurable, and returns the object.
889 * ※ This is effectively an alias for `Object.seal´.
891 export const seal
= (O
) => objectSeal(O
);
894 * Sets the provided property key to the provided value on the provided
895 * object and returns the object.
897 * ※ This function differs from `Reflect.set´ in that it throws if the
898 * setting is unsuccessful.
900 * ☡ This function throws if the first argument is not an object.
902 export const setPropertyValue
= (O
, P
, V
, Receiver
= O
) => {
903 if (type(O
) !== "object") {
904 // The provided value is not an object; this is an error.
906 `${PISCĒS}: Tried to set property but provided value was not an object: ${V}`,
908 } else if (!set(O
, P
, V
, Receiver
)) {
909 // Setting the property fails; this is an error.
911 `${PISCĒS}: Tried to set property on object but [[Set]] returned false: ${P}`,
914 // The property was successfully set.
916 // Return the provided object.
922 * Sets the values of the enumerable own properties of the provided
923 * additional objects on the provided values.
925 * ※ This is effectively an alias for `Object.assign´.
927 export const setPropertyValues
= (target
, source
, ...sources
) => {
928 const to
= toObject(target
);
929 for (let i
= -1; i
< sources
.length
; ++i
) {
930 // Iterate over each source and set the appropriate property
932 const nextSource
= i
=== -1 ? source : sources
[i
];
933 if (nextSource
!= null) {
934 // The current source is not nullish.
936 // Handle its own properties.
937 const from = toObject(nextSource
);
938 const keys
= ownKeys(from);
939 for (let k
= 0; k
< keys
.length
; ++k
) {
940 // Iterate over each key in the current source and set it in
941 // the target object if it is enumerable.
942 const nextKey
= keys
[k
];
943 const desc
= reflectGetOwnPropertyDescriptor(from, nextKey
);
944 if (desc
!== UNDEFINED
&& desc
.enumerable
) {
945 // The current key is present and enumerable.
947 // Set it to its corresponding value.
948 const propValue
= from[nextKey
];
949 to
[nextKey
] = propValue
;
951 // The current key is not present or not enumerable.
956 // The current source is nullish.
964 * Sets the prototype of the provided object to the provided value and
965 * returns the object.
967 * ※ This is effectively an alias for `Object.setPrototypeOf´, but it
968 * won¦t throw when setting the prototype of a primitive to its current
971 export const setPrototype
= (O
, proto
) => {
972 const obj
= toObject(O
);
974 // The provided value is an object.
976 // Set its prototype normally.
977 return setPrototypeOf(O
, proto
);
979 // The provided value is not an object.
981 // Attempt to set the prototype on a coerced version with
982 // extensions prevented, then return the provided value.
984 // ☡ This will throw if the given prototype does not match the
985 // existing one on the coerced object.
986 setPrototypeOf(objectPreventExtensions(obj
), proto
);
992 * Returns the provided value converted to an object.
994 * Existing objects are returned with no modification.
996 * ☡ This function throws if its argument is null or undefined.
998 export const toObject
= ($) => {
1000 // The provided value is nullish; this is an error.
1001 throw new TypeError(
1002 `${PISCĒS}: Cannot convert ${$} into an object.`,
1005 // The provided value is not nullish.
1007 // Coerce it to an object.