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
, createArrowFunction
} from "./function.js";
22 * An object whose properties are lazy‐loaded from the methods on the
23 * own properties of the provided object.
25 * This is useful when you are looking to reference properties on
26 * objects which, due to module dependency graphs, cannot be guaranteed
27 * to have been initialized yet.
29 * The resulting properties will have the same attributes (regarding
30 * configurability, enumerability, and writability) as the
31 * corresponding properties on the methods object. If a property is
32 * marked as writable, the method will never be called if it is set
33 * before it is gotten. By necessity, the resulting properties are all
34 * configurable before they are accessed for the first time.
36 * Methods will be called with the resulting object as their this
39 * `LazyLoader` objects have the same prototype as the passed methods
42 export class LazyLoader
extends null {
44 * Constructs a new `LazyLoader` object.
46 * ☡ This function throws if the provided value is not an object.
48 constructor(loadMethods
) {
49 if (type(loadMethods
) !== "object") {
50 // The provided value is not an object; throw an error.
52 `Piscēs: Cannot construct LazyLoader: Provided argument is not an object: ${loadMethods}.`,
55 // The provided value is an object; process it and build the
57 const result
= objectCreate(getPrototype(loadMethods
));
58 const methodKeys
= getOwnPropertyKeys(loadMethods
);
59 for (let index
= 0; index
< methodKeys
.length
; ++index
) {
60 // Iterate over the property keys of the provided object and
61 // define getters and setters appropriately on the result.
62 const methodKey
= methodKeys
[index
];
63 const { configurable
, enumerable
, writable
} =
64 getOwnPropertyDescriptor(loadMethods
, methodKey
);
65 defineOwnProperty(result
, methodKey
, {
68 get: defineOwnProperty(
70 const value
= call(loadMethods
[methodKey
], result
, []);
71 defineOwnProperty(result
, methodKey
, {
80 { value
: toFunctionName(methodKey
, "get") },
85 defineOwnProperty(result
, methodKey
, {
92 { value
: toFunctionName(methodKey
, "set") },
103 * Defines an own property on the provided object on the provided
104 * property key using the provided property descriptor.
106 * ※ This is effectively an alias for `Object.defineProperty`.
108 export const defineOwnProperty
= createArrowFunction(
109 Object
.defineProperty
,
110 { name
: "defineOwnProperty" },
115 * Defines own properties on the provided object using the
116 * descriptors on the enumerable own properties of the provided
117 * additional objects.
119 * ※ This differs from `Object.defineProperties` in that it can take
120 * multiple source objects.
125 * Returns a new frozen shallow copy of the enumerable own properties
126 * of the provided object, according to the following rules :—
128 * - For data properties, create a nonconfigurable, nonwritable
129 * property with the same value.
131 * - For accessor properties, create a nonconfigurable accessor
132 * property with the same getter *and* setter.
134 * The prototype for the resulting object will be taken from the
135 * `.prototype` property of the provided constructor, or the
136 * `.prototype` of the `.constructor` of the provided object if the
137 * provided constructor is undefined. If the used constructor has a
138 * nonnullish `.[Symbol.species]`, that will be used instead. If the
139 * used constructor or species is nullish or does not have a
140 * `.prototype` property, the prototype is set to null.
142 * ※ The prototype of the provided object itself is ignored.
147 * Returns whether the provided object is frozen.
149 * ※ This function returns false for nonobjects.
151 * ※ This is effectively an alias for `!Object.isFrozen`.
156 * Returns whether the provided object is sealed.
158 * ※ This function returns false for nonobjects.
160 * ※ This is effectively an alias for `!Object.isSealed`.
165 * Sets the prototype of the provided object to the provided value
166 * and returns the object.
168 * ※ This is effectively an alias for `Object.setPrototypeOf`.
173 * Returns the provided value converted to an object.
175 * Existing objects are returned with no modification.
177 * ☡ This function throws if its argument is null or undefined.
181 const createObject
= Object
;
191 next
: generatorIteratorNext
,
192 } = getPrototypeOf(function* () {}.prototype);
193 const propertyDescriptorEntryIterablePrototype
= {
196 next
: bind(generatorIteratorNext
, this.generator(), []),
200 const propertyDescriptorEntryIterable
= ($) =>
201 create(propertyDescriptorEntryIterablePrototype
, {
202 generator
: { value
: $ },
206 defineOwnProperties
: (O
, ...sources
) => {
207 const { length
} = sources
;
208 for (let index
= 0; index
< length
; ++index
) {
209 defineProperties(O
, sources
[index
]);
213 frozenCopy
: (O
, constructor = O
?.constructor) => {
215 // O is null or undefined.
217 "Piscēs: Cannot copy properties of null or undefined.",
220 // O is not null or undefined.
222 // (If not provided, the constructor will be the value of
223 // getting the `.constructor` property of O.)
224 const species
= constructor?.[SPECIES
] ?? constructor;
225 return preventExtensions(
227 species
== null || !("prototype" in species
)
231 propertyDescriptorEntryIterable(function* () {
232 const ownPropertyKeys
= getOwnPropertyKeys(O
);
235 i
< ownPropertyKeys
.length
;
238 const P
= ownPropertyKeys
[i
];
239 const Desc
= getOwnPropertyDescriptor(O
, P
);
240 if (Desc
.enumerable
) {
241 // P is an enumerable property.
244 "get" in Desc
|| "set" in Desc
259 // P is not an enumerable property.
269 isUnfrozenObject
: (O
) => !isFrozen(O
),
270 isUnsealedObject
: (O
) => !isSealed(O
),
271 setPrototype
: (O
, proto
) => {
272 const obj
= toObject(O
);
274 // The provided value is an object; set its prototype normally.
275 return setPrototypeOf(O
, proto
);
277 // The provided value is not an object; attempt to set the
278 // prototype on a coerced version with extensions prevented,
279 // then return the provided value.
281 // This will throw if the given prototype does not match the
282 // existing one on the coerced object.
283 setPrototypeOf(preventExtensions(obj
), proto
);
289 // The provided value is nullish; this is an error.
291 `Piscēs: Cannot convert ${$} into an object.`,
294 // The provided value is not nullish; coerce it to an object.
295 return createObject($);
303 * Removes the provided property key from the provided object and
304 * returns the object.
306 * ※ This function differs from `Reflect.deleteProperty` and the
307 * `delete` operator in that it throws if the deletion is
310 * ☡ This function throws if the first argument is not an object.
315 * Returns an array of property keys on the provided object.
317 * ※ This is effectively an alias for `Reflect.ownKeys`, except that
318 * it does not require that the argument be an object.
323 * Returns the value of the provided property key on the provided
326 * ※ This is effectively an alias for `Reflect.get`, except that it
327 * does not require that the argument be an object.
332 * Returns whether the provided property key exists on the provided
335 * ※ This is effectively an alias for `Reflect.has`, except that it
336 * does not require that the argument be an object.
338 * ※ This includes properties present on the prototype chain.
343 * Sets the provided property key to the provided value on the
344 * provided object and returns the object.
346 * ※ This function differs from `Reflect.set` in that it throws if
347 * the setting is unsuccessful.
349 * ☡ This function throws if the first argument is not an object.
353 const { deleteProperty
, get, has
, ownKeys
, set } = Reflect
;
356 deleteOwnProperty
: (O
, P
) => {
357 if (type(O
) !== "object") {
359 `Piscēs: Tried to set property but provided value was not an object: ${V}`,
361 } else if (!deleteProperty(O
, P
)) {
363 `Piscēs: Tried to delete property from object but [[Delete]] returned false: ${P}`,
369 getOwnPropertyKeys
: (O
) => ownKeys(toObject(O
)),
370 getPropertyValue
: (O
, P
, Receiver
= O
) =>
371 get(toObject(O
), P
, Receiver
),
372 hasProperty
: (O
, P
) => has(toObject(O
), P
),
373 setPropertyValue
: (O
, P
, V
, Receiver
= O
) => {
374 if (type(O
) !== "object") {
376 `Piscēs: Tried to set property but provided value was not an object: ${V}`,
378 } else if (!set(O
, P
, V
, Receiver
)) {
380 `Piscēs: Tried to set property on object but [[Set]] returned false: ${P}`,
390 * Marks the provided object as non·extensible and marks all its
391 * properties as nonconfigurable and (if data properties) nonwritable,
392 * and returns the object.
394 * ※ This is effectively an alias for `Object.freeze`.
396 export const freeze
= createArrowFunction(Object
.freeze
);
399 * Returns the function on the provided value at the provided property
402 * ☡ This function throws if the provided property key does not have an
403 * associated value which is callable.
405 export const getMethod
= (V
, P
) => {
406 const func
= getPropertyValue(V
, P
);
409 } else if (typeof func
!== "function") {
410 throw new TypeError(`Piscēs: Method not callable: ${P}`);
417 * Returns the property descriptor for the own property with the
418 * provided property key on the provided object, or null if none
421 * ※ This is effectively an alias for
422 * `Object.getOwnPropertyDescriptor`.
424 export const getOwnPropertyDescriptor
= createArrowFunction(
425 Object
.getOwnPropertyDescriptor
,
429 * Returns the property descriptors for the own properties on the
432 * ※ This is effectively an alias for
433 * `Object.getOwnPropertyDescriptors`.
435 export const getOwnPropertyDescriptors
= createArrowFunction(
436 Object
.getOwnPropertyDescriptors
,
440 * Returns an array of string‐valued own property keys on the
443 * ☡ This includes both enumerable and non·enumerable properties.
445 * ※ This is effectively an alias for `Object.getOwnPropertyNames`.
447 export const getOwnPropertyStrings
= createArrowFunction(
448 Object
.getOwnPropertyNames
,
449 { name
: "getOwnPropertyStrings" },
453 * Returns an array of symbol‐valued own property keys on the
456 * ☡ This includes both enumerable and non·enumerable properties.
458 * ※ This is effectively an alias for
459 * `Object.getOwnPropertySymbols`.
461 export const getOwnPropertySymbols
= createArrowFunction(
462 Object
.getOwnPropertySymbols
,
466 * Returns the prototype of the provided object.
468 * ※ This is effectively an alias for `Object.getPrototypeOf`.
470 export const getPrototype
= createArrowFunction(
471 Object
.getPrototypeOf
,
472 { name
: "getPrototype" },
476 * Returns whether the provided object has an own property with the
477 * provided property key.
479 * ※ This is effectively an alias for `Object.hasOwn`.
481 export const hasOwnProperty
= createArrowFunction(Object
.hasOwn
, {
482 name
: "hasOwnProperty",
485 /** Returns whether the provided value is an arraylike object. */
486 export const isArraylikeObject
= ($) => {
487 if (type($) !== "object") {
491 lengthOfArraylike($); // throws if not arraylike
501 * Returns whether the provided value is spreadable during array
504 * This is also used to determine which things should be treated as
507 isConcatSpreadableObject
,
509 const { isArray
} = Array
;
512 isConcatSpreadableObject
: ($) => {
513 if (type($) !== "object") {
514 // The provided value is not an object.
517 // The provided value is an object.
518 const spreadable
= $[IS_CONCAT_SPREADABLE
];
519 return spreadable
!== undefined ? !!spreadable
: isArray($);
526 * Returns whether the provided object is extensible.
528 * ※ This function returns false for nonobjects.
530 * ※ This is effectively an alias for `Object.isExtensible`.
532 export const isExtensibleObject
= createArrowFunction(
534 { name
: "isExtensibleObject" },
538 * Returns the length of the provided arraylike value.
540 * This can produce larger lengths than can actually be stored in
541 * arrays, because no such restrictions exist on arraylike methods.
543 * ☡ This function throws if the provided value is not arraylike.
545 export const lengthOfArraylike
= ({ length
}) => toLength(length
);
548 * Returns an array of key~value pairs for the enumerable,
549 * string‐valued property keys on the provided object.
551 * ※ This is effectively an alias for `Object.entries`.
553 export const namedEntries
= createArrowFunction(Object
.entries
, {
554 name
: "namedEntries",
558 * Returns an array of the enumerable, string‐valued property keys on
559 * the provided object.
561 * ※ This is effectively an alias for `Object.keys`.
563 export const namedKeys
= createArrowFunction(Object
.keys
, {
568 * Returns an array of property values for the enumerable,
569 * string‐valued property keys on the provided object.
571 * ※ This is effectively an alias for `Object.values`.
573 export const namedValues
= createArrowFunction(Object
.values
, {
578 * Returns a new object with the provided prototype and property
581 * ※ This is effectively an alias for `Object.create`.
583 export const objectCreate
= createArrowFunction(Object
.create
, {
584 name
: "objectCreate",
588 * Returns a new object with the provided property keys and values.
590 * ※ This is effectively an alias for `Object.fromEntries`.
592 export const objectFromEntries
= createArrowFunction(
594 { name
: "objectFromEntries" },
598 * Marks the provided object as non·extensible, and returns the
601 * ※ This is effectively an alias for `Object.preventExtensions`.
603 export const preventExtensions
= createArrowFunction(
604 Object
.preventExtensions
,
608 * Marks the provided object as non·extensible and marks all its
609 * properties as nonconfigurable, and returns the object.
611 * ※ This is effectively an alias for `Object.seal`.
613 export const seal
= createArrowFunction(Object
.seal
);
616 * Sets the values of the enumerable own properties of the provided
617 * additional objects on the provided object.
619 * ※ This is effectively an alias for `Object.assign`.
621 export const setPropertyValues
= createArrowFunction(Object
.assign
, {
622 name
: "setPropertyValues",
626 * Returns the property key (symbol or string) corresponding to the
629 export const toPropertyKey
= ($) => {
630 const key
= toPrimitive($, "string");
631 return typeof key
=== "symbol" ? key
: `${key}`;