1 // ♓🌟 Piscēs ∷ object.js
2 // ====================================================================
4 // Copyright © 2022 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 { 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 {
35 /** Constructs a new LazyLoader object. */
36 constructor(loadMethods
) {
37 const result
= objectCreate(getPrototype(loadMethods
));
38 const methodKeys
= getOwnPropertyKeys(loadMethods
);
39 for (let index
= 0; index
< methodKeys
.length
; ++index
) {
40 const methodKey
= methodKeys
[index
];
41 const { configurable
, enumerable
, writable
} =
42 getOwnPropertyDescriptor(loadMethods
, methodKey
);
43 defineOwnProperty(result
, methodKey
, {
47 const value
= call(loadMethods
[methodKey
], result
, []);
48 defineOwnProperty(result
, methodKey
, {
58 defineOwnProperty(result
, methodKey
, {
72 * A property descriptor object.
74 * Actually constructing a property descriptor object using this class
75 * is only necessary if you need strict guarantees about the types of
76 * its properties; the resulting object is proxied to ensure the types
77 * match what one would expect from composing FromPropertyDescriptor
78 * and ToPropertyDescriptor in the Ecmascript specification.
80 * Otherwise, the instance properties and methods are generic.
82 export const { PropertyDescriptor
} = (() => {
83 class PropertyDescriptor
extends null {
85 * Constructs a new property descriptor object from the provided
88 * The resulting object is proxied to enforce types (for example,
89 * its `enumerable` property, if defined, will always be a
92 //deno-lint-ignore constructor-super
94 if (type(O
) !== "object") {
95 // The provided value is not an object.
97 "Piscēs: Cannot convert primitive to property descriptor.",
100 // The provided value is an object.
101 const desc
= objectCreate(propertyDescriptorPrototype
);
102 if ("enumerable" in O
) {
103 // An enumerable property is specified.
104 desc
.enumerable
= !!O
.enumerable
;
106 // An enumerable property is not specified.
109 if ("configurable" in O
) {
110 // A configurable property is specified.
111 desc
.configurable
= !!O
.configurable
;
113 // A configurable property is not specified.
117 // A value property is specified.
118 desc
.value
= O
.value
;
120 // A value property is not specified.
123 if ("writable" in O
) {
124 // A writable property is specified.
125 desc
.writable
= !!O
.writable
;
127 // A writable property is not specified.
131 // A get property is specified.
132 const getter
= O
.get;
133 if (getter
!== undefined && typeof getter
!== "function") {
134 // The getter is not callable.
135 throw new TypeError("Piscēs: Getters must be callable.");
137 // The getter is callable.
141 // A get property is not specified.
145 // A set property is specified.
146 const setter
= O
.set;
147 if (setter
!== undefined && typeof setter
!== "function") {
148 // The setter is not callable.
149 throw new TypeError("Piscēs: Setters must be callable.");
151 // The setter is callable.
155 // A set property is not specified.
159 ("get" in desc
|| "set" in desc
) &&
160 ("value" in desc
|| "writable" in desc
)
162 // Both accessor and data attributes have been defined.
164 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
167 // The property descriptor is valid.
168 return new Proxy(desc
, propertyDescriptorProxyHandler
);
174 * Completes this property descriptor by setting missing values to
177 * This method modifies this object and returns undefined.
180 if (this !== undefined && !("get" in this || "set" in this)) {
181 // This is a generic or data descriptor.
182 if (!("value" in this)) {
183 // `value` is not defined on this.
184 this.value
= undefined;
186 // `value` is already defined on this.
189 if (!("writable" in this)) {
190 // `writable` is not defined on this.
191 this.writable
= false;
193 // `writable` is already defined on this.
197 // This is not a generic or data descriptor.
198 if (!("get" in this)) {
199 // `get` is not defined on this.
200 this.get = undefined;
202 // `get` is already defined on this.
205 if (!("set" in this)) {
206 // `set` is not defined on this.
207 this.set = undefined;
209 // `set` is already defined on this.
213 if (!("enumerable" in this)) {
214 // `enumerable` is not defined on this.
215 this.enumerable
= false;
217 // `enumerable` is already defined on this.
220 if (!("configurable" in this)) {
221 // `configurable` is not defined on this.
222 this.configurable
= false;
224 // `configurable` is already defined on this.
229 /** Gets whether this is an accessor descrtiptor. */
230 get isAccessorDescriptor() {
231 return this !== undefined && ("get" in this || "set" in this);
234 /** Gets whether this is a data descrtiptor. */
235 get isDataDescriptor() {
236 return this !== undefined &&
237 ("value" in this || "writable" in this);
240 /** Gets whether this is a fully‐populated property descriptor. */
241 get isFullyPopulated() {
242 return this !== undefined &&
243 ("value" in this && "writable" in this ||
244 "get" in this && "set" in this) &&
245 "enumerable" in this && "configurable" in this;
249 * Gets whether this is a generic (not accessor or data)
252 get isGenericDescriptor() {
253 return this !== undefined &&
254 !("get" in this || "set" in this || "value" in this ||
259 const coercePropertyDescriptorValue
= (P
, V
) => {
268 if (V
!== undefined && typeof V
!== "function") {
270 "Piscēs: Getters must be callable.",
276 if (V
!== undefined && typeof V
!== "function") {
278 "Piscēs: Setters must be callable.",
289 prototype: propertyDescriptorPrototype
,
290 } = PropertyDescriptor
;
292 const propertyDescriptorProxyHandler
= Object
.assign(
295 defineProperty(O
, P
, Desc
) {
297 P
=== "configurable" || P
=== "enumerable" ||
298 P
=== "writable" || P
=== "value" ||
299 P
=== "get" || P
=== "set"
301 // P is a property descriptor attribute.
302 const desc
= new PropertyDescriptor(Desc
);
303 if ("get" in desc
|| "set" in desc
) {
304 // Desc is an accessor property descriptor.
306 "Piscēs: Property descriptor attributes must be data properties.",
308 } else if ("value" in desc
|| !(P
in O
)) {
309 // Desc has a value or P does not already exist on O.
310 desc
.value
= coercePropertyDescriptorValue(P
, desc
.value
);
312 // Desc is not an accessor property descriptor and has no
316 const isAccessorDescriptor
= "get" === P
|| "set" === P
||
317 "get" in O
|| "set" in O
;
318 const isDataDescriptor
= "value" === P
|| "writable" === P
||
319 "value" in O
|| "writable" in O
;
320 if (isAccessorDescriptor
&& isDataDescriptor
) {
321 // Both accessor and data attributes will be present on O
324 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
327 // P can be safely defined on O.
328 return defineOwnProperty(O
, P
, desc
);
331 // P is not a property descriptor attribute.
332 return defineOwnProperty(O
, P
, Desc
);
335 set(O
, P
, V
, Receiver
) {
337 P
=== "configurable" || P
=== "enumerable" ||
338 P
=== "writable" || P
=== "value" ||
339 P
=== "get" || P
=== "set"
341 // P is a property descriptor attribute.
342 const newValue
= coercePropertyDescriptorValue(P
, V
);
343 const isAccessorDescriptor
= "get" === P
|| "set" === P
||
344 "get" in O
|| "set" in O
;
345 const isDataDescriptor
= "value" === P
|| "writable" === P
||
346 "value" in O
|| "writable" in O
;
347 if (isAccessorDescriptor
&& isDataDescriptor
) {
348 // Both accessor and data attributes will be present on O
351 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
354 // P can be safely defined on O.
356 // ☡ Receiver will be the *proxied* object, so passing it
357 // through to setPropertyValue here would produce an
360 // ☡ This has implications on objects with a proxied
361 // PropertyDescriptor in their prototype.
362 return setPropertyValue(O
, P
, newValue
, O
);
365 return setPropertyValue(O
, P
, V
, Receiver
);
368 setPrototypeOf(O
, V
) {
369 if (V
!== propertyDescriptorPrototype
) {
370 // V is not the property descriptor prototype.
373 // V is the property descriptor prototype.
374 return setPrototype(O
, V
);
380 return { PropertyDescriptor
};
385 * Defines own properties on the provided object using the
386 * descriptors on the enumerable own properties of the provided
387 * additional objects.
389 * ※ This differs from Object.defineProperties in that it can take
390 * multiple source objects.
394 const { defineProperties
} = Object
;
395 const { forEach
: arrayForEach
} = Array
.prototype;
397 defineOwnProperties
: (O
, ...sources
) => {
401 [(source
) => defineProperties(O
, source
)],
410 * Defines an own property on the provided object on the provided
411 * property key using the provided property descriptor.
413 * ※ This is an alias for Object.defineProperty.
415 defineProperty
: defineOwnProperty
,
418 * Marks the provided object as non·extensible and marks all its
419 * properties as nonconfigurable and (if data properties)
420 * nonwritable, and returns the object.
422 * ※ This is an alias for Object.freeze.
427 * Returns the property descriptor for the own property with the
428 * provided property key on the provided object, or null if none
431 * ※ This is an alias for Object.getOwnPropertyDescriptor.
433 getOwnPropertyDescriptor
,
436 * Returns the property descriptors for the own properties on the
439 * ※ This is an alias for Object.getOwnPropertyDescriptors.
441 getOwnPropertyDescriptors
,
444 * Returns an array of string‐valued own property keys on the
447 * ☡ This includes both enumerable and non·enumerable properties.
449 * ※ This is an alias for Object.getOwnPropertyNames.
451 getOwnPropertyNames
: getOwnPropertyStrings
,
454 * Returns an array of symbol‐valued own property keys on the
457 * ☡ This includes both enumerable and non·enumerable properties.
459 * ※ This is an alias for Object.getOwnPropertySymbols.
461 getOwnPropertySymbols
,
464 * Returns the prototype of the provided object.
466 * ※ This is an alias for Object.getPrototypeOf.
468 getPrototypeOf
: getPrototype
,
471 * Returns whether the provided object has an own property with the
472 * provided property key.
474 * ※ This is an alias for Object.hasOwn.
476 hasOwn
: hasOwnProperty
,
479 * Returns whether the provided object is extensible.
481 * ※ This is an alias for Object.isExtensible.
486 * Returns whether the provided object is frozen.
488 * ※ This is an alias for Object.isFrozen.
493 * Returns whether the provided object is sealed.
495 * ※ This is an alias for Object.isSealed.
500 * Returns an array of key~value pairs for the enumerable,
501 * string‐valued property keys on the provided object.
503 * ※ This is an alias for Object.entries.
505 entries
: namedEntries
,
508 * Returns an array of the enumerable, string‐valued property keys on
509 * the provided object.
511 * ※ This is an alias for Object.keys.
516 * Returns an array of property values for the enumerable,
517 * string‐valued property keys on the provided object.
519 * ※ This is an alias for Object.values.
524 * Returns a new object with the provided prototype and property
527 * ※ This is an alias for Object.create.
529 create
: objectCreate
,
532 * Returns a new object with the provided property keys and values.
534 * ※ This is an alias for Object.fromEntries.
536 fromEntries
: objectFromEntries
,
539 * Marks the provided object as non·extensible, and returns the
542 * ※ This is an alias for Object.preventExtensions.
547 * Marks the provided object as non·extensible and marks all its
548 * properties as nonconfigurable, and returns the object.
550 * ※ This is an alias for Object.seal.
555 * Sets the values of the enumerable own properties of the provided
556 * additional objects on the provided object.
558 * ※ This is an alias for Object.assign.
560 assign
: setPropertyValues
,
563 * Sets the prototype of the provided object to the provided value
564 * and returns the object.
566 * ※ This is an alias for Object.setPrototypeOf.
568 setPrototypeOf
: setPrototype
,
573 * Removes the provided property key from the provided object and
574 * returns the object.
576 * ☡ This function differs from Reflect.deleteProperty and the
577 * `delete` operator in that it throws if the deletion is
583 * Sets the provided property key to the provided value on the
584 * provided object and returns the object.
586 * ※ This function differs from Reflect.set in that it throws if the
587 * setting is unsuccessful.
591 const { deleteProperty
, set } = Reflect
;
594 deleteOwnProperty
: (O
, P
) => {
595 if (!deleteProperty(O
, P
)) {
597 `Piscēs: Tried to delete property from object but [[Delete]] returned false: ${P}`,
603 setPropertyValue
: (O
, P
, V
, Receiver
= O
) => {
604 if (!set(O
, P
, V
, Receiver
)) {
606 `Piscēs: Tried to set property on object but [[Set]] returned false: ${P}`,
617 * Returns a new frozen shallow copy of the enumerable own properties
618 * of the provided object, according to the following rules :—
620 * - For data properties, create a nonconfigurable, nonwritable
621 * property with the same value.
623 * - For accessor properties, create a nonconfigurable accessor
624 * property with the same getter *and* setter.
626 * The prototype for the resulting object will be taken from the
627 * `prototype` property of the provided constructor, or the
628 * `prototype` of the `constructor` of the provided object if the
629 * provided constructor is undefined. If the used constructor has a
630 * nonnullish `Symbol.species`, that will be used instead. If the
631 * used constructor or species is nullish or does not have a
632 * `prototype` property, the prototype is set to null.
637 iterator
: iteratorSymbol
,
638 species
: speciesSymbol
,
641 next
: generatorIteratorNext
,
642 } = getPrototype(function* () {}.prototype);
643 const propertyDescriptorEntryIterablePrototype
= {
646 next
: bind(generatorIteratorNext
, this.generator(), []),
651 frozenCopy
: (O
, constructor = O
?.constructor) => {
653 // O is null or undefined.
655 "Piscēs: Cannot copy properties of null or undefined.",
658 // O is not null or undefined.
660 // (If not provided, the constructor will be the value of
661 // getting the `constructor` property of O.)
662 const species
= constructor?.[speciesSymbol
] ?? constructor;
663 return preventExtensions(
665 species
== null || !("prototype" in species
)
670 propertyDescriptorEntryIterablePrototype
,
673 value
: function* () {
674 const ownPropertyKeys
= getOwnPropertyKeys(O
);
677 i
< ownPropertyKeys
.length
;
680 const P
= ownPropertyKeys
[i
];
681 const Desc
= getOwnPropertyDescriptor(O
, P
);
682 if (Desc
.enumerable
) {
683 // P is an enumerable property.
686 "get" in Desc
|| "set" in Desc
701 // P is not an enumerable property.
719 * Returns an array of property keys on the provided object.
721 * ※ This is an alias for Reflect.ownKeys.
723 ownKeys
: getOwnPropertyKeys
,
726 * Returns the value of the provided property key on the provided
729 * ※ This is an alias for Reflect.get.
731 get: getPropertyValue
,
734 * Returns whether the provided property key exists on the provided
737 * ※ This is an alias for Reflect.has.
739 * ※ This includes properties present on the prototype chain.
745 * Returns the provided value converted to an object.
747 * Null and undefined are converted to a new, empty object. Other
748 * primitives are wrapped. Existing objects are returned with no
751 * ※ This is effectively a nonconstructible version of the Object
754 export const { toObject
} = (() => {
755 const makeObject
= Object
;
756 return { toObject
: ($) => makeObject($) };
760 * Returns the property key (symbol or string) corresponding to the
763 export const toPropertyKey
= ($) => {
764 const key
= toPrimitive($, "string");
765 return typeof key
== "symbol" ? key
: `${key}`;