+import {
+ IS_CONCAT_SPREADABLE,
+ isAccessorDescriptor,
+ isDataDescriptor,
+ SPECIES,
+ toFunctionName,
+ toLength,
+ type,
+ UNDEFINED,
+} from "./value.js";
+
+const createArray = Array;
+const { isArray } = Array;
+const object = Object;
+const {
+ assign,
+ create,
+ defineProperty,
+ defineProperties,
+ entries,
+ freeze: objectFreeze,
+ fromEntries,
+ getOwnPropertyDescriptor: objectGetOwnPropertyDescriptor,
+ getOwnPropertyNames,
+ getOwnPropertySymbols: objectGetOwnPropertySymbols,
+ getPrototypeOf,
+ hasOwn,
+ isExtensible,
+ isFrozen,
+ isSealed,
+ keys,
+ preventExtensions: objectPreventExtensions,
+ seal: objectSeal,
+ setPrototypeOf,
+ values,
+} = Object;
+const {
+ apply: call,
+ deleteProperty,
+ defineProperty: reflectDefineProperty,
+ get,
+ getOwnPropertyDescriptor: reflectGetOwnPropertyDescriptor,
+ has,
+ ownKeys,
+ set,
+ setPrototypeOf: reflectSetPrototypeOf,
+} = Reflect;
+
+/**
+ * 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") {
+ // The provided value is not an object; throw an error.
+ throw new TypeError(
+ `Piscēs: Cannot construct LazyLoader: Provided argument is not an object: ${loadMethods}.`,
+ );
+ } else {
+ // The provided value is an object; process it and build the
+ // result.
+ const result = create(getPrototypeOf(loadMethods));
+ const methodKeys = ownKeys(loadMethods);
+ for (let index = 0; index < methodKeys.length; ++index) {
+ // Iterate over the property keys of the provided object and
+ // define getters and setters appropriately on the result.
+ const methodKey = methodKeys[index];
+ const { configurable, enumerable, writable } =
+ getOwnPropertyDescriptor(loadMethods, methodKey);
+ defineProperty(
+ result,
+ methodKey,
+ assign(create(null), {
+ configurable: true,
+ enumerable,
+ get: defineProperty(
+ () => {
+ const value = call(loadMethods[methodKey], result, []);
+ defineProperty(
+ result,
+ methodKey,
+ assign(create(null), {
+ configurable,
+ enumerable,
+ value,
+ writable,
+ }),
+ );
+ return value;
+ },
+ "name",
+ assign(create(null), {
+ value: toFunctionName(methodKey, "get"),
+ }),
+ ),
+ set: writable
+ ? defineProperty(
+ ($) =>
+ defineProperty(
+ result,
+ methodKey,
+ assign(create(null), {
+ configurable,
+ enumerable,
+ value: $,
+ writable,
+ }),
+ ),
+ "name",
+ assign(create(null), {
+ value: toFunctionName(methodKey, "set"),
+ }),
+ )
+ : UNDEFINED,
+ }),
+ );
+ }
+ return result;
+ }
+ }
+}
+
+/**
+ * Defines an own enumerable data property on the provided object with
+ * the provided property key and value.
+ */
+export const defineOwnDataProperty = (O, P, V) =>
+ defineProperty(
+ O,
+ P,
+ assign(create(null), {
+ configurable: true,
+ enumerable: true,
+ value: V,
+ writable: true,
+ }),
+ );
+
+/**
+ * Defines an own nonenumerable data property on the provided object
+ * with the provided property key and value.
+ */
+export const defineOwnNonenumerableDataProperty = (O, P, V) =>
+ defineProperty(
+ O,
+ P,
+ assign(create(null), {
+ configurable: true,
+ enumerable: false,
+ value: V,
+ writable: true,
+ }),
+ );
+
+/**
+ * Defines an own property on the provided object on the provided
+ * property key using the provided property descriptor.
+ *
+ * ※ This is effectively an alias for `Object.defineProperty`.
+ */
+export const defineOwnProperty = (O, P, Desc) =>
+ defineProperty(O, P, Desc);
+
+/**
+ * Defines own properties on the provided object using the descriptors
+ * on the enumerable own properties of the provided additional objects.
+ *
+ * ※ This differs from `Object.defineProperties` in that it can take
+ * multiple source objects.
+ */
+export const defineOwnProperties = (O, ...sources) => {
+ const { length } = sources;
+ for (let k = 0; k < length; ++k) {
+ defineProperties(O, sources[k]);
+ }
+ return O;
+};
+
+/**
+ * Removes the provided property key from the provided object and
+ * returns the object.
+ *
+ * ※ 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.
+ */
+export const deleteOwnProperty = (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}`,
+ );
+ } else {
+ return O;
+ }
+};
+
+/**
+ * Marks the provided object as non·extensible and marks all its
+ * properties as nonconfigurable and (if data properties) nonwritable,
+ * and returns the object.
+ *
+ * ※ This is effectively an alias for `Object.freeze`.
+ */
+export const freeze = (O) => objectFreeze(O);
+
+/**
+ * Returns a new frozen shallow copy of the enumerable own properties
+ * of the provided object, according to the following rules :—
+ *
+ * - For data properties, create a nonconfigurable, nonwritable
+ * property with the same value.
+ *
+ * - For accessor properties, create a nonconfigurable accessor
+ * 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
+ * provided constructor is undefined. If the used constructor has a
+ * 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.
+ *
+ * ※ The prototype of the provided object itself is ignored.
+ */
+export const frozenCopy = (O, constructor = O?.constructor) => {
+ if (O == null) {
+ // O is null or undefined.
+ throw new TypeError(
+ "Piscēs: Cannot copy properties of null or undefined.",
+ );
+ } else {
+ // 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?.[SPECIES] ?? constructor;
+ const copy = create(
+ species == null || !("prototype" in species)
+ ? null
+ : species.prototype,
+ );
+ const keys = ownKeys(O);
+ for (let k = 0; k < keys.length; ++k) {
+ const P = keys[k];
+ const Desc = getOwnPropertyDescriptor(O, P);
+ if (Desc.enumerable) {
+ // P is an enumerable property.
+ defineProperty(
+ copy,
+ P,
+ assign(
+ create(null),
+ isAccessorDescriptor(Desc)
+ ? {
+ configurable: false,
+ enumerable: true,
+ get: Desc.get,
+ set: Desc.set,
+ }
+ : {
+ configurable: false,
+ enumerable: true,
+ value: Desc.value,
+ writable: false,
+ },
+ ),
+ );
+ } else {
+ // P is not an enumerable property.
+ /* do nothing */
+ }
+ }
+ return objectPreventExtensions(copy);
+ }
+};
+
+/**
+ * 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 = 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 property descriptor record for the own property with the
+ * provided property key on the provided value, or null if none exists.
+ *
+ * ※ This is effectively an alias for
+ * `Object.getOwnPropertyDescriptor`, but the return value is a proxied
+ * object with null prototype.
+ */
+export const getOwnPropertyDescriptor = (O, P) => {
+ const desc = objectGetOwnPropertyDescriptor(O, P);
+ return desc === UNDEFINED
+ ? UNDEFINED
+ : toPropertyDescriptorRecord(desc);
+};
+
+/**
+ * Returns the property descriptors for the own properties on the
+ * provided value.
+ *
+ * ※ This is effectively an alias for
+ * `Object.getOwnPropertyDescriptors`, but the values on the resulting
+ * object are proxied objects with null prototypes.
+ */
+export const getOwnPropertyDescriptors = (O) => {
+ const obj = toObject(O);
+ const keys = ownKeys(obj);
+ const descriptors = {};
+ for (let k = 0; k < keys.length; ++k) {
+ // Iterate over the keys of the provided object and collect its
+ // descriptors.
+ const key = keys[k];
+ defineOwnDataProperty(
+ descriptors,
+ key,
+ getOwnPropertyDescriptor(O, key),
+ );
+ }
+ return descriptors;
+};
+
+/**
+ * Returns an array of own property entries on the provided value,
+ * using the provided receiver if given.
+ */
+export const getOwnPropertyEntries = (O, Receiver = O) => {
+ const obj = toObject(O);
+ const keys = ownKeys(obj);
+ const target = Receiver === UNDEFINED ? obj : toObject(Receiver);
+ const result = createArray(keys.length);
+ for (let k = 0; k < keys.length; ++k) {
+ // Iterate over each key and add the corresponding entry to the
+ // result.
+ const key = keys[k];
+ defineOwnDataProperty(
+ result,
+ k,
+ [key, getOwnPropertyValue(obj, keys[k], target)],
+ );
+ }
+ return result;
+};
+
+/**
+ * Returns an array of own property keys on the provided value.
+ *
+ * ※ This is effectively an alias for `Reflect.ownKeys`, except that
+ * it does not require that the argument be an object.
+ */
+export const getOwnPropertyKeys = (O) => ownKeys(toObject(O));
+
+/**
+ * Returns an array of string‐valued own property keys on the
+ * provided value.
+ *
+ * ☡ This includes both enumerable and non·enumerable properties.
+ *
+ * ※ This is effectively an alias for `Object.getOwnPropertyNames`.
+ */
+export const getOwnPropertyStrings = (O) => getOwnPropertyNames(O);
+
+/**
+ * Returns an array of symbol‐valued own property keys on the
+ * provided value.
+ *
+ * ☡ This includes both enumerable and non·enumerable properties.
+ *
+ * ※ This is effectively an alias for
+ * `Object.getOwnPropertySymbols`.
+ */
+export const getOwnPropertySymbols = (O) =>
+ objectGetOwnPropertySymbols(O);
+
+/**
+ * Returns the value of the provided own property on the provided
+ * value using the provided receiver, or undefined if the provided
+ * property is not an own property on the provided value.
+ *
+ * ※ If the receiver is not provided, it defaults to the provided
+ * value.
+ */
+export const getOwnPropertyValue = (O, P, Receiver = UNDEFINED) => {
+ const obj = toObject(O);
+ const desc = getOwnPropertyDescriptor(O, P);
+ if (desc === UNDEFINED) {
+ // The provided property is not an own property on the provided
+ // value; return undefined.
+ return UNDEFINED;
+ }
+ if (isDataDescriptor(desc)) {
+ // The provided property is a data property; return its value.
+ return desc.value;
+ } else {
+ // The provided property is an accessor property; get its value
+ // using the appropriate receiver.
+ const target = Receiver === UNDEFINED ? obj : toObject(Receiver);
+ return call(desc.get, target, []);
+ }
+};
+
+/**
+ * Returns an array of own property values on the provided value, using
+ * the provided receiver if given.
+ */
+export const getOwnPropertyValues = (O, Receiver = O) => {
+ const obj = toObject(O);
+ const keys = ownKeys(obj);
+ const target = Receiver === UNDEFINED ? obj : toObject(Receiver);
+ const result = createArray(keys.length);
+ for (let k = 0; k < keys.length; ++k) {
+ // Iterate over each key and collect the values.
+ defineOwnDataProperty(
+ result,
+ k,
+ getOwnPropertyValue(obj, keys[k], target),
+ );
+ }
+ return result;
+};
+
+/**
+ * Returns the value of the provided property key on the provided
+ * value.
+ *
+ * ※ This is effectively an alias for `Reflect.get`, except that it
+ * does not require that the argument be an object.
+ */
+export const getPropertyValue = (O, P, Receiver = O) =>
+ get(toObject(O), P, Receiver);
+