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
} 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") {
43 `Piscēs: Cannot construct LazyLoader: Provided argument is not an object: ${loadMethods}.`,
46 const result
= objectCreate(getPrototype(loadMethods
));
47 const methodKeys
= getOwnPropertyKeys(loadMethods
);
48 for (let index
= 0; index
< methodKeys
.length
; ++index
) {
49 const methodKey
= methodKeys
[index
];
50 const { configurable
, enumerable
, writable
} =
51 getOwnPropertyDescriptor(loadMethods
, methodKey
);
52 defineOwnProperty(result
, methodKey
, {
56 const value
= call(loadMethods
[methodKey
], result
, []);
57 defineOwnProperty(result
, methodKey
, {
67 defineOwnProperty(result
, methodKey
, {
82 * A property descriptor object.
84 * Actually constructing a property descriptor object using this class
85 * is only necessary if you need strict guarantees about the types of
86 * its properties; the resulting object is proxied to ensure the types
87 * match what one would expect from composing FromPropertyDescriptor
88 * and ToPropertyDescriptor in the Ecmascript specification.
90 * Otherwise, the instance properties and methods are generic.
92 export const { PropertyDescriptor
} = (() => {
93 class PropertyDescriptor
extends null {
95 * Constructs a new property descriptor object from the provided
98 * The resulting object is proxied to enforce types (for example,
99 * its `enumerable` property, if defined, will always be a
103 if (type(O
) !== "object") {
104 // The provided value is not an object.
106 `Piscēs: Cannot convert primitive to property descriptor: ${O}.`,
109 // The provided value is an object.
110 const desc
= objectCreate(propertyDescriptorPrototype
);
111 if ("enumerable" in O
) {
112 // An enumerable property is specified.
113 desc
.enumerable
= !!O
.enumerable
;
115 // An enumerable property is not specified.
118 if ("configurable" in O
) {
119 // A configurable property is specified.
120 desc
.configurable
= !!O
.configurable
;
122 // A configurable property is not specified.
126 // A value property is specified.
127 desc
.value
= O
.value
;
129 // A value property is not specified.
132 if ("writable" in O
) {
133 // A writable property is specified.
134 desc
.writable
= !!O
.writable
;
136 // A writable property is not specified.
140 // A get property is specified.
141 const getter
= O
.get;
142 if (getter
!== undefined && typeof getter
!== "function") {
143 // The getter is not callable.
144 throw new TypeError("Piscēs: Getters must be callable.");
146 // The getter is callable.
150 // A get property is not specified.
154 // A set property is specified.
155 const setter
= O
.set;
156 if (setter
!== undefined && typeof setter
!== "function") {
157 // The setter is not callable.
158 throw new TypeError("Piscēs: Setters must be callable.");
160 // The setter is callable.
164 // A set property is not specified.
168 ("get" in desc
|| "set" in desc
) &&
169 ("value" in desc
|| "writable" in desc
)
171 // Both accessor and data attributes have been defined.
173 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
176 // The property descriptor is valid.
177 return new Proxy(desc
, propertyDescriptorProxyHandler
);
183 * Completes this property descriptor by setting missing values to
186 * This method modifies this object and returns undefined.
189 if (this !== undefined && !("get" in this || "set" in this)) {
190 // This is a generic or data descriptor.
191 if (!("value" in this)) {
192 // `value` is not defined on this.
193 this.value
= undefined;
195 // `value` is already defined on this.
198 if (!("writable" in this)) {
199 // `writable` is not defined on this.
200 this.writable
= false;
202 // `writable` is already defined on this.
206 // This is not a generic or data descriptor.
207 if (!("get" in this)) {
208 // `get` is not defined on this.
209 this.get = undefined;
211 // `get` is already defined on this.
214 if (!("set" in this)) {
215 // `set` is not defined on this.
216 this.set = undefined;
218 // `set` is already defined on this.
222 if (!("enumerable" in this)) {
223 // `enumerable` is not defined on this.
224 this.enumerable
= false;
226 // `enumerable` is already defined on this.
229 if (!("configurable" in this)) {
230 // `configurable` is not defined on this.
231 this.configurable
= false;
233 // `configurable` is already defined on this.
238 /** Gets whether this is an accessor descrtiptor. */
239 get isAccessorDescriptor() {
240 return this !== undefined && ("get" in this || "set" in this);
243 /** Gets whether this is a data descrtiptor. */
244 get isDataDescriptor() {
245 return this !== undefined &&
246 ("value" in this || "writable" in this);
249 /** Gets whether this is a fully‐populated property descriptor. */
250 get isFullyPopulated() {
251 return this !== undefined &&
252 ("value" in this && "writable" in this ||
253 "get" in this && "set" in this) &&
254 "enumerable" in this && "configurable" in this;
258 * Gets whether this is a generic (not accessor or data)
261 get isGenericDescriptor() {
262 return this !== undefined &&
263 !("get" in this || "set" in this || "value" in this ||
268 const coercePropertyDescriptorValue
= (P
, V
) => {
277 if (V
!== undefined && typeof V
!== "function") {
279 "Piscēs: Getters must be callable.",
285 if (V
!== undefined && typeof V
!== "function") {
287 "Piscēs: Setters must be callable.",
298 prototype: propertyDescriptorPrototype
,
299 } = PropertyDescriptor
;
301 const propertyDescriptorProxyHandler
= Object
.assign(
304 defineProperty(O
, P
, Desc
) {
306 P
=== "configurable" || P
=== "enumerable" ||
307 P
=== "writable" || P
=== "value" ||
308 P
=== "get" || P
=== "set"
310 // P is a property descriptor attribute.
311 const desc
= new PropertyDescriptor(Desc
);
312 if ("get" in desc
|| "set" in desc
) {
313 // Desc is an accessor property descriptor.
315 "Piscēs: Property descriptor attributes must be data properties.",
317 } else if ("value" in desc
|| !(P
in O
)) {
318 // Desc has a value or P does not already exist on O.
319 desc
.value
= coercePropertyDescriptorValue(P
, desc
.value
);
321 // Desc is not an accessor property descriptor and has no
325 const isAccessorDescriptor
= "get" === P
|| "set" === P
||
326 "get" in O
|| "set" in O
;
327 const isDataDescriptor
= "value" === P
|| "writable" === P
||
328 "value" in O
|| "writable" in O
;
329 if (isAccessorDescriptor
&& isDataDescriptor
) {
330 // Both accessor and data attributes will be present on O
333 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
336 // P can be safely defined on O.
337 return defineOwnProperty(O
, P
, desc
);
340 // P is not a property descriptor attribute.
341 return defineOwnProperty(O
, P
, Desc
);
344 set(O
, P
, V
, Receiver
) {
346 P
=== "configurable" || P
=== "enumerable" ||
347 P
=== "writable" || P
=== "value" ||
348 P
=== "get" || P
=== "set"
350 // P is a property descriptor attribute.
351 const newValue
= coercePropertyDescriptorValue(P
, V
);
352 const isAccessorDescriptor
= "get" === P
|| "set" === P
||
353 "get" in O
|| "set" in O
;
354 const isDataDescriptor
= "value" === P
|| "writable" === P
||
355 "value" in O
|| "writable" in O
;
356 if (isAccessorDescriptor
&& isDataDescriptor
) {
357 // Both accessor and data attributes will be present on O
360 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
363 // P can be safely defined on O.
365 // ☡ Receiver will be the *proxied* object, so passing it
366 // through to setPropertyValue here would produce an
369 // ☡ This has implications on objects with a proxied
370 // PropertyDescriptor in their prototype.
371 return setPropertyValue(O
, P
, newValue
, O
);
374 return setPropertyValue(O
, P
, V
, Receiver
);
377 setPrototypeOf(O
, V
) {
378 if (V
!== propertyDescriptorPrototype
) {
379 // V is not the property descriptor prototype.
382 // V is the property descriptor prototype.
383 return setPrototype(O
, V
);
389 return { PropertyDescriptor
};
394 * Defines own properties on the provided object using the
395 * descriptors on the enumerable own properties of the provided
396 * additional objects.
398 * ※ This differs from Object.defineProperties in that it can take
399 * multiple source objects.
403 const { defineProperties
} = Object
;
404 const { forEach
: arrayForEach
} = Array
.prototype;
406 defineOwnProperties
: (O
, ...sources
) => {
410 [(source
) => defineProperties(O
, source
)],
419 * Defines an own property on the provided object on the provided
420 * property key using the provided property descriptor.
422 * ※ This is an alias for Object.defineProperty.
424 defineProperty
: defineOwnProperty
,
427 * Marks the provided object as non·extensible and marks all its
428 * properties as nonconfigurable and (if data properties)
429 * nonwritable, and returns the object.
431 * ※ This is an alias for Object.freeze.
436 * Returns the property descriptor for the own property with the
437 * provided property key on the provided object, or null if none
440 * ※ This is an alias for Object.getOwnPropertyDescriptor.
442 getOwnPropertyDescriptor
,
445 * Returns the property descriptors for the own properties on the
448 * ※ This is an alias for 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 an alias for Object.getOwnPropertyNames.
460 getOwnPropertyNames
: 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 an alias for Object.getOwnPropertySymbols.
470 getOwnPropertySymbols
,
473 * Returns the prototype of the provided object.
475 * ※ This is an alias for Object.getPrototypeOf.
477 getPrototypeOf
: getPrototype
,
480 * Returns whether the provided object has an own property with the
481 * provided property key.
483 * ※ This is an alias for Object.hasOwn.
485 hasOwn
: hasOwnProperty
,
488 * Returns whether the provided object is extensible.
490 * ※ This is an alias for Object.isExtensible.
495 * Returns whether the provided object is frozen.
497 * ※ This is an alias for Object.isFrozen.
502 * Returns whether the provided object is sealed.
504 * ※ This is an alias for Object.isSealed.
509 * Returns an array of key~value pairs for the enumerable,
510 * string‐valued property keys on the provided object.
512 * ※ This is an alias for Object.entries.
514 entries
: namedEntries
,
517 * Returns an array of the enumerable, string‐valued property keys on
518 * the provided object.
520 * ※ This is an alias for Object.keys.
525 * Returns an array of property values for the enumerable,
526 * string‐valued property keys on the provided object.
528 * ※ This is an alias for Object.values.
533 * Returns a new object with the provided prototype and property
536 * ※ This is an alias for Object.create.
538 create
: objectCreate
,
541 * Returns a new object with the provided property keys and values.
543 * ※ This is an alias for Object.fromEntries.
545 fromEntries
: objectFromEntries
,
548 * Marks the provided object as non·extensible, and returns the
551 * ※ This is an alias for Object.preventExtensions.
556 * Marks the provided object as non·extensible and marks all its
557 * properties as nonconfigurable, and returns the object.
559 * ※ This is an alias for Object.seal.
564 * Sets the values of the enumerable own properties of the provided
565 * additional objects on the provided object.
567 * ※ This is an alias for Object.assign.
569 assign
: setPropertyValues
,
572 * Sets the prototype of the provided object to the provided value
573 * and returns the object.
575 * ※ This is an alias for Object.setPrototypeOf.
577 setPrototypeOf
: setPrototype
,
582 * Removes the provided property key from the provided object and
583 * returns the object.
585 * ※ This function differs from Reflect.deleteProperty and the
586 * `delete` operator in that it throws if the deletion is
589 * ☡ This function throws if the first argument is not an object.
594 * Returns an array of property keys on the provided object.
596 * ※ This is effectively an alias for Reflect.ownKeys, except that
597 * it does not require that the argument be an object.
602 * Returns the value of the provided property key on the provided
605 * ※ This is effectively an alias for Reflect.get, except that it
606 * does not require that the argument be an object.
611 * Returns whether the provided property key exists on the provided
614 * ※ This is effectively an alias for Reflect.has, except that it
615 * does not require that the argument be an object.
617 * ※ This includes properties present on the prototype chain.
622 * Sets the provided property key to the provided value on the
623 * provided object and returns the object.
625 * ※ This function differs from Reflect.set in that it throws if the
626 * setting is unsuccessful.
628 * ☡ This function throws if the first argument is not an object.
632 const { deleteProperty
, get, has
, ownKeys
, set } = Reflect
;
635 deleteOwnProperty
: (O
, P
) => {
636 if (type(O
) !== "object") {
638 `Piscēs: Tried to set property but provided value was not an object: ${V}`,
640 } else if (!deleteProperty(O
, P
)) {
642 `Piscēs: Tried to delete property from object but [[Delete]] returned false: ${P}`,
648 getOwnPropertyKeys
: (O
) => ownKeys(toObject(O
)),
649 getPropertyValue
: (O
, P
, Receiver
= O
) =>
650 get(toObject(O
), P
, Receiver
),
651 hasProperty
: (O
, P
) => has(toObject(O
), P
),
652 setPropertyValue
: (O
, P
, V
, Receiver
= O
) => {
653 if (type(O
) !== "object") {
655 `Piscēs: Tried to set property but provided value was not an object: ${V}`,
657 } else if (!set(O
, P
, V
, Receiver
)) {
659 `Piscēs: Tried to set property on object but [[Set]] returned false: ${P}`,
670 * Returns a new frozen shallow copy of the enumerable own properties
671 * of the provided object, according to the following rules :—
673 * - For data properties, create a nonconfigurable, nonwritable
674 * property with the same value.
676 * - For accessor properties, create a nonconfigurable accessor
677 * property with the same getter *and* setter.
679 * The prototype for the resulting object will be taken from the
680 * `prototype` property of the provided constructor, or the
681 * `prototype` of the `constructor` of the provided object if the
682 * provided constructor is undefined. If the used constructor has a
683 * nonnullish `Symbol.species`, that will be used instead. If the
684 * used constructor or species is nullish or does not have a
685 * `prototype` property, the prototype is set to null.
687 * ※ The prototype of the provided object itself is ignored.
692 next
: generatorIteratorNext
,
693 } = getPrototype(function* () {}.prototype);
694 const propertyDescriptorEntryIterablePrototype
= {
697 next
: bind(generatorIteratorNext
, this.generator(), []),
702 frozenCopy
: (O
, constructor = O
?.constructor) => {
704 // O is null or undefined.
706 "Piscēs: Cannot copy properties of null or undefined.",
709 // O is not null or undefined.
711 // (If not provided, the constructor will be the value of
712 // getting the `constructor` property of O.)
713 const species
= constructor?.[SPECIES
] ?? constructor;
714 return preventExtensions(
716 species
== null || !("prototype" in species
)
721 propertyDescriptorEntryIterablePrototype
,
724 value
: function* () {
725 const ownPropertyKeys
= getOwnPropertyKeys(O
);
728 i
< ownPropertyKeys
.length
;
731 const P
= ownPropertyKeys
[i
];
732 const Desc
= getOwnPropertyDescriptor(O
, P
);
733 if (Desc
.enumerable
) {
734 // P is an enumerable property.
737 "get" in Desc
|| "set" in Desc
752 // P is not an enumerable property.
769 * Returns the function on the provided value at the provided property
772 * ☡ This function throws if the provided property key does not have an
773 * associated value which is callable.
775 export const getMethod
= (V
, P
) => {
776 const func
= getPropertyValue(V
, P
);
779 } else if (typeof func
!== "function") {
780 throw new TypeError(`Piscēs: Method not callable: ${P}`);
787 * Returns the provided value converted to an object.
789 * Existing objects are returned with no modification.
791 * ☡ This function throws if its argument is null or undefined.
793 export const { toObject
} = (() => {
794 const makeObject
= Object
;
799 `Piscēs: Cannot convert ${$} into an object.`,
802 return makeObject($);
809 * Returns the property key (symbol or string) corresponding to the
812 export const toPropertyKey
= ($) => {
813 const key
= toPrimitive($, "string");
814 return typeof key
== "symbol" ? key
: `${key}`;