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 own properties on the provided object using the
408 * descriptors on the enumerable own properties of the provided
409 * additional objects.
411 * ※ This differs from `Object.defineProperties` in that it can take
412 * multiple source objects.
416 const { defineProperties
} = Object
;
417 const { forEach
: arrayForEach
} = Array
.prototype;
419 defineOwnProperties
: (O
, ...sources
) => {
423 [(source
) => defineProperties(O
, source
)],
432 * Defines an own property on the provided object on the provided
433 * property key using the provided property descriptor.
435 * ※ This is an alias for `Object.defineProperty`.
437 defineProperty
: defineOwnProperty
,
440 * Marks the provided object as non·extensible and marks all its
441 * properties as nonconfigurable and (if data properties)
442 * nonwritable, and returns the object.
444 * ※ This is an alias for `Object.freeze`.
449 * Returns the property descriptor for the own property with the
450 * provided property key on the provided object, or null if none
453 * ※ This is an alias for `Object.getOwnPropertyDescriptor`.
455 getOwnPropertyDescriptor
,
458 * Returns the property descriptors for the own properties on the
461 * ※ This is an alias for `Object.getOwnPropertyDescriptors`.
463 getOwnPropertyDescriptors
,
466 * Returns an array of string‐valued own property keys on the
469 * ☡ This includes both enumerable and non·enumerable properties.
471 * ※ This is an alias for `Object.getOwnPropertyNames`.
473 getOwnPropertyNames
: getOwnPropertyStrings
,
476 * Returns an array of symbol‐valued own property keys on the
479 * ☡ This includes both enumerable and non·enumerable properties.
481 * ※ This is an alias for `Object.getOwnPropertySymbols`.
483 getOwnPropertySymbols
,
486 * Returns the prototype of the provided object.
488 * ※ This is an alias for `Object.getPrototypeOf`.
490 getPrototypeOf
: getPrototype
,
493 * Returns whether the provided object has an own property with the
494 * provided property key.
496 * ※ This is an alias for `Object.hasOwn`.
498 hasOwn
: hasOwnProperty
,
501 * Returns whether the provided object is extensible.
503 * ※ This is an alias for `Object.isExtensible`.
505 isExtensible
: isExtensibleObject
,
508 * Returns whether the provided object is frozen.
510 * ※ This is an alias for `Object.isFrozen`.
512 isFrozen
: isFrozenObject
,
515 * Returns whether the provided object is sealed.
517 * ※ This is an alias for `Object.isSealed`.
519 isSealed
: isSealedObject
,
522 * Returns an array of key~value pairs for the enumerable,
523 * string‐valued property keys on the provided object.
525 * ※ This is an alias for `Object.entries`.
527 entries
: namedEntries
,
530 * Returns an array of the enumerable, string‐valued property keys on
531 * the provided object.
533 * ※ This is an alias for `Object.keys`.
538 * Returns an array of property values for the enumerable,
539 * string‐valued property keys on the provided object.
541 * ※ This is an alias for `Object.values`.
546 * Returns a new object with the provided prototype and property
549 * ※ This is an alias for `Object.create`.
551 create
: objectCreate
,
554 * Returns a new object with the provided property keys and values.
556 * ※ This is an alias for `Object.fromEntries`.
558 fromEntries
: objectFromEntries
,
561 * Marks the provided object as non·extensible, and returns the
564 * ※ This is an alias for `Object.preventExtensions`.
569 * Marks the provided object as non·extensible and marks all its
570 * properties as nonconfigurable, and returns the object.
572 * ※ This is an alias for `Object.seal`.
577 * Sets the values of the enumerable own properties of the provided
578 * additional objects on the provided object.
580 * ※ This is an alias for `Object.assign`.
582 assign
: setPropertyValues
,
585 * Sets the prototype of the provided object to the provided value
586 * and returns the object.
588 * ※ This is an alias for `Object.setPrototypeOf`.
590 setPrototypeOf
: setPrototype
,
595 * Removes the provided property key from the provided object and
596 * returns the object.
598 * ※ This function differs from `Reflect.deleteProperty` and the
599 * `delete` operator in that it throws if the deletion is
602 * ☡ This function throws if the first argument is not an object.
607 * Returns an array of property keys on the provided object.
609 * ※ This is effectively an alias for `Reflect.ownKeys`, except that
610 * it does not require that the argument be an object.
615 * Returns the value of the provided property key on the provided
618 * ※ This is effectively an alias for `Reflect.get`, except that it
619 * does not require that the argument be an object.
624 * Returns whether the provided property key exists on the provided
627 * ※ This is effectively an alias for `Reflect.has`, except that it
628 * does not require that the argument be an object.
630 * ※ This includes properties present on the prototype chain.
635 * Sets the provided property key to the provided value on the
636 * provided object and returns the object.
638 * ※ This function differs from `Reflect.set` in that it throws if
639 * the setting is unsuccessful.
641 * ☡ This function throws if the first argument is not an object.
645 const { deleteProperty
, get, has
, ownKeys
, set } = Reflect
;
648 deleteOwnProperty
: (O
, P
) => {
649 if (type(O
) !== "object") {
651 `Piscēs: Tried to set property but provided value was not an object: ${V}`,
653 } else if (!deleteProperty(O
, P
)) {
655 `Piscēs: Tried to delete property from object but [[Delete]] returned false: ${P}`,
661 getOwnPropertyKeys
: (O
) => ownKeys(toObject(O
)),
662 getPropertyValue
: (O
, P
, Receiver
= O
) =>
663 get(toObject(O
), P
, Receiver
),
664 hasProperty
: (O
, P
) => has(toObject(O
), P
),
665 setPropertyValue
: (O
, P
, V
, Receiver
= O
) => {
666 if (type(O
) !== "object") {
668 `Piscēs: Tried to set property but provided value was not an object: ${V}`,
670 } else if (!set(O
, P
, V
, Receiver
)) {
672 `Piscēs: Tried to set property on object but [[Set]] returned false: ${P}`,
683 * Returns a new frozen shallow copy of the enumerable own properties
684 * of the provided object, according to the following rules :—
686 * - For data properties, create a nonconfigurable, nonwritable
687 * property with the same value.
689 * - For accessor properties, create a nonconfigurable accessor
690 * property with the same getter *and* setter.
692 * The prototype for the resulting object will be taken from the
693 * `.prototype` property of the provided constructor, or the
694 * `.prototype` of the `.constructor` of the provided object if the
695 * provided constructor is undefined. If the used constructor has a
696 * nonnullish `.[Symbol.species]`, that will be used instead. If the
697 * used constructor or species is nullish or does not have a
698 * `.prototype` property, the prototype is set to null.
700 * ※ The prototype of the provided object itself is ignored.
705 next
: generatorIteratorNext
,
706 } = getPrototype(function* () {}.prototype);
707 const propertyDescriptorEntryIterablePrototype
= {
710 next
: bind(generatorIteratorNext
, this.generator(), []),
715 frozenCopy
: (O
, constructor = O
?.constructor) => {
717 // O is null or undefined.
719 "Piscēs: Cannot copy properties of null or undefined.",
722 // O is not null or undefined.
724 // (If not provided, the constructor will be the value of
725 // getting the `.constructor` property of O.)
726 const species
= constructor?.[SPECIES
] ?? constructor;
727 return preventExtensions(
729 species
== null || !("prototype" in species
)
734 propertyDescriptorEntryIterablePrototype
,
737 value
: function* () {
738 const ownPropertyKeys
= getOwnPropertyKeys(O
);
741 i
< ownPropertyKeys
.length
;
744 const P
= ownPropertyKeys
[i
];
745 const Desc
= getOwnPropertyDescriptor(O
, P
);
746 if (Desc
.enumerable
) {
747 // P is an enumerable property.
750 "get" in Desc
|| "set" in Desc
765 // P is not an enumerable property.
782 * Returns the function on the provided value at the provided property
785 * ☡ This function throws if the provided property key does not have an
786 * associated value which is callable.
788 export const getMethod
= (V
, P
) => {
789 const func
= getPropertyValue(V
, P
);
792 } else if (typeof func
!== "function") {
793 throw new TypeError(`Piscēs: Method not callable: ${P}`);
800 * Returns the provided value converted to an object.
802 * Existing objects are returned with no modification.
804 * ☡ This function throws if its argument is null or undefined.
806 export const { toObject
} = (() => {
807 const makeObject
= Object
;
812 `Piscēs: Cannot convert ${$} into an object.`,
815 return makeObject($);
822 * Returns the property key (symbol or string) corresponding to the
825 export const toPropertyKey
= ($) => {
826 const key
= toPrimitive($, "string");
827 return typeof key
== "symbol" ? key
: `${key}`;