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";
24 * An object whose properties are lazy‐loaded from the methods on the
25 * own properties of the provided object.
27 * This is useful when you are looking to reference properties on
28 * objects which, due to module dependency graphs, cannot be guaranteed
29 * to have been initialized yet.
31 * The resulting properties will have the same attributes (regarding
32 * configurability, enumerability, and writability) as the
33 * corresponding properties on the methods object. If a property is
34 * marked as writable, the method will never be called if it is set
35 * before it is gotten. By necessity, the resulting properties are all
36 * configurable before they are accessed for the first time.
38 * Methods will be called with the resulting object as their this
41 * `LazyLoader` objects have the same prototype as the passed methods
44 export class LazyLoader
extends null {
46 * Constructs a new `LazyLoader` object.
48 * ☡ This function throws if the provided value is not an object.
50 constructor(loadMethods
) {
51 if (type(loadMethods
) !== "object") {
52 // The provided value is not an object; throw an error.
54 `Piscēs: Cannot construct LazyLoader: Provided argument is not an object: ${loadMethods}.`,
57 // The provided value is an object; process it and build the
59 const result
= objectCreate(getPrototype(loadMethods
));
60 const methodKeys
= getOwnPropertyKeys(loadMethods
);
61 for (let index
= 0; index
< methodKeys
.length
; ++index
) {
62 // Iterate over the property keys of the provided object and
63 // define getters and setters appropriately on the result.
64 const methodKey
= methodKeys
[index
];
65 const { configurable
, enumerable
, writable
} =
66 getOwnPropertyDescriptor(loadMethods
, methodKey
);
67 defineOwnProperty(result
, methodKey
, {
70 get: defineOwnProperty(
72 const value
= call(loadMethods
[methodKey
], result
, []);
73 defineOwnProperty(result
, methodKey
, {
82 { value
: toFunctionName(methodKey
, "get") },
87 defineOwnProperty(result
, methodKey
, {
94 { value
: toFunctionName(methodKey
, "set") },
105 * Defines an own property on the provided object on the provided
106 * property key using the provided property descriptor.
108 * ※ This is effectively an alias for `Object.defineProperty`.
110 export const defineOwnProperty
= createArrowFunction(
111 Object
.defineProperty
,
112 { name
: "defineOwnProperty" },
117 * Defines own properties on the provided object using the
118 * descriptors on the enumerable own properties of the provided
119 * additional objects.
121 * ※ This differs from `Object.defineProperties` in that it can take
122 * multiple source objects.
127 * Returns a new frozen shallow copy of the enumerable own properties
128 * of the provided object, according to the following rules :—
130 * - For data properties, create a nonconfigurable, nonwritable
131 * property with the same value.
133 * - For accessor properties, create a nonconfigurable accessor
134 * property with the same getter *and* setter.
136 * The prototype for the resulting object will be taken from the
137 * `.prototype` property of the provided constructor, or the
138 * `.prototype` of the `.constructor` of the provided object if the
139 * provided constructor is undefined. If the used constructor has a
140 * nonnullish `.[Symbol.species]`, that will be used instead. If the
141 * used constructor or species is nullish or does not have a
142 * `.prototype` property, the prototype is set to null.
144 * ※ The prototype of the provided object itself is ignored.
149 * Returns the property descriptor record for the own property with
150 * the provided property key on the provided object, or null if none
153 * ※ This is effectively an alias for
154 * `Object.getOwnPropertyDescriptor`, but the return value is a
155 * proxied object with null prototype.
157 getOwnPropertyDescriptor
,
160 * Returns the property descriptors for the own properties on the
163 * ※ This is effectively an alias for
164 * `Object.getOwnPropertyDescriptors`, but the values on the
165 * resulting object are proxied objects with null prototypes.
167 getOwnPropertyDescriptors
,
170 * Returns whether the provided object is frozen.
172 * ※ This function returns false for nonobjects.
174 * ※ This is effectively an alias for `!Object.isFrozen`.
179 * Returns whether the provided object is sealed.
181 * ※ This function returns false for nonobjects.
183 * ※ This is effectively an alias for `!Object.isSealed`.
188 * Sets the prototype of the provided object to the provided value
189 * and returns the object.
191 * ※ This is effectively an alias for `Object.setPrototypeOf`.
196 * Returns the provided value converted to an object.
198 * Existing objects are returned with no modification.
200 * ☡ This function throws if its argument is null or undefined.
204 const createObject
= Object
;
208 getOwnPropertyDescriptor
: objectGetOwnPropertyDescriptor
,
215 next
: generatorIteratorNext
,
216 } = getPrototypeOf(function* () {}.prototype);
217 const propertyDescriptorEntryIterablePrototype
= {
220 next
: bind(generatorIteratorNext
, this.generator(), []),
224 const propertyDescriptorEntryIterable
= ($) =>
225 create(propertyDescriptorEntryIterablePrototype
, {
226 generator
: { value
: $ },
230 defineOwnProperties
: (O
, ...sources
) => {
231 const { length
} = sources
;
232 for (let index
= 0; index
< length
; ++index
) {
233 defineProperties(O
, sources
[index
]);
237 frozenCopy
: (O
, constructor = O
?.constructor) => {
239 // O is null or undefined.
241 "Piscēs: Cannot copy properties of null or undefined.",
244 // O is not null or undefined.
246 // (If not provided, the constructor will be the value of
247 // getting the `.constructor` property of O.)
248 const species
= constructor?.[SPECIES
] ?? constructor;
249 return preventExtensions(
251 species
== null || !("prototype" in species
)
255 propertyDescriptorEntryIterable(function* () {
256 const ownPropertyKeys
= getOwnPropertyKeys(O
);
259 i
< ownPropertyKeys
.length
;
262 const P
= ownPropertyKeys
[i
];
263 const Desc
= getOwnPropertyDescriptor(O
, P
);
264 if (Desc
.enumerable
) {
265 // P is an enumerable property.
268 "get" in Desc
|| "set" in Desc
283 // P is not an enumerable property.
293 getOwnPropertyDescriptor
: (O
, P
) => {
294 const desc
= objectGetOwnPropertyDescriptor(O
, P
);
295 return desc
=== UNDEFINED
297 : toPropertyDescriptor(desc
);
299 getOwnPropertyDescriptors
: (O
) => {
300 const obj
= toObject(O
);
301 const ownKeys
= getOwnPropertyKeys(obj
);
302 const descriptors
= {};
303 for (let k
= 0; k
< ownKeys
.length
; ++k
) {
304 const key
= ownKeys
[k
];
305 defineOwnProperty(descriptors
, key
, {
308 value
: getOwnPropertyDescriptor(O
, key
),
314 isUnfrozenObject
: (O
) => !isFrozen(O
),
315 isUnsealedObject
: (O
) => !isSealed(O
),
316 setPrototype
: (O
, proto
) => {
317 const obj
= toObject(O
);
319 // The provided value is an object; set its prototype normally.
320 return setPrototypeOf(O
, proto
);
322 // The provided value is not an object; attempt to set the
323 // prototype on a coerced version with extensions prevented,
324 // then return the provided value.
326 // This will throw if the given prototype does not match the
327 // existing one on the coerced object.
328 setPrototypeOf(preventExtensions(obj
), proto
);
334 // The provided value is nullish; this is an error.
336 `Piscēs: Cannot convert ${$} into an object.`,
339 // The provided value is not nullish; coerce it to an object.
340 return createObject($);
348 * Removes the provided property key from the provided object and
349 * returns the object.
351 * ※ This function differs from `Reflect.deleteProperty` and the
352 * `delete` operator in that it throws if the deletion is
355 * ☡ This function throws if the first argument is not an object.
360 * Returns an array of property keys on the provided object.
362 * ※ This is effectively an alias for `Reflect.ownKeys`, except that
363 * it does not require that the argument be an object.
368 * Returns the value of the provided property key on the provided
371 * ※ This is effectively an alias for `Reflect.get`, except that it
372 * does not require that the argument be an object.
377 * Returns whether the provided property key exists on the provided
380 * ※ This is effectively an alias for `Reflect.has`, except that it
381 * does not require that the argument be an object.
383 * ※ This includes properties present on the prototype chain.
388 * Sets the provided property key to the provided value on the
389 * provided object and returns the object.
391 * ※ This function differs from `Reflect.set` in that it throws if
392 * the setting is unsuccessful.
394 * ☡ This function throws if the first argument is not an object.
398 const { deleteProperty
, get, has
, ownKeys
, set } = Reflect
;
401 deleteOwnProperty
: (O
, P
) => {
402 if (type(O
) !== "object") {
404 `Piscēs: Tried to set property but provided value was not an object: ${V}`,
406 } else if (!deleteProperty(O
, P
)) {
408 `Piscēs: Tried to delete property from object but [[Delete]] returned false: ${P}`,
414 getOwnPropertyKeys
: (O
) => ownKeys(toObject(O
)),
415 getPropertyValue
: (O
, P
, Receiver
= O
) =>
416 get(toObject(O
), P
, Receiver
),
417 hasProperty
: (O
, P
) => has(toObject(O
), P
),
418 setPropertyValue
: (O
, P
, V
, Receiver
= O
) => {
419 if (type(O
) !== "object") {
421 `Piscēs: Tried to set property but provided value was not an object: ${V}`,
423 } else if (!set(O
, P
, V
, Receiver
)) {
425 `Piscēs: Tried to set property on object but [[Set]] returned false: ${P}`,
435 * Marks the provided object as non·extensible and marks all its
436 * properties as nonconfigurable and (if data properties) nonwritable,
437 * and returns the object.
439 * ※ This is effectively an alias for `Object.freeze`.
441 export const freeze
= createArrowFunction(Object
.freeze
);
444 * Returns the function on the provided value at the provided property
447 * ☡ This function throws if the provided property key does not have an
448 * associated value which is callable.
450 export const getMethod
= (V
, P
) => {
451 const func
= getPropertyValue(V
, P
);
454 } else if (typeof func
!== "function") {
455 throw new TypeError(`Piscēs: Method not callable: ${P}`);
462 * Returns an array of string‐valued own property keys on the
465 * ☡ This includes both enumerable and non·enumerable properties.
467 * ※ This is effectively an alias for `Object.getOwnPropertyNames`.
469 export const getOwnPropertyStrings
= createArrowFunction(
470 Object
.getOwnPropertyNames
,
471 { name
: "getOwnPropertyStrings" },
475 * Returns an array of symbol‐valued own property keys on the
478 * ☡ This includes both enumerable and non·enumerable properties.
480 * ※ This is effectively an alias for
481 * `Object.getOwnPropertySymbols`.
483 export const getOwnPropertySymbols
= createArrowFunction(
484 Object
.getOwnPropertySymbols
,
488 * Returns the prototype of the provided object.
490 * ※ This is effectively an alias for `Object.getPrototypeOf`.
492 export const getPrototype
= createArrowFunction(
493 Object
.getPrototypeOf
,
494 { name
: "getPrototype" },
498 * Returns whether the provided object has an own property with the
499 * provided property key.
501 * ※ This is effectively an alias for `Object.hasOwn`.
503 export const hasOwnProperty
= createArrowFunction(Object
.hasOwn
, {
504 name
: "hasOwnProperty",
507 /** Returns whether the provided value is an arraylike object. */
508 export const isArraylikeObject
= ($) => {
509 if (type($) !== "object") {
513 lengthOfArraylike($); // throws if not arraylike
523 * Returns whether the provided value is spreadable during array
526 * This is also used to determine which things should be treated as
529 isConcatSpreadableObject
,
531 const { isArray
} = Array
;
534 isConcatSpreadableObject
: ($) => {
535 if (type($) !== "object") {
536 // The provided value is not an object.
539 // The provided value is an object.
540 const spreadable
= $[IS_CONCAT_SPREADABLE
];
541 return spreadable
!== undefined ? !!spreadable
: isArray($);
548 * Returns whether the provided object is extensible.
550 * ※ This function returns false for nonobjects.
552 * ※ This is effectively an alias for `Object.isExtensible`.
554 export const isExtensibleObject
= createArrowFunction(
556 { name
: "isExtensibleObject" },
560 * Returns the length of the provided arraylike value.
562 * This can produce larger lengths than can actually be stored in
563 * arrays, because no such restrictions exist on arraylike methods.
565 * ☡ This function throws if the provided value is not arraylike.
567 export const lengthOfArraylike
= ({ length
}) => toLength(length
);
570 * Returns an array of key~value pairs for the enumerable,
571 * string‐valued property keys on the provided object.
573 * ※ This is effectively an alias for `Object.entries`.
575 export const namedEntries
= createArrowFunction(Object
.entries
, {
576 name
: "namedEntries",
580 * Returns an array of the enumerable, string‐valued property keys on
581 * the provided object.
583 * ※ This is effectively an alias for `Object.keys`.
585 export const namedKeys
= createArrowFunction(Object
.keys
, {
590 * Returns an array of property values for the enumerable,
591 * string‐valued property keys on the provided object.
593 * ※ This is effectively an alias for `Object.values`.
595 export const namedValues
= createArrowFunction(Object
.values
, {
600 * Returns a new object with the provided prototype and property
603 * ※ This is effectively an alias for `Object.create`.
605 export const objectCreate
= createArrowFunction(Object
.create
, {
606 name
: "objectCreate",
610 * Returns a new object with the provided property keys and values.
612 * ※ This is effectively an alias for `Object.fromEntries`.
614 export const objectFromEntries
= createArrowFunction(
616 { name
: "objectFromEntries" },
620 * Marks the provided object as non·extensible, and returns the
623 * ※ This is effectively an alias for `Object.preventExtensions`.
625 export const preventExtensions
= createArrowFunction(
626 Object
.preventExtensions
,
630 * Marks the provided object as non·extensible and marks all its
631 * properties as nonconfigurable, and returns the object.
633 * ※ This is effectively an alias for `Object.seal`.
635 export const seal
= createArrowFunction(Object
.seal
);
638 * Sets the values of the enumerable own properties of the provided
639 * additional objects on the provided object.
641 * ※ This is effectively an alias for `Object.assign`.
643 export const setPropertyValues
= createArrowFunction(Object
.assign
, {
644 name
: "setPropertyValues",
648 * Returns the property key (symbol or string) corresponding to the
651 export const toPropertyKey
= ($) => {
652 const key
= toPrimitive($, "string");
653 return typeof key
=== "symbol" ? key
: `${key}`;