// ♓🌟 Piscēs ∷ object.js
// ====================================================================
//
-// Copyright © 2022 Lady [@ Lady’s Computer].
+// Copyright © 2022–2023 Lady [@ Lady’s Computer].
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
import { bind, call } from "./function.js";
-import { toPrimitive, type } from "./value.js";
+import { ITERATOR, SPECIES, toPrimitive, type } from "./value.js";
+
+/**
+ * An object whose properties are lazy‐loaded from the methods on the
+ * own properties of the provided object.
+ *
+ * This is useful when you are looking to reference properties on
+ * objects which, due to module dependency graphs, cannot be guaranteed
+ * to have been initialized yet.
+ *
+ * The resulting properties will have the same attributes (regarding
+ * configurability, enumerability, and writability) as the
+ * corresponding properties on the methods object. If a property is
+ * marked as writable, the method will never be called if it is set
+ * before it is gotten. By necessity, the resulting properties are all
+ * configurable before they are accessed for the first time.
+ *
+ * Methods will be called with the resulting object as their this
+ * value.
+ *
+ * `LazyLoader` objects have the same prototype as the passed methods
+ * object.
+ */
+export class LazyLoader extends null {
+ /**
+ * Constructs a new `LazyLoader` object.
+ *
+ * ☡ This function throws if the provided value is not an object.
+ */
+ constructor(loadMethods) {
+ if (type(loadMethods) !== "object") {
+ throw new TypeError(
+ `Piscēs: Cannot construct LazyLoader: Provided argument is not an object: ${loadMethods}.`,
+ );
+ } else {
+ const result = objectCreate(getPrototype(loadMethods));
+ const methodKeys = getOwnPropertyKeys(loadMethods);
+ for (let index = 0; index < methodKeys.length; ++index) {
+ const methodKey = methodKeys[index];
+ const { configurable, enumerable, writable } =
+ getOwnPropertyDescriptor(loadMethods, methodKey);
+ defineOwnProperty(result, methodKey, {
+ configurable: true,
+ enumerable,
+ get: () => {
+ const value = call(loadMethods[methodKey], result, []);
+ defineOwnProperty(result, methodKey, {
+ configurable,
+ enumerable,
+ value,
+ writable,
+ });
+ return value;
+ },
+ set: writable
+ ? ($) =>
+ defineOwnProperty(result, methodKey, {
+ configurable,
+ enumerable,
+ value: $,
+ writable,
+ })
+ : void {},
+ });
+ }
+ return result;
+ }
+ }
+}
/**
* A property descriptor object.
* object.
*
* The resulting object is proxied to enforce types (for example,
- * its `enumerable` property, if defined, will always be a
+ * its `.enumerable` property, if defined, will always be a
* boolean).
*/
- //deno-lint-ignore constructor-super
constructor(O) {
if (type(O) !== "object") {
// The provided value is not an object.
throw new TypeError(
- "Piscēs: Cannot convert primitive to property descriptor.",
+ `Piscēs: Cannot convert primitive to property descriptor: ${O}.`,
);
} else {
// The provided value is an object.
* descriptors on the enumerable own properties of the provided
* additional objects.
*
- * ※ This differs from Object.defineProperties in that it can take
+ * ※ This differs from `Object.defineProperties` in that it can take
* multiple source objects.
*/
defineOwnProperties,
* Defines an own property on the provided object on the provided
* property key using the provided property descriptor.
*
- * ※ This is an alias for Object.defineProperty.
+ * ※ This is an alias for `Object.defineProperty`.
*/
defineProperty: defineOwnProperty,
* properties as nonconfigurable and (if data properties)
* nonwritable, and returns the object.
*
- * ※ This is an alias for Object.freeze.
+ * ※ This is an alias for `Object.freeze`.
*/
freeze,
* provided property key on the provided object, or null if none
* exists.
*
- * ※ This is an alias for Object.getOwnPropertyDescriptor.
+ * ※ This is an alias for `Object.getOwnPropertyDescriptor`.
*/
getOwnPropertyDescriptor,
* Returns the property descriptors for the own properties on the
* provided object.
*
- * ※ This is an alias for Object.getOwnPropertyDescriptors.
+ * ※ This is an alias for `Object.getOwnPropertyDescriptors`.
*/
getOwnPropertyDescriptors,
*
* ☡ This includes both enumerable and non·enumerable properties.
*
- * ※ This is an alias for Object.getOwnPropertyNames.
+ * ※ This is an alias for `Object.getOwnPropertyNames`.
*/
getOwnPropertyNames: getOwnPropertyStrings,
*
* ☡ This includes both enumerable and non·enumerable properties.
*
- * ※ This is an alias for Object.getOwnPropertySymbols.
+ * ※ This is an alias for `Object.getOwnPropertySymbols`.
*/
getOwnPropertySymbols,
/**
* Returns the prototype of the provided object.
*
- * ※ This is an alias for Object.getPrototypeOf.
+ * ※ This is an alias for `Object.getPrototypeOf`.
*/
getPrototypeOf: getPrototype,
* Returns whether the provided object has an own property with the
* provided property key.
*
- * ※ This is an alias for Object.hasOwn.
+ * ※ This is an alias for `Object.hasOwn`.
*/
hasOwn: hasOwnProperty,
/**
* Returns whether the provided object is extensible.
*
- * ※ This is an alias for Object.isExtensible.
+ * ※ This is an alias for `Object.isExtensible`.
*/
- isExtensible,
+ isExtensible: isExtensibleObject,
/**
* Returns whether the provided object is frozen.
*
- * ※ This is an alias for Object.isFrozen.
+ * ※ This is an alias for `Object.isFrozen`.
*/
- isFrozen,
+ isFrozen: isFrozenObject,
/**
* Returns whether the provided object is sealed.
*
- * ※ This is an alias for Object.isSealed.
+ * ※ This is an alias for `Object.isSealed`.
*/
- isSealed,
+ isSealed: isSealedObject,
/**
* Returns an array of key~value pairs for the enumerable,
* string‐valued property keys on the provided object.
*
- * ※ This is an alias for Object.entries.
+ * ※ This is an alias for `Object.entries`.
*/
entries: namedEntries,
* Returns an array of the enumerable, string‐valued property keys on
* the provided object.
*
- * ※ This is an alias for Object.keys.
+ * ※ This is an alias for `Object.keys`.
*/
keys: namedKeys,
* Returns an array of property values for the enumerable,
* string‐valued property keys on the provided object.
*
- * ※ This is an alias for Object.values.
+ * ※ This is an alias for `Object.values`.
*/
values: namedValues,
* Returns a new object with the provided prototype and property
* descriptors.
*
- * ※ This is an alias for Object.create.
+ * ※ This is an alias for `Object.create`.
*/
create: objectCreate,
/**
* Returns a new object with the provided property keys and values.
*
- * ※ This is an alias for Object.fromEntries.
+ * ※ This is an alias for `Object.fromEntries`.
*/
fromEntries: objectFromEntries,
* Marks the provided object as non·extensible, and returns the
* object.
*
- * ※ This is an alias for Object.preventExtensions.
+ * ※ This is an alias for `Object.preventExtensions`.
*/
preventExtensions,
* Marks the provided object as non·extensible and marks all its
* properties as nonconfigurable, and returns the object.
*
- * ※ This is an alias for Object.seal.
+ * ※ This is an alias for `Object.seal`.
*/
seal,
* Sets the values of the enumerable own properties of the provided
* additional objects on the provided object.
*
- * ※ This is an alias for Object.assign.
+ * ※ This is an alias for `Object.assign`.
*/
assign: setPropertyValues,
* Sets the prototype of the provided object to the provided value
* and returns the object.
*
- * ※ This is an alias for Object.setPrototypeOf.
+ * ※ This is an alias for `Object.setPrototypeOf`.
*/
setPrototypeOf: setPrototype,
} = Object;
* Removes the provided property key from the provided object and
* returns the object.
*
- * ☡ This function differs from Reflect.deleteProperty and the
+ * ※ This function differs from `Reflect.deleteProperty` and the
* `delete` operator in that it throws if the deletion is
* unsuccessful.
+ *
+ * ☡ This function throws if the first argument is not an object.
*/
deleteOwnProperty,
+ /**
+ * Returns an array of property keys on the provided object.
+ *
+ * ※ This is effectively an alias for `Reflect.ownKeys`, except that
+ * it does not require that the argument be an object.
+ */
+ getOwnPropertyKeys,
+
+ /**
+ * Returns the value of the provided property key on the provided
+ * object.
+ *
+ * ※ This is effectively an alias for `Reflect.get`, except that it
+ * does not require that the argument be an object.
+ */
+ getPropertyValue,
+
+ /**
+ * Returns whether the provided property key exists on the provided
+ * object.
+ *
+ * ※ This is effectively an alias for `Reflect.has`, except that it
+ * does not require that the argument be an object.
+ *
+ * ※ This includes properties present on the prototype chain.
+ */
+ hasProperty,
+
/**
* Sets the provided property key to the provided value on the
* provided object and returns the object.
*
- * ※ This function differs from Reflect.set in that it throws if the
- * setting is unsuccessful.
+ * ※ This function differs from `Reflect.set` in that it throws if
+ * the setting is unsuccessful.
+ *
+ * ☡ This function throws if the first argument is not an object.
*/
setPropertyValue,
} = (() => {
- const { deleteProperty, set } = Reflect;
+ const { deleteProperty, get, has, ownKeys, set } = Reflect;
return {
deleteOwnProperty: (O, P) => {
- if (!deleteProperty(O, P)) {
+ if (type(O) !== "object") {
+ throw new TypeError(
+ `Piscēs: Tried to set property but provided value was not an object: ${V}`,
+ );
+ } else if (!deleteProperty(O, P)) {
throw new TypeError(
`Piscēs: Tried to delete property from object but [[Delete]] returned false: ${P}`,
);
return O;
}
},
+ getOwnPropertyKeys: (O) => ownKeys(toObject(O)),
+ getPropertyValue: (O, P, Receiver = O) =>
+ get(toObject(O), P, Receiver),
+ hasProperty: (O, P) => has(toObject(O), P),
setPropertyValue: (O, P, V, Receiver = O) => {
- if (!set(O, P, V, Receiver)) {
+ if (type(O) !== "object") {
+ throw new TypeError(
+ `Piscēs: Tried to set property but provided value was not an object: ${V}`,
+ );
+ } else if (!set(O, P, V, Receiver)) {
throw new TypeError(
`Piscēs: Tried to set property on object but [[Set]] returned false: ${P}`,
);
export const {
/**
* Returns a new frozen shallow copy of the enumerable own properties
- * of the provided object, according to the following rules :—
+ * of the provided object, according to the following rules :—
*
* - For data properties, create a nonconfigurable, nonwritable
* property with the same value.
* property with the same getter *and* setter.
*
* The prototype for the resulting object will be taken from the
- * `prototype` property of the provided constructor, or the
- * `prototype` of the `constructor` of the provided object if the
+ * `.prototype` property of the provided constructor, or the
+ * `.prototype` of the `.constructor` of the provided object if the
* provided constructor is undefined. If the used constructor has a
- * nonnullish `Symbol.species`, that will be used instead. If the
+ * nonnullish `.[Symbol.species]`, that will be used instead. If the
* used constructor or species is nullish or does not have a
- * `prototype` property, the prototype is set to null.
+ * `.prototype` property, the prototype is set to null.
+ *
+ * ※ The prototype of the provided object itself is ignored.
*/
frozenCopy,
} = (() => {
- const {
- iterator: iteratorSymbol,
- species: speciesSymbol,
- } = Symbol;
const {
next: generatorIteratorNext,
} = getPrototype(function* () {}.prototype);
const propertyDescriptorEntryIterablePrototype = {
- [iteratorSymbol]() {
+ [ITERATOR]() {
return {
next: bind(generatorIteratorNext, this.generator(), []),
};
// O is not null or undefined.
//
// (If not provided, the constructor will be the value of
- // getting the `constructor` property of O.)
- const species = constructor?.[speciesSymbol] ?? constructor;
+ // getting the `.constructor` property of O.)
+ const species = constructor?.[SPECIES] ?? constructor;
return preventExtensions(
objectCreate(
species == null || !("prototype" in species)
};
})();
-export const {
- /**
- * Returns an array of property keys on the provided object.
- *
- * ※ This is an alias for Reflect.ownKeys.
- */
- ownKeys: getOwnPropertyKeys,
-
- /**
- * Returns the value of the provided property key on the provided
- * object.
- *
- * ※ This is an alias for Reflect.get.
- */
- get: getPropertyValue,
-
- /**
- * Returns whether the provided property key exists on the provided
- * object.
- *
- * ※ This is an alias for Reflect.has.
- *
- * ※ This includes properties present on the prototype chain.
- */
- has: hasProperty,
-} = Reflect;
+/**
+ * Returns the function on the provided value at the provided property
+ * key.
+ *
+ * ☡ This function throws if the provided property key does not have an
+ * associated value which is callable.
+ */
+export const getMethod = (V, P) => {
+ const func = getPropertyValue(V, P);
+ if (func == null) {
+ return undefined;
+ } else if (typeof func !== "function") {
+ throw new TypeError(`Piscēs: Method not callable: ${P}`);
+ } else {
+ return func;
+ }
+};
/**
* Returns the provided value converted to an object.
*
- * Null and undefined are converted to a new, empty object. Other
- * primitives are wrapped. Existing objects are returned with no
- * modification.
+ * Existing objects are returned with no modification.
*
- * ※ This is effectively a nonconstructible version of the Object
- * constructor.
+ * ☡ This function throws if its argument is null or undefined.
*/
export const { toObject } = (() => {
const makeObject = Object;
- return { toObject: ($) => makeObject($) };
+ return {
+ toObject: ($) => {
+ if ($ == null) {
+ throw new TypeError(
+ `Piscēs: Cannot convert ${$} into an object.`,
+ );
+ } else {
+ return makeObject($);
+ }
+ },
+ };
})();
/**