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/>.
10 import { bind
, call
, toFunctionName
} from "./function.js";
11 import { ITERATOR
, SPECIES
, toPrimitive
, type
} from "./value.js";
14 * An object whose properties are lazy‐loaded from the methods on the
15 * own properties of the provided object.
17 * This is useful when you are looking to reference properties on
18 * objects which, due to module dependency graphs, cannot be guaranteed
19 * to have been initialized yet.
21 * The resulting properties will have the same attributes (regarding
22 * configurability, enumerability, and writability) as the
23 * corresponding properties on the methods object. If a property is
24 * marked as writable, the method will never be called if it is set
25 * before it is gotten. By necessity, the resulting properties are all
26 * configurable before they are accessed for the first time.
28 * Methods will be called with the resulting object as their this
31 * `LazyLoader` objects have the same prototype as the passed methods
34 export class LazyLoader
extends null {
36 * Constructs a new `LazyLoader` object.
38 * ☡ This function throws if the provided value is not an object.
40 constructor(loadMethods
) {
41 if (type(loadMethods
) !== "object") {
42 // The provided value is not an object; throw an error.
44 `Piscēs: Cannot construct LazyLoader: Provided argument is not an object: ${loadMethods}.`,
47 // The provided value is an object; process it and build the
49 const result
= objectCreate(getPrototype(loadMethods
));
50 const methodKeys
= getOwnPropertyKeys(loadMethods
);
51 for (let index
= 0; index
< methodKeys
.length
; ++index
) {
52 // Iterate over the property keys of the provided object and
53 // define getters and setters appropriately on the result.
54 const methodKey
= methodKeys
[index
];
55 const { configurable
, enumerable
, writable
} =
56 getOwnPropertyDescriptor(loadMethods
, methodKey
);
57 defineOwnProperty(result
, methodKey
, {
60 get: defineOwnProperty(
62 const value
= call(loadMethods
[methodKey
], result
, []);
63 defineOwnProperty(result
, methodKey
, {
72 { value
: toFunctionName(methodKey
, "get") },
77 defineOwnProperty(result
, methodKey
, {
84 { value
: toFunctionName(methodKey
, "set") },
95 * A property descriptor object.
97 * Actually constructing a property descriptor object using this class
98 * is only necessary if you need strict guarantees about the types of
99 * its properties; the resulting object is proxied to ensure the types
100 * match what one would expect from composing FromPropertyDescriptor
101 * and ToPropertyDescriptor in the Ecmascript specification.
103 * Otherwise, the instance properties and methods are generic.
105 export const { PropertyDescriptor
} = (() => {
106 class PropertyDescriptor
extends null {
108 * Constructs a new property descriptor object from the provided
111 * The resulting object is proxied to enforce types (for example,
112 * its `.enumerable` property, if defined, will always be a
116 if (type(O
) !== "object") {
117 // The provided value is not an object.
119 `Piscēs: Cannot convert primitive to property descriptor: ${O}.`,
122 // The provided value is an object.
123 const desc
= objectCreate(propertyDescriptorPrototype
);
124 if ("enumerable" in O
) {
125 // An enumerable property is specified.
126 desc
.enumerable
= !!O
.enumerable
;
128 // An enumerable property is not specified.
131 if ("configurable" in O
) {
132 // A configurable property is specified.
133 desc
.configurable
= !!O
.configurable
;
135 // A configurable property is not specified.
139 // A value property is specified.
140 desc
.value
= O
.value
;
142 // A value property is not specified.
145 if ("writable" in O
) {
146 // A writable property is specified.
147 desc
.writable
= !!O
.writable
;
149 // A writable property is not specified.
153 // A get property is specified.
154 const getter
= O
.get;
155 if (getter
!== undefined && typeof getter
!== "function") {
156 // The getter is not callable.
157 throw new TypeError("Piscēs: Getters must be callable.");
159 // The getter is callable.
163 // A get property is not specified.
167 // A set property is specified.
168 const setter
= O
.set;
169 if (setter
!== undefined && typeof setter
!== "function") {
170 // The setter is not callable.
171 throw new TypeError("Piscēs: Setters must be callable.");
173 // The setter is callable.
177 // A set property is not specified.
181 ("get" in desc
|| "set" in desc
) &&
182 ("value" in desc
|| "writable" in desc
)
184 // Both accessor and data attributes have been defined.
186 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
189 // The property descriptor is valid.
190 return new Proxy(desc
, propertyDescriptorProxyHandler
);
196 * Completes this property descriptor by setting missing values to
199 * This method modifies this object and returns undefined.
202 if (this !== undefined && !("get" in this || "set" in this)) {
203 // This is a generic or data descriptor.
204 if (!("value" in this)) {
205 // `value` is not defined on this.
206 this.value
= undefined;
208 // `value` is already defined on this.
211 if (!("writable" in this)) {
212 // `writable` is not defined on this.
213 this.writable
= false;
215 // `writable` is already defined on this.
219 // This is not a generic or data descriptor.
220 if (!("get" in this)) {
221 // `get` is not defined on this.
222 this.get = undefined;
224 // `get` is already defined on this.
227 if (!("set" in this)) {
228 // `set` is not defined on this.
229 this.set = undefined;
231 // `set` is already defined on this.
235 if (!("enumerable" in this)) {
236 // `enumerable` is not defined on this.
237 this.enumerable
= false;
239 // `enumerable` is already defined on this.
242 if (!("configurable" in this)) {
243 // `configurable` is not defined on this.
244 this.configurable
= false;
246 // `configurable` is already defined on this.
251 /** Gets whether this is an accessor descrtiptor. */
252 get isAccessorDescriptor() {
253 return this !== undefined && ("get" in this || "set" in this);
256 /** Gets whether this is a data descrtiptor. */
257 get isDataDescriptor() {
258 return this !== undefined &&
259 ("value" in this || "writable" in this);
262 /** Gets whether this is a fully‐populated property descriptor. */
263 get isFullyPopulated() {
264 return this !== undefined &&
265 ("value" in this && "writable" in this ||
266 "get" in this && "set" in this) &&
267 "enumerable" in this && "configurable" in this;
271 * Gets whether this is a generic (not accessor or data)
274 get isGenericDescriptor() {
275 return this !== undefined &&
276 !("get" in this || "set" in this || "value" in this ||
281 const coercePropertyDescriptorValue
= (P
, V
) => {
290 if (V
!== undefined && typeof V
!== "function") {
292 "Piscēs: Getters must be callable.",
298 if (V
!== undefined && typeof V
!== "function") {
300 "Piscēs: Setters must be callable.",
311 prototype: propertyDescriptorPrototype
,
312 } = PropertyDescriptor
;
314 const propertyDescriptorProxyHandler
= Object
.assign(
317 defineProperty(O
, P
, Desc
) {
319 P
=== "configurable" || P
=== "enumerable" ||
320 P
=== "writable" || P
=== "value" ||
321 P
=== "get" || P
=== "set"
323 // P is a property descriptor attribute.
324 const desc
= new PropertyDescriptor(Desc
);
325 if ("get" in desc
|| "set" in desc
) {
326 // Desc is an accessor property descriptor.
328 "Piscēs: Property descriptor attributes must be data properties.",
330 } else if ("value" in desc
|| !(P
in O
)) {
331 // Desc has a value or P does not already exist on O.
332 desc
.value
= coercePropertyDescriptorValue(P
, desc
.value
);
334 // Desc is not an accessor property descriptor and has no
338 const isAccessorDescriptor
= "get" === P
|| "set" === P
||
339 "get" in O
|| "set" in O
;
340 const isDataDescriptor
= "value" === P
|| "writable" === P
||
341 "value" in O
|| "writable" in O
;
342 if (isAccessorDescriptor
&& isDataDescriptor
) {
343 // Both accessor and data attributes will be present on O
346 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
349 // P can be safely defined on O.
350 return defineOwnProperty(O
, P
, desc
);
353 // P is not a property descriptor attribute.
354 return defineOwnProperty(O
, P
, Desc
);
357 set(O
, P
, V
, Receiver
) {
359 P
=== "configurable" || P
=== "enumerable" ||
360 P
=== "writable" || P
=== "value" ||
361 P
=== "get" || P
=== "set"
363 // P is a property descriptor attribute.
364 const newValue
= coercePropertyDescriptorValue(P
, V
);
365 const isAccessorDescriptor
= "get" === P
|| "set" === P
||
366 "get" in O
|| "set" in O
;
367 const isDataDescriptor
= "value" === P
|| "writable" === P
||
368 "value" in O
|| "writable" in O
;
369 if (isAccessorDescriptor
&& isDataDescriptor
) {
370 // Both accessor and data attributes will be present on O
373 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
376 // P can be safely defined on O.
378 // ☡ Receiver will be the *proxied* object, so passing it
379 // through to setPropertyValue here would produce an
382 // ☡ This has implications on objects with a proxied
383 // PropertyDescriptor in their prototype.
384 return setPropertyValue(O
, P
, newValue
, O
);
387 return setPropertyValue(O
, P
, V
, Receiver
);
390 setPrototypeOf(O
, V
) {
391 if (V
!== propertyDescriptorPrototype
) {
392 // V is not the property descriptor prototype.
395 // V is the property descriptor prototype.
396 return setPrototype(O
, V
);
402 return { PropertyDescriptor
};
407 * Defines an own property on the provided object on the provided
408 * property key using the provided property descriptor.
410 * ※ This is effectively an alias for `Object.defineProperty`.
415 * Defines own properties on the provided object using the
416 * descriptors on the enumerable own properties of the provided
417 * additional objects.
419 * ※ This differs from `Object.defineProperties` in that it can take
420 * multiple source objects.
425 * Marks the provided object as non·extensible and marks all its
426 * properties as nonconfigurable and (if data properties)
427 * nonwritable, and returns the object.
429 * ※ This is effectively an alias for `Object.freeze`.
434 * Returns the property descriptor for the own property with the
435 * provided property key on the provided object, or null if none
438 * ※ This is effectively an alias for
439 * `Object.getOwnPropertyDescriptor`.
441 getOwnPropertyDescriptor
,
444 * Returns the property descriptors for the own properties on the
447 * ※ This is effectively an alias for
448 * `Object.getOwnPropertyDescriptors`.
450 getOwnPropertyDescriptors
,
453 * Returns an array of string‐valued own property keys on the
456 * ☡ This includes both enumerable and non·enumerable properties.
458 * ※ This is effectively an alias for `Object.getOwnPropertyNames`.
460 getOwnPropertyStrings
,
463 * Returns an array of symbol‐valued own property keys on the
466 * ☡ This includes both enumerable and non·enumerable properties.
468 * ※ This is effectively an alias for
469 * `Object.getOwnPropertySymbols`.
471 getOwnPropertySymbols
,
474 * Returns the prototype of the provided object.
476 * ※ This is effectively an alias for `Object.getPrototypeOf`.
481 * Returns whether the provided object has an own property with the
482 * provided property key.
484 * ※ This is effectively an alias for `Object.hasOwn`.
489 * Returns whether the provided object is extensible.
491 * ※ This function returns false for nonobjects.
493 * ※ This is effectively an alias for `Object.isExtensible`.
498 * Returns whether the provided object is frozen.
500 * ※ This function returns false for nonobjects.
502 * ※ This is effectively an alias for `!Object.isFrozen`.
507 * Returns whether the provided object is sealed.
509 * ※ This function returns false for nonobjects.
511 * ※ This is effectively an alias for `!Object.isSealed`.
516 * Returns an array of key~value pairs for the enumerable,
517 * string‐valued property keys on the provided object.
519 * ※ This is effectively an alias for `Object.entries`.
524 * Returns an array of the enumerable, string‐valued property keys on
525 * the provided object.
527 * ※ This is effectively an alias for `Object.keys`.
532 * Returns an array of property values for the enumerable,
533 * string‐valued property keys on the provided object.
535 * ※ This is effectively an alias for `Object.values`.
540 * Returns a new object with the provided prototype and property
543 * ※ This is effectively an alias for `Object.create`.
548 * Returns a new object with the provided property keys and values.
550 * ※ This is effectively an alias for `Object.fromEntries`.
555 * Marks the provided object as non·extensible, and returns the
558 * ※ This is effectively an alias for `Object.preventExtensions`.
563 * Marks the provided object as non·extensible and marks all its
564 * properties as nonconfigurable, and returns the object.
566 * ※ This is effectively an alias for `Object.seal`.
571 * Sets the values of the enumerable own properties of the provided
572 * additional objects on the provided object.
574 * ※ This is effectively an alias for `Object.assign`.
579 * Sets the prototype of the provided object to the provided value
580 * and returns the object.
582 * ※ This is effectively an alias for `Object.setPrototypeOf`.
587 * Returns the provided value converted to an object.
589 * Existing objects are returned with no modification.
591 * ☡ This function throws if its argument is null or undefined.
595 const createObject
= Object
;
604 getOwnPropertyDescriptor
,
605 getOwnPropertyDescriptors
,
607 getOwnPropertySymbols
,
619 const { [ITERATOR
]: arrayIterator
} = Array
.prototype;
620 const arrayIteratorNext
= getPrototypeOf([][ITERATOR
]()).next
;
621 const splatIterablePrototype
= {
626 call(arrayIterator
, this.args
, []),
632 const splatIterable
= ($) =>
633 create(splatIterablePrototype
, { args
: { value
: $ } });
636 defineOwnProperty
: (O
, P
, Attributes
) =>
637 defineProperty(O
, P
, Attributes
),
638 defineOwnProperties
: (O
, ...sources
) => {
639 for (const source
of splatIterable(sources
)) {
640 defineProperties(O
, source
);
644 freeze
: (O
) => freeze(O
),
645 getOwnPropertyDescriptor
: (O
, P
) => getOwnPropertyDescriptor(O
, P
),
646 getOwnPropertyDescriptors
: (O
) => getOwnPropertyDescriptors(O
),
647 getOwnPropertyStrings
: (O
) => getOwnPropertyNames(O
),
648 getOwnPropertySymbols
: (O
) => getOwnPropertySymbols(O
),
649 getPrototype
: (O
) => getPrototypeOf(O
),
650 hasOwnProperty
: (O
, P
) => hasOwn(O
, P
),
651 isExtensibleObject
: (O
) => isExtensible(O
),
652 isUnfrozenObject
: (O
) => !isFrozen(O
),
653 isUnsealedObject
: (O
) => !isSealed(O
),
654 namedEntries
: (O
) => entries(O
),
655 namedKeys
: (O
) => keys(O
),
656 namedValues
: (O
) => values(O
),
657 objectCreate
: (O
, Properties
) => create(O
, Properties
),
658 objectFromEntries
: (iterable
) => fromEntries(iterable
),
659 preventExtensions
: (O
) => preventExtensions(O
),
660 seal
: (O
) => seal(O
),
661 setPropertyValues
: (target
, source
, ...sources
) =>
662 assign(target
, source
, ...splatIterable(sources
)),
663 setPrototype
: (O
, proto
) => {
664 const obj
= toObject(O
);
666 // The provided value is an object; set its prototype normally.
667 return setPrototypeOf(O
, proto
);
669 // The provided value is not an object; attempt to set the
670 // prototype on a coerced version with extensions prevented,
671 // then return the provided value.
673 // This will throw if the given prototype does not match the
674 // existing one on the coerced object.
675 setPrototypeOf(preventExtensions(obj
), proto
);
681 // The provided value is nullish; this is an error.
683 `Piscēs: Cannot convert ${$} into an object.`,
686 // The provided value is not nullish; coerce it to an object.
687 return createObject($);
695 * Removes the provided property key from the provided object and
696 * returns the object.
698 * ※ This function differs from `Reflect.deleteProperty` and the
699 * `delete` operator in that it throws if the deletion is
702 * ☡ This function throws if the first argument is not an object.
707 * Returns an array of property keys on the provided object.
709 * ※ This is effectively an alias for `Reflect.ownKeys`, except that
710 * it does not require that the argument be an object.
715 * Returns the value of the provided property key on the provided
718 * ※ This is effectively an alias for `Reflect.get`, except that it
719 * does not require that the argument be an object.
724 * Returns whether the provided property key exists on the provided
727 * ※ This is effectively an alias for `Reflect.has`, except that it
728 * does not require that the argument be an object.
730 * ※ This includes properties present on the prototype chain.
735 * Sets the provided property key to the provided value on the
736 * provided object and returns the object.
738 * ※ This function differs from `Reflect.set` in that it throws if
739 * the setting is unsuccessful.
741 * ☡ This function throws if the first argument is not an object.
745 const { deleteProperty
, get, has
, ownKeys
, set } = Reflect
;
748 deleteOwnProperty
: (O
, P
) => {
749 if (type(O
) !== "object") {
751 `Piscēs: Tried to set property but provided value was not an object: ${V}`,
753 } else if (!deleteProperty(O
, P
)) {
755 `Piscēs: Tried to delete property from object but [[Delete]] returned false: ${P}`,
761 getOwnPropertyKeys
: (O
) => ownKeys(toObject(O
)),
762 getPropertyValue
: (O
, P
, Receiver
= O
) =>
763 get(toObject(O
), P
, Receiver
),
764 hasProperty
: (O
, P
) => has(toObject(O
), P
),
765 setPropertyValue
: (O
, P
, V
, Receiver
= O
) => {
766 if (type(O
) !== "object") {
768 `Piscēs: Tried to set property but provided value was not an object: ${V}`,
770 } else if (!set(O
, P
, V
, Receiver
)) {
772 `Piscēs: Tried to set property on object but [[Set]] returned false: ${P}`,
783 * Returns a new frozen shallow copy of the enumerable own properties
784 * of the provided object, according to the following rules :—
786 * - For data properties, create a nonconfigurable, nonwritable
787 * property with the same value.
789 * - For accessor properties, create a nonconfigurable accessor
790 * property with the same getter *and* setter.
792 * The prototype for the resulting object will be taken from the
793 * `.prototype` property of the provided constructor, or the
794 * `.prototype` of the `.constructor` of the provided object if the
795 * provided constructor is undefined. If the used constructor has a
796 * nonnullish `.[Symbol.species]`, that will be used instead. If the
797 * used constructor or species is nullish or does not have a
798 * `.prototype` property, the prototype is set to null.
800 * ※ The prototype of the provided object itself is ignored.
805 next
: generatorIteratorNext
,
806 } = getPrototype(function* () {}.prototype);
807 const propertyDescriptorEntryIterablePrototype
= {
810 next
: bind(generatorIteratorNext
, this.generator(), []),
815 frozenCopy
: (O
, constructor = O
?.constructor) => {
817 // O is null or undefined.
819 "Piscēs: Cannot copy properties of null or undefined.",
822 // O is not null or undefined.
824 // (If not provided, the constructor will be the value of
825 // getting the `.constructor` property of O.)
826 const species
= constructor?.[SPECIES
] ?? constructor;
827 return preventExtensions(
829 species
== null || !("prototype" in species
)
834 propertyDescriptorEntryIterablePrototype
,
837 value
: function* () {
838 const ownPropertyKeys
= getOwnPropertyKeys(O
);
841 i
< ownPropertyKeys
.length
;
844 const P
= ownPropertyKeys
[i
];
845 const Desc
= getOwnPropertyDescriptor(O
, P
);
846 if (Desc
.enumerable
) {
847 // P is an enumerable property.
850 "get" in Desc
|| "set" in Desc
865 // P is not an enumerable property.
882 * Returns the function on the provided value at the provided property
885 * ☡ This function throws if the provided property key does not have an
886 * associated value which is callable.
888 export const getMethod
= (V
, P
) => {
889 const func
= getPropertyValue(V
, P
);
892 } else if (typeof func
!== "function") {
893 throw new TypeError(`Piscēs: Method not callable: ${P}`);
900 * Returns the property key (symbol or string) corresponding to the
903 export const toPropertyKey
= ($) => {
904 const key
= toPrimitive($, "string");
905 return typeof key
=== "symbol" ? key
: `${key}`;