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/>.
15 } from "./function.js";
26 * An object whose properties are lazy‐loaded from the methods on the
27 * own properties of the provided object.
29 * This is useful when you are looking to reference properties on
30 * objects which, due to module dependency graphs, cannot be guaranteed
31 * to have been initialized yet.
33 * The resulting properties will have the same attributes (regarding
34 * configurability, enumerability, and writability) as the
35 * corresponding properties on the methods object. If a property is
36 * marked as writable, the method will never be called if it is set
37 * before it is gotten. By necessity, the resulting properties are all
38 * configurable before they are accessed for the first time.
40 * Methods will be called with the resulting object as their this
43 * `LazyLoader` objects have the same prototype as the passed methods
46 export class LazyLoader
extends null {
48 * Constructs a new `LazyLoader` object.
50 * ☡ This function throws if the provided value is not an object.
52 constructor(loadMethods
) {
53 if (type(loadMethods
) !== "object") {
54 // The provided value is not an object; throw an error.
56 `Piscēs: Cannot construct LazyLoader: Provided argument is not an object: ${loadMethods}.`,
59 // The provided value is an object; process it and build the
61 const result
= objectCreate(getPrototype(loadMethods
));
62 const methodKeys
= getOwnPropertyKeys(loadMethods
);
63 for (let index
= 0; index
< methodKeys
.length
; ++index
) {
64 // Iterate over the property keys of the provided object and
65 // define getters and setters appropriately on the result.
66 const methodKey
= methodKeys
[index
];
67 const { configurable
, enumerable
, writable
} =
68 getOwnPropertyDescriptor(loadMethods
, methodKey
);
69 defineOwnProperty(result
, methodKey
, {
72 get: defineOwnProperty(
74 const value
= call(loadMethods
[methodKey
], result
, []);
75 defineOwnProperty(result
, methodKey
, {
84 { value
: toFunctionName(methodKey
, "get") },
89 defineOwnProperty(result
, methodKey
, {
96 { value
: toFunctionName(methodKey
, "set") },
107 * A property descriptor object.
109 * Actually constructing a property descriptor object using this class
110 * is only necessary if you need strict guarantees about the types of
111 * its properties; the resulting object is proxied to ensure the types
112 * match what one would expect from composing FromPropertyDescriptor
113 * and ToPropertyDescriptor in the Ecmascript specification.
115 * Otherwise, the instance properties and methods are generic.
117 export const { PropertyDescriptor
} = (() => {
118 class PropertyDescriptor
extends null {
120 * Constructs a new property descriptor object from the provided
123 * The resulting object is proxied to enforce types (for example,
124 * its `.enumerable` property, if defined, will always be a
128 if (type(O
) !== "object") {
129 // The provided value is not an object.
131 `Piscēs: Cannot convert primitive to property descriptor: ${O}.`,
134 // The provided value is an object.
135 const desc
= objectCreate(propertyDescriptorPrototype
);
136 if ("enumerable" in O
) {
137 // An enumerable property is specified.
138 desc
.enumerable
= !!O
.enumerable
;
140 // An enumerable property is not specified.
143 if ("configurable" in O
) {
144 // A configurable property is specified.
145 desc
.configurable
= !!O
.configurable
;
147 // A configurable property is not specified.
151 // A value property is specified.
152 desc
.value
= O
.value
;
154 // A value property is not specified.
157 if ("writable" in O
) {
158 // A writable property is specified.
159 desc
.writable
= !!O
.writable
;
161 // A writable property is not specified.
165 // A get property is specified.
166 const getter
= O
.get;
167 if (getter
!== undefined && typeof getter
!== "function") {
168 // The getter is not callable.
169 throw new TypeError("Piscēs: Getters must be callable.");
171 // The getter is callable.
175 // A get property is not specified.
179 // A set property is specified.
180 const setter
= O
.set;
181 if (setter
!== undefined && typeof setter
!== "function") {
182 // The setter is not callable.
183 throw new TypeError("Piscēs: Setters must be callable.");
185 // The setter is callable.
189 // A set property is not specified.
193 ("get" in desc
|| "set" in desc
) &&
194 ("value" in desc
|| "writable" in desc
)
196 // Both accessor and data attributes have been defined.
198 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
201 // The property descriptor is valid.
202 return new Proxy(desc
, propertyDescriptorProxyHandler
);
208 * Completes this property descriptor by setting missing values to
211 * This method modifies this object and returns undefined.
214 if (this !== undefined && !("get" in this || "set" in this)) {
215 // This is a generic or data descriptor.
216 if (!("value" in this)) {
217 // `value` is not defined on this.
218 this.value
= undefined;
220 // `value` is already defined on this.
223 if (!("writable" in this)) {
224 // `writable` is not defined on this.
225 this.writable
= false;
227 // `writable` is already defined on this.
231 // This is not a generic or data descriptor.
232 if (!("get" in this)) {
233 // `get` is not defined on this.
234 this.get = undefined;
236 // `get` is already defined on this.
239 if (!("set" in this)) {
240 // `set` is not defined on this.
241 this.set = undefined;
243 // `set` is already defined on this.
247 if (!("enumerable" in this)) {
248 // `enumerable` is not defined on this.
249 this.enumerable
= false;
251 // `enumerable` is already defined on this.
254 if (!("configurable" in this)) {
255 // `configurable` is not defined on this.
256 this.configurable
= false;
258 // `configurable` is already defined on this.
263 /** Gets whether this is an accessor descrtiptor. */
264 get isAccessorDescriptor() {
265 return this !== undefined && ("get" in this || "set" in this);
268 /** Gets whether this is a data descrtiptor. */
269 get isDataDescriptor() {
270 return this !== undefined &&
271 ("value" in this || "writable" in this);
274 /** Gets whether this is a fully‐populated property descriptor. */
275 get isFullyPopulated() {
276 return this !== undefined &&
277 ("value" in this && "writable" in this ||
278 "get" in this && "set" in this) &&
279 "enumerable" in this && "configurable" in this;
283 * Gets whether this is a generic (not accessor or data)
286 get isGenericDescriptor() {
287 return this !== undefined &&
288 !("get" in this || "set" in this || "value" in this ||
293 const coercePropertyDescriptorValue
= (P
, V
) => {
302 if (V
!== undefined && typeof V
!== "function") {
304 "Piscēs: Getters must be callable.",
310 if (V
!== undefined && typeof V
!== "function") {
312 "Piscēs: Setters must be callable.",
323 prototype: propertyDescriptorPrototype
,
324 } = PropertyDescriptor
;
326 const propertyDescriptorProxyHandler
= Object
.assign(
329 defineProperty(O
, P
, Desc
) {
331 P
=== "configurable" || P
=== "enumerable" ||
332 P
=== "writable" || P
=== "value" ||
333 P
=== "get" || P
=== "set"
335 // P is a property descriptor attribute.
336 const desc
= new PropertyDescriptor(Desc
);
337 if ("get" in desc
|| "set" in desc
) {
338 // Desc is an accessor property descriptor.
340 "Piscēs: Property descriptor attributes must be data properties.",
342 } else if ("value" in desc
|| !(P
in O
)) {
343 // Desc has a value or P does not already exist on O.
344 desc
.value
= coercePropertyDescriptorValue(P
, desc
.value
);
346 // Desc is not an accessor property descriptor and has no
350 const isAccessorDescriptor
= "get" === P
|| "set" === P
||
351 "get" in O
|| "set" in O
;
352 const isDataDescriptor
= "value" === P
|| "writable" === P
||
353 "value" in O
|| "writable" in O
;
354 if (isAccessorDescriptor
&& isDataDescriptor
) {
355 // Both accessor and data attributes will be present on O
358 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
361 // P can be safely defined on O.
362 return defineOwnProperty(O
, P
, desc
);
365 // P is not a property descriptor attribute.
366 return defineOwnProperty(O
, P
, Desc
);
369 set(O
, P
, V
, Receiver
) {
371 P
=== "configurable" || P
=== "enumerable" ||
372 P
=== "writable" || P
=== "value" ||
373 P
=== "get" || P
=== "set"
375 // P is a property descriptor attribute.
376 const newValue
= coercePropertyDescriptorValue(P
, V
);
377 const isAccessorDescriptor
= "get" === P
|| "set" === P
||
378 "get" in O
|| "set" in O
;
379 const isDataDescriptor
= "value" === P
|| "writable" === P
||
380 "value" in O
|| "writable" in O
;
381 if (isAccessorDescriptor
&& isDataDescriptor
) {
382 // Both accessor and data attributes will be present on O
385 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
388 // P can be safely defined on O.
390 // ☡ Receiver will be the *proxied* object, so passing it
391 // through to setPropertyValue here would produce an
394 // ☡ This has implications on objects with a proxied
395 // PropertyDescriptor in their prototype.
396 return setPropertyValue(O
, P
, newValue
, O
);
399 return setPropertyValue(O
, P
, V
, Receiver
);
402 setPrototypeOf(O
, V
) {
403 if (V
!== propertyDescriptorPrototype
) {
404 // V is not the property descriptor prototype.
407 // V is the property descriptor prototype.
408 return setPrototype(O
, V
);
414 return { PropertyDescriptor
};
418 * Defines an own property on the provided object on the provided
419 * property key using the provided property descriptor.
421 * ※ This is effectively an alias for `Object.defineProperty`.
423 export const defineOwnProperty
= createArrowFunction(
424 Object
.defineProperty
,
425 { name
: "defineOwnProperty" },
430 * Defines own properties on the provided object using the
431 * descriptors on the enumerable own properties of the provided
432 * additional objects.
434 * ※ This differs from `Object.defineProperties` in that it can take
435 * multiple source objects.
440 * Returns a new frozen shallow copy of the enumerable own properties
441 * of the provided object, according to the following rules :—
443 * - For data properties, create a nonconfigurable, nonwritable
444 * property with the same value.
446 * - For accessor properties, create a nonconfigurable accessor
447 * property with the same getter *and* setter.
449 * The prototype for the resulting object will be taken from the
450 * `.prototype` property of the provided constructor, or the
451 * `.prototype` of the `.constructor` of the provided object if the
452 * provided constructor is undefined. If the used constructor has a
453 * nonnullish `.[Symbol.species]`, that will be used instead. If the
454 * used constructor or species is nullish or does not have a
455 * `.prototype` property, the prototype is set to null.
457 * ※ The prototype of the provided object itself is ignored.
462 * Returns whether the provided object is frozen.
464 * ※ This function returns false for nonobjects.
466 * ※ This is effectively an alias for `!Object.isFrozen`.
471 * Returns whether the provided object is sealed.
473 * ※ This function returns false for nonobjects.
475 * ※ This is effectively an alias for `!Object.isSealed`.
480 * Sets the prototype of the provided object to the provided value
481 * and returns the object.
483 * ※ This is effectively an alias for `Object.setPrototypeOf`.
488 * Returns the provided value converted to an object.
490 * Existing objects are returned with no modification.
492 * ☡ This function throws if its argument is null or undefined.
496 const createObject
= Object
;
506 next
: generatorIteratorNext
,
507 } = getPrototypeOf(function* () {}.prototype);
508 const propertyDescriptorEntryIterablePrototype
= {
511 next
: bind(generatorIteratorNext
, this.generator(), []),
515 const propertyDescriptorEntryIterable
= ($) =>
516 create(propertyDescriptorEntryIterablePrototype
, {
517 generator
: { value
: $ },
521 defineOwnProperties
: (O
, ...sources
) => {
522 const { length
} = sources
;
523 for (let index
= 0; index
< length
; ++index
) {
524 defineProperties(O
, sources
[index
]);
528 frozenCopy
: (O
, constructor = O
?.constructor) => {
530 // O is null or undefined.
532 "Piscēs: Cannot copy properties of null or undefined.",
535 // O is not null or undefined.
537 // (If not provided, the constructor will be the value of
538 // getting the `.constructor` property of O.)
539 const species
= constructor?.[SPECIES
] ?? constructor;
540 return preventExtensions(
542 species
== null || !("prototype" in species
)
546 propertyDescriptorEntryIterable(function* () {
547 const ownPropertyKeys
= getOwnPropertyKeys(O
);
550 i
< ownPropertyKeys
.length
;
553 const P
= ownPropertyKeys
[i
];
554 const Desc
= getOwnPropertyDescriptor(O
, P
);
555 if (Desc
.enumerable
) {
556 // P is an enumerable property.
559 "get" in Desc
|| "set" in Desc
574 // P is not an enumerable property.
584 isUnfrozenObject
: (O
) => !isFrozen(O
),
585 isUnsealedObject
: (O
) => !isSealed(O
),
586 setPrototype
: (O
, proto
) => {
587 const obj
= toObject(O
);
589 // The provided value is an object; set its prototype normally.
590 return setPrototypeOf(O
, proto
);
592 // The provided value is not an object; attempt to set the
593 // prototype on a coerced version with extensions prevented,
594 // then return the provided value.
596 // This will throw if the given prototype does not match the
597 // existing one on the coerced object.
598 setPrototypeOf(preventExtensions(obj
), proto
);
604 // The provided value is nullish; this is an error.
606 `Piscēs: Cannot convert ${$} into an object.`,
609 // The provided value is not nullish; coerce it to an object.
610 return createObject($);
618 * Removes the provided property key from the provided object and
619 * returns the object.
621 * ※ This function differs from `Reflect.deleteProperty` and the
622 * `delete` operator in that it throws if the deletion is
625 * ☡ This function throws if the first argument is not an object.
630 * Returns an array of property keys on the provided object.
632 * ※ This is effectively an alias for `Reflect.ownKeys`, except that
633 * it does not require that the argument be an object.
638 * Returns the value of the provided property key on the provided
641 * ※ This is effectively an alias for `Reflect.get`, except that it
642 * does not require that the argument be an object.
647 * Returns whether the provided property key exists on the provided
650 * ※ This is effectively an alias for `Reflect.has`, except that it
651 * does not require that the argument be an object.
653 * ※ This includes properties present on the prototype chain.
658 * Sets the provided property key to the provided value on the
659 * provided object and returns the object.
661 * ※ This function differs from `Reflect.set` in that it throws if
662 * the setting is unsuccessful.
664 * ☡ This function throws if the first argument is not an object.
668 const { deleteProperty
, get, has
, ownKeys
, set } = Reflect
;
671 deleteOwnProperty
: (O
, P
) => {
672 if (type(O
) !== "object") {
674 `Piscēs: Tried to set property but provided value was not an object: ${V}`,
676 } else if (!deleteProperty(O
, P
)) {
678 `Piscēs: Tried to delete property from object but [[Delete]] returned false: ${P}`,
684 getOwnPropertyKeys
: (O
) => ownKeys(toObject(O
)),
685 getPropertyValue
: (O
, P
, Receiver
= O
) =>
686 get(toObject(O
), P
, Receiver
),
687 hasProperty
: (O
, P
) => has(toObject(O
), P
),
688 setPropertyValue
: (O
, P
, V
, Receiver
= O
) => {
689 if (type(O
) !== "object") {
691 `Piscēs: Tried to set property but provided value was not an object: ${V}`,
693 } else if (!set(O
, P
, V
, Receiver
)) {
695 `Piscēs: Tried to set property on object but [[Set]] returned false: ${P}`,
705 * Marks the provided object as non·extensible and marks all its
706 * properties as nonconfigurable and (if data properties) nonwritable,
707 * and returns the object.
709 * ※ This is effectively an alias for `Object.freeze`.
711 export const freeze
= createArrowFunction(Object
.freeze
);
714 * Returns the function on the provided value at the provided property
717 * ☡ This function throws if the provided property key does not have an
718 * associated value which is callable.
720 export const getMethod
= (V
, P
) => {
721 const func
= getPropertyValue(V
, P
);
724 } else if (typeof func
!== "function") {
725 throw new TypeError(`Piscēs: Method not callable: ${P}`);
732 * Returns the property descriptor for the own property with the
733 * provided property key on the provided object, or null if none
736 * ※ This is effectively an alias for
737 * `Object.getOwnPropertyDescriptor`.
739 export const getOwnPropertyDescriptor
= createArrowFunction(
740 Object
.getOwnPropertyDescriptor
,
744 * Returns the property descriptors for the own properties on the
747 * ※ This is effectively an alias for
748 * `Object.getOwnPropertyDescriptors`.
750 export const getOwnPropertyDescriptors
= createArrowFunction(
751 Object
.getOwnPropertyDescriptors
,
755 * Returns an array of string‐valued own property keys on the
758 * ☡ This includes both enumerable and non·enumerable properties.
760 * ※ This is effectively an alias for `Object.getOwnPropertyNames`.
762 export const getOwnPropertyStrings
= createArrowFunction(
763 Object
.getOwnPropertyNames
,
764 { name
: "getOwnPropertyStrings" },
768 * Returns an array of symbol‐valued own property keys on the
771 * ☡ This includes both enumerable and non·enumerable properties.
773 * ※ This is effectively an alias for
774 * `Object.getOwnPropertySymbols`.
776 export const getOwnPropertySymbols
= createArrowFunction(
777 Object
.getOwnPropertySymbols
,
781 * Returns the prototype of the provided object.
783 * ※ This is effectively an alias for `Object.getPrototypeOf`.
785 export const getPrototype
= createArrowFunction(
786 Object
.getPrototypeOf
,
787 { name
: "getPrototype" },
791 * Returns whether the provided object has an own property with the
792 * provided property key.
794 * ※ This is effectively an alias for `Object.hasOwn`.
796 export const hasOwnProperty
= createArrowFunction(Object
.hasOwn
, {
797 name
: "hasOwnProperty",
800 /** Returns whether the provided value is an arraylike object. */
801 export const isArraylikeObject
= ($) => {
802 if (type($) !== "object") {
806 lengthOfArraylike($); // throws if not arraylike
816 * Returns whether the provided value is spreadable during array
819 * This is also used to determine which things should be treated as
822 isConcatSpreadableObject
,
824 const { isArray
} = Array
;
827 isConcatSpreadableObject
: ($) => {
828 if (type($) !== "object") {
829 // The provided value is not an object.
832 // The provided value is an object.
833 const spreadable
= $[IS_CONCAT_SPREADABLE
];
834 return spreadable
!== undefined ? !!spreadable
: isArray($);
841 * Returns whether the provided object is extensible.
843 * ※ This function returns false for nonobjects.
845 * ※ This is effectively an alias for `Object.isExtensible`.
847 export const isExtensibleObject
= createArrowFunction(
849 { name
: "isExtensibleObject" },
853 * Returns the length of the provided arraylike value.
855 * This can produce larger lengths than can actually be stored in
856 * arrays, because no such restrictions exist on arraylike methods.
858 * ☡ This function throws if the provided value is not arraylike.
860 export const lengthOfArraylike
= ({ length
}) => toLength(length
);
863 * Returns an array of key~value pairs for the enumerable,
864 * string‐valued property keys on the provided object.
866 * ※ This is effectively an alias for `Object.entries`.
868 export const namedEntries
= createArrowFunction(Object
.entries
, {
869 name
: "namedEntries",
873 * Returns an array of the enumerable, string‐valued property keys on
874 * the provided object.
876 * ※ This is effectively an alias for `Object.keys`.
878 export const namedKeys
= createArrowFunction(Object
.keys
, {
883 * Returns an array of property values for the enumerable,
884 * string‐valued property keys on the provided object.
886 * ※ This is effectively an alias for `Object.values`.
888 export const namedValues
= createArrowFunction(Object
.values
, {
893 * Returns a new object with the provided prototype and property
896 * ※ This is effectively an alias for `Object.create`.
898 export const objectCreate
= createArrowFunction(Object
.create
, {
899 name
: "objectCreate",
903 * Returns a new object with the provided property keys and values.
905 * ※ This is effectively an alias for `Object.fromEntries`.
907 export const objectFromEntries
= createArrowFunction(
909 { name
: "objectFromEntries" },
913 * Marks the provided object as non·extensible, and returns the
916 * ※ This is effectively an alias for `Object.preventExtensions`.
918 export const preventExtensions
= createArrowFunction(
919 Object
.preventExtensions
,
923 * Marks the provided object as non·extensible and marks all its
924 * properties as nonconfigurable, and returns the object.
926 * ※ This is effectively an alias for `Object.seal`.
928 export const seal
= createArrowFunction(Object
.seal
);
931 * Sets the values of the enumerable own properties of the provided
932 * additional objects on the provided object.
934 * ※ This is effectively an alias for `Object.assign`.
936 export const setPropertyValues
= createArrowFunction(Object
.assign
, {
937 name
: "setPropertyValues",
941 * Returns the property key (symbol or string) corresponding to the
944 export const toPropertyKey
= ($) => {
945 const key
= toPrimitive($, "string");
946 return typeof key
=== "symbol" ? key
: `${key}`;