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";
25 * An object whose properties are lazy‐loaded from the methods on the
26 * own properties of the provided object.
28 * This is useful when you are looking to reference properties on
29 * objects which, due to module dependency graphs, cannot be guaranteed
30 * to have been initialized yet.
32 * The resulting properties will have the same attributes (regarding
33 * configurability, enumerability, and writability) as the
34 * corresponding properties on the methods object. If a property is
35 * marked as writable, the method will never be called if it is set
36 * before it is gotten. By necessity, the resulting properties are all
37 * configurable before they are accessed for the first time.
39 * Methods will be called with the resulting object as their this
42 * `LazyLoader` objects have the same prototype as the passed methods
45 export class LazyLoader
extends null {
47 * Constructs a new `LazyLoader` object.
49 * ☡ This function throws if the provided value is not an object.
51 constructor(loadMethods
) {
52 if (type(loadMethods
) !== "object") {
53 // The provided value is not an object; throw an error.
55 `Piscēs: Cannot construct LazyLoader: Provided argument is not an object: ${loadMethods}.`,
58 // The provided value is an object; process it and build the
60 const result
= objectCreate(getPrototype(loadMethods
));
61 const methodKeys
= getOwnPropertyKeys(loadMethods
);
62 for (let index
= 0; index
< methodKeys
.length
; ++index
) {
63 // Iterate over the property keys of the provided object and
64 // define getters and setters appropriately on the result.
65 const methodKey
= methodKeys
[index
];
66 const { configurable
, enumerable
, writable
} =
67 getOwnPropertyDescriptor(loadMethods
, methodKey
);
68 defineOwnProperty(result
, methodKey
, {
71 get: defineOwnProperty(
73 const value
= call(loadMethods
[methodKey
], result
, []);
74 defineOwnProperty(result
, methodKey
, {
83 { value
: toFunctionName(methodKey
, "get") },
88 defineOwnProperty(result
, methodKey
, {
95 { value
: toFunctionName(methodKey
, "set") },
106 * A property descriptor object.
108 * Actually constructing a property descriptor object using this class
109 * is only necessary if you need strict guarantees about the types of
110 * its properties; the resulting object is proxied to ensure the types
111 * match what one would expect from composing FromPropertyDescriptor
112 * and ToPropertyDescriptor in the Ecmascript specification.
114 * Otherwise, the instance properties and methods are generic.
116 export const { PropertyDescriptor
} = (() => {
117 class PropertyDescriptor
extends null {
119 * Constructs a new property descriptor object from the provided
122 * The resulting object is proxied to enforce types (for example,
123 * its `.enumerable` property, if defined, will always be a
127 if (type(O
) !== "object") {
128 // The provided value is not an object.
130 `Piscēs: Cannot convert primitive to property descriptor: ${O}.`,
133 // The provided value is an object.
134 const desc
= objectCreate(propertyDescriptorPrototype
);
135 if ("enumerable" in O
) {
136 // An enumerable property is specified.
137 desc
.enumerable
= !!O
.enumerable
;
139 // An enumerable property is not specified.
142 if ("configurable" in O
) {
143 // A configurable property is specified.
144 desc
.configurable
= !!O
.configurable
;
146 // A configurable property is not specified.
150 // A value property is specified.
151 desc
.value
= O
.value
;
153 // A value property is not specified.
156 if ("writable" in O
) {
157 // A writable property is specified.
158 desc
.writable
= !!O
.writable
;
160 // A writable property is not specified.
164 // A get property is specified.
165 const getter
= O
.get;
166 if (getter
!== undefined && typeof getter
!== "function") {
167 // The getter is not callable.
168 throw new TypeError("Piscēs: Getters must be callable.");
170 // The getter is callable.
174 // A get property is not specified.
178 // A set property is specified.
179 const setter
= O
.set;
180 if (setter
!== undefined && typeof setter
!== "function") {
181 // The setter is not callable.
182 throw new TypeError("Piscēs: Setters must be callable.");
184 // The setter is callable.
188 // A set property is not specified.
192 ("get" in desc
|| "set" in desc
) &&
193 ("value" in desc
|| "writable" in desc
)
195 // Both accessor and data attributes have been defined.
197 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
200 // The property descriptor is valid.
201 return new Proxy(desc
, propertyDescriptorProxyHandler
);
207 * Completes this property descriptor by setting missing values to
210 * This method modifies this object and returns undefined.
213 if (this !== undefined && !("get" in this || "set" in this)) {
214 // This is a generic or data descriptor.
215 if (!("value" in this)) {
216 // `value` is not defined on this.
217 this.value
= undefined;
219 // `value` is already defined on this.
222 if (!("writable" in this)) {
223 // `writable` is not defined on this.
224 this.writable
= false;
226 // `writable` is already defined on this.
230 // This is not a generic or data descriptor.
231 if (!("get" in this)) {
232 // `get` is not defined on this.
233 this.get = undefined;
235 // `get` is already defined on this.
238 if (!("set" in this)) {
239 // `set` is not defined on this.
240 this.set = undefined;
242 // `set` is already defined on this.
246 if (!("enumerable" in this)) {
247 // `enumerable` is not defined on this.
248 this.enumerable
= false;
250 // `enumerable` is already defined on this.
253 if (!("configurable" in this)) {
254 // `configurable` is not defined on this.
255 this.configurable
= false;
257 // `configurable` is already defined on this.
262 /** Gets whether this is an accessor descrtiptor. */
263 get isAccessorDescriptor() {
264 return this !== undefined && ("get" in this || "set" in this);
267 /** Gets whether this is a data descrtiptor. */
268 get isDataDescriptor() {
269 return this !== undefined &&
270 ("value" in this || "writable" in this);
273 /** Gets whether this is a fully‐populated property descriptor. */
274 get isFullyPopulated() {
275 return this !== undefined &&
276 ("value" in this && "writable" in this ||
277 "get" in this && "set" in this) &&
278 "enumerable" in this && "configurable" in this;
282 * Gets whether this is a generic (not accessor or data)
285 get isGenericDescriptor() {
286 return this !== undefined &&
287 !("get" in this || "set" in this || "value" in this ||
292 const coercePropertyDescriptorValue
= (P
, V
) => {
301 if (V
!== undefined && typeof V
!== "function") {
303 "Piscēs: Getters must be callable.",
309 if (V
!== undefined && typeof V
!== "function") {
311 "Piscēs: Setters must be callable.",
322 prototype: propertyDescriptorPrototype
,
323 } = PropertyDescriptor
;
325 const propertyDescriptorProxyHandler
= Object
.assign(
328 defineProperty(O
, P
, Desc
) {
330 P
=== "configurable" || P
=== "enumerable" ||
331 P
=== "writable" || P
=== "value" ||
332 P
=== "get" || P
=== "set"
334 // P is a property descriptor attribute.
335 const desc
= new PropertyDescriptor(Desc
);
336 if ("get" in desc
|| "set" in desc
) {
337 // Desc is an accessor property descriptor.
339 "Piscēs: Property descriptor attributes must be data properties.",
341 } else if ("value" in desc
|| !(P
in O
)) {
342 // Desc has a value or P does not already exist on O.
343 desc
.value
= coercePropertyDescriptorValue(P
, desc
.value
);
345 // Desc is not an accessor property descriptor and has no
349 const isAccessorDescriptor
= "get" === P
|| "set" === P
||
350 "get" in O
|| "set" in O
;
351 const isDataDescriptor
= "value" === P
|| "writable" === P
||
352 "value" in O
|| "writable" in O
;
353 if (isAccessorDescriptor
&& isDataDescriptor
) {
354 // Both accessor and data attributes will be present on O
357 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
360 // P can be safely defined on O.
361 return defineOwnProperty(O
, P
, desc
);
364 // P is not a property descriptor attribute.
365 return defineOwnProperty(O
, P
, Desc
);
368 set(O
, P
, V
, Receiver
) {
370 P
=== "configurable" || P
=== "enumerable" ||
371 P
=== "writable" || P
=== "value" ||
372 P
=== "get" || P
=== "set"
374 // P is a property descriptor attribute.
375 const newValue
= coercePropertyDescriptorValue(P
, V
);
376 const isAccessorDescriptor
= "get" === P
|| "set" === P
||
377 "get" in O
|| "set" in O
;
378 const isDataDescriptor
= "value" === P
|| "writable" === P
||
379 "value" in O
|| "writable" in O
;
380 if (isAccessorDescriptor
&& isDataDescriptor
) {
381 // Both accessor and data attributes will be present on O
384 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
387 // P can be safely defined on O.
389 // ☡ Receiver will be the *proxied* object, so passing it
390 // through to setPropertyValue here would produce an
393 // ☡ This has implications on objects with a proxied
394 // PropertyDescriptor in their prototype.
395 return setPropertyValue(O
, P
, newValue
, O
);
398 return setPropertyValue(O
, P
, V
, Receiver
);
401 setPrototypeOf(O
, V
) {
402 if (V
!== propertyDescriptorPrototype
) {
403 // V is not the property descriptor prototype.
406 // V is the property descriptor prototype.
407 return setPrototype(O
, V
);
413 return { PropertyDescriptor
};
417 * Defines an own property on the provided object on the provided
418 * property key using the provided property descriptor.
420 * ※ This is effectively an alias for `Object.defineProperty`.
422 export const defineOwnProperty
= createArrowFunction(
423 Object
.defineProperty
,
424 { name
: "defineOwnProperty" },
429 * Defines own properties on the provided object using the
430 * descriptors on the enumerable own properties of the provided
431 * additional objects.
433 * ※ This differs from `Object.defineProperties` in that it can take
434 * multiple source objects.
439 * Returns a new frozen shallow copy of the enumerable own properties
440 * of the provided object, according to the following rules :—
442 * - For data properties, create a nonconfigurable, nonwritable
443 * property with the same value.
445 * - For accessor properties, create a nonconfigurable accessor
446 * property with the same getter *and* setter.
448 * The prototype for the resulting object will be taken from the
449 * `.prototype` property of the provided constructor, or the
450 * `.prototype` of the `.constructor` of the provided object if the
451 * provided constructor is undefined. If the used constructor has a
452 * nonnullish `.[Symbol.species]`, that will be used instead. If the
453 * used constructor or species is nullish or does not have a
454 * `.prototype` property, the prototype is set to null.
456 * ※ The prototype of the provided object itself is ignored.
461 * Returns whether the provided object is frozen.
463 * ※ This function returns false for nonobjects.
465 * ※ This is effectively an alias for `!Object.isFrozen`.
470 * Returns whether the provided object is sealed.
472 * ※ This function returns false for nonobjects.
474 * ※ This is effectively an alias for `!Object.isSealed`.
479 * Sets the prototype of the provided object to the provided value
480 * and returns the object.
482 * ※ This is effectively an alias for `Object.setPrototypeOf`.
487 * Returns the provided value converted to an object.
489 * Existing objects are returned with no modification.
491 * ☡ This function throws if its argument is null or undefined.
495 const createObject
= Object
;
505 next
: generatorIteratorNext
,
506 } = getPrototypeOf(function* () {}.prototype);
507 const propertyDescriptorEntryIterablePrototype
= {
510 next
: bind(generatorIteratorNext
, this.generator(), []),
514 const propertyDescriptorEntryIterable
= ($) =>
515 create(propertyDescriptorEntryIterablePrototype
, {
516 generator
: { value
: $ },
520 defineOwnProperties
: (O
, ...sources
) => {
521 const { length
} = sources
;
522 for (let index
= 0; index
< length
; ++index
) {
523 defineProperties(O
, sources
[index
]);
527 frozenCopy
: (O
, constructor = O
?.constructor) => {
529 // O is null or undefined.
531 "Piscēs: Cannot copy properties of null or undefined.",
534 // O is not null or undefined.
536 // (If not provided, the constructor will be the value of
537 // getting the `.constructor` property of O.)
538 const species
= constructor?.[SPECIES
] ?? constructor;
539 return preventExtensions(
541 species
== null || !("prototype" in species
)
545 propertyDescriptorEntryIterable(function* () {
546 const ownPropertyKeys
= getOwnPropertyKeys(O
);
549 i
< ownPropertyKeys
.length
;
552 const P
= ownPropertyKeys
[i
];
553 const Desc
= getOwnPropertyDescriptor(O
, P
);
554 if (Desc
.enumerable
) {
555 // P is an enumerable property.
558 "get" in Desc
|| "set" in Desc
573 // P is not an enumerable property.
583 isUnfrozenObject
: (O
) => !isFrozen(O
),
584 isUnsealedObject
: (O
) => !isSealed(O
),
585 setPrototype
: (O
, proto
) => {
586 const obj
= toObject(O
);
588 // The provided value is an object; set its prototype normally.
589 return setPrototypeOf(O
, proto
);
591 // The provided value is not an object; attempt to set the
592 // prototype on a coerced version with extensions prevented,
593 // then return the provided value.
595 // This will throw if the given prototype does not match the
596 // existing one on the coerced object.
597 setPrototypeOf(preventExtensions(obj
), proto
);
603 // The provided value is nullish; this is an error.
605 `Piscēs: Cannot convert ${$} into an object.`,
608 // The provided value is not nullish; coerce it to an object.
609 return createObject($);
617 * Removes the provided property key from the provided object and
618 * returns the object.
620 * ※ This function differs from `Reflect.deleteProperty` and the
621 * `delete` operator in that it throws if the deletion is
624 * ☡ This function throws if the first argument is not an object.
629 * Returns an array of property keys on the provided object.
631 * ※ This is effectively an alias for `Reflect.ownKeys`, except that
632 * it does not require that the argument be an object.
637 * Returns the value of the provided property key on the provided
640 * ※ This is effectively an alias for `Reflect.get`, except that it
641 * does not require that the argument be an object.
646 * Returns whether the provided property key exists on the provided
649 * ※ This is effectively an alias for `Reflect.has`, except that it
650 * does not require that the argument be an object.
652 * ※ This includes properties present on the prototype chain.
657 * Sets the provided property key to the provided value on the
658 * provided object and returns the object.
660 * ※ This function differs from `Reflect.set` in that it throws if
661 * the setting is unsuccessful.
663 * ☡ This function throws if the first argument is not an object.
667 const { deleteProperty
, get, has
, ownKeys
, set } = Reflect
;
670 deleteOwnProperty
: (O
, P
) => {
671 if (type(O
) !== "object") {
673 `Piscēs: Tried to set property but provided value was not an object: ${V}`,
675 } else if (!deleteProperty(O
, P
)) {
677 `Piscēs: Tried to delete property from object but [[Delete]] returned false: ${P}`,
683 getOwnPropertyKeys
: (O
) => ownKeys(toObject(O
)),
684 getPropertyValue
: (O
, P
, Receiver
= O
) =>
685 get(toObject(O
), P
, Receiver
),
686 hasProperty
: (O
, P
) => has(toObject(O
), P
),
687 setPropertyValue
: (O
, P
, V
, Receiver
= O
) => {
688 if (type(O
) !== "object") {
690 `Piscēs: Tried to set property but provided value was not an object: ${V}`,
692 } else if (!set(O
, P
, V
, Receiver
)) {
694 `Piscēs: Tried to set property on object but [[Set]] returned false: ${P}`,
704 * Marks the provided object as non·extensible and marks all its
705 * properties as nonconfigurable and (if data properties) nonwritable,
706 * and returns the object.
708 * ※ This is effectively an alias for `Object.freeze`.
710 export const freeze
= createArrowFunction(Object
.freeze
);
713 * Returns the function on the provided value at the provided property
716 * ☡ This function throws if the provided property key does not have an
717 * associated value which is callable.
719 export const getMethod
= (V
, P
) => {
720 const func
= getPropertyValue(V
, P
);
723 } else if (typeof func
!== "function") {
724 throw new TypeError(`Piscēs: Method not callable: ${P}`);
731 * Returns the property descriptor for the own property with the
732 * provided property key on the provided object, or null if none
735 * ※ This is effectively an alias for
736 * `Object.getOwnPropertyDescriptor`.
738 export const getOwnPropertyDescriptor
= createArrowFunction(
739 Object
.getOwnPropertyDescriptor
,
743 * Returns the property descriptors for the own properties on the
746 * ※ This is effectively an alias for
747 * `Object.getOwnPropertyDescriptors`.
749 export const getOwnPropertyDescriptors
= createArrowFunction(
750 Object
.getOwnPropertyDescriptors
,
754 * Returns an array of string‐valued own property keys on the
757 * ☡ This includes both enumerable and non·enumerable properties.
759 * ※ This is effectively an alias for `Object.getOwnPropertyNames`.
761 export const getOwnPropertyStrings
= createArrowFunction(
762 Object
.getOwnPropertyNames
,
763 { name
: "getOwnPropertyStrings" },
767 * Returns an array of symbol‐valued own property keys on the
770 * ☡ This includes both enumerable and non·enumerable properties.
772 * ※ This is effectively an alias for
773 * `Object.getOwnPropertySymbols`.
775 export const getOwnPropertySymbols
= createArrowFunction(
776 Object
.getOwnPropertySymbols
,
780 * Returns the prototype of the provided object.
782 * ※ This is effectively an alias for `Object.getPrototypeOf`.
784 export const getPrototype
= createArrowFunction(
785 Object
.getPrototypeOf
,
786 { name
: "getPrototype" },
790 * Returns whether the provided object has an own property with the
791 * provided property key.
793 * ※ This is effectively an alias for `Object.hasOwn`.
795 export const hasOwnProperty
= createArrowFunction(Object
.hasOwn
, {
796 name
: "hasOwnProperty",
799 /** Returns whether the provided value is an arraylike object. */
800 export const isArraylikeObject
= ($) => {
801 if (type($) !== "object") {
805 lengthOfArraylike($); // throws if not arraylike
814 * Returns whether the provided object is extensible.
816 * ※ This function returns false for nonobjects.
818 * ※ This is effectively an alias for `Object.isExtensible`.
820 export const isExtensibleObject
= createArrowFunction(
822 { name
: "isExtensibleObject" },
826 * Returns the length of the provided arraylike value.
828 * This can produce larger lengths than can actually be stored in
829 * arrays, because no such restrictions exist on arraylike methods.
831 * ☡ This function throws if the provided value is not arraylike.
833 export const lengthOfArraylike
= ({ length
}) => toLength(length
);
836 * Returns an array of key~value pairs for the enumerable,
837 * string‐valued property keys on the provided object.
839 * ※ This is effectively an alias for `Object.entries`.
841 export const namedEntries
= createArrowFunction(Object
.entries
, {
842 name
: "namedEntries",
846 * Returns an array of the enumerable, string‐valued property keys on
847 * the provided object.
849 * ※ This is effectively an alias for `Object.keys`.
851 export const namedKeys
= createArrowFunction(Object
.keys
, {
856 * Returns an array of property values for the enumerable,
857 * string‐valued property keys on the provided object.
859 * ※ This is effectively an alias for `Object.values`.
861 export const namedValues
= createArrowFunction(Object
.values
, {
866 * Returns a new object with the provided prototype and property
869 * ※ This is effectively an alias for `Object.create`.
871 export const objectCreate
= createArrowFunction(Object
.create
, {
872 name
: "objectCreate",
876 * Returns a new object with the provided property keys and values.
878 * ※ This is effectively an alias for `Object.fromEntries`.
880 export const objectFromEntries
= createArrowFunction(
882 { name
: "objectFromEntries" },
886 * Marks the provided object as non·extensible, and returns the
889 * ※ This is effectively an alias for `Object.preventExtensions`.
891 export const preventExtensions
= createArrowFunction(
892 Object
.preventExtensions
,
896 * Marks the provided object as non·extensible and marks all its
897 * properties as nonconfigurable, and returns the object.
899 * ※ This is effectively an alias for `Object.seal`.
901 export const seal
= createArrowFunction(Object
.seal
);
904 * Sets the values of the enumerable own properties of the provided
905 * additional objects on the provided object.
907 * ※ This is effectively an alias for `Object.assign`.
909 export const setPropertyValues
= createArrowFunction(Object
.assign
, {
910 name
: "setPropertyValues",
914 * Returns the property key (symbol or string) corresponding to the
917 export const toPropertyKey
= ($) => {
918 const key
= toPrimitive($, "string");
919 return typeof key
=== "symbol" ? key
: `${key}`;