// ♓🌟 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 {
+ 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);
+
/**
- * A property descriptor object.
- *
- * Actually constructing a property descriptor object using this class
- * is only necessary if you need strict guarantees about the types of
- * its properties; the resulting object is proxied to ensure the types
- * match what one would expect from composing FromPropertyDescriptor
- * and ToPropertyDescriptor in the Ecmascript specification.
- *
- * Otherwise, the instance properties and methods are generic.
- */
-export const PropertyDescriptor = (() => {
- class PropertyDescriptor extends null {
- /**
- * Constructs a new property descriptor object from the provided
- * object.
- *
- * The resulting object is proxied to enforce types (for example,
- * its `enumerable` property, if defined, will always be a
- * boolean).
- */
- //deno-lint-ignore constructor-super
- constructor(Obj) {
- if (!isObject(Obj)) {
+ * Returns the prototype of the provided value.
+ *
+ * ※ This is effectively an alias for `Object.getPrototypeOf`.
+ */
+export const getPrototype = (O) => getPrototypeOf(O);
+
+/**
+ * Returns whether the provided value has an own property with the
+ * provided property key.
+ *
+ * ※ This is effectively an alias for `Object.hasOwn`.
+ */
+export const hasOwnProperty = (O, P) => hasOwn(O, P);
+
+/**
+ * Returns whether the provided property key exists on the provided
+ * value.
+ *
+ * ※ 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.
+ */
+export const hasProperty = (O, P) => has(toObject(O), P);
+
+/** Returns whether the provided value is an arraylike object. */
+export const isArraylikeObject = ($) => {
+ if (type($) !== "object") {
+ return false;
+ } else {
+ try {
+ lengthOfArraylike($); // throws if not arraylike
+ return true;
+ } catch {
+ return false;
+ }
+ }
+};
+
+/**
+ * Returns whether the provided value is spreadable during array
+ * concatenation.
+ *
+ * This is also used to determine which things should be treated as
+ * collections.
+ */
+export const isConcatSpreadableObject = ($) => {
+ if (type($) !== "object") {
+ // The provided value is not an object.
+ return false;
+ } else {
+ // The provided value is an object.
+ const spreadable = $[IS_CONCAT_SPREADABLE];
+ return spreadable !== UNDEFINED ? !!spreadable : isArray($);
+ }
+};
+
+/**
+ * Returns whether the provided value is an extensible object.
+ *
+ * ※ This function returns false for nonobjects.
+ *
+ * ※ This is effectively an alias for `Object.isExtensible`.
+ */
+export const isExtensibleObject = (O) => isExtensible(O);
+
+export const {
+ /**
+ * Returns whether the provided value is a property descriptor record
+ * as created by `toPropertyDescriptor`.
+ *
+ * ※ This function is provided to enable inspection of whether an
+ * object uses the property descriptor record proxy implementation,
+ * not as a general test of whether an object satisfies the
+ * requirements for property descriptors. In most cases, a more
+ * specific test, like `isAccessorDescriptor`, `isDataDescriptor`, or
+ * `isGenericDescriptor`, is preferrable.
+ */
+ isPropertyDescriptorRecord,
+
+ /**
+ * Converts the provided value to a property descriptor record.
+ *
+ * ※ The prototype of a property descriptor record is always `null`.
+ *
+ * ※ Actually constructing a property descriptor object using this
+ * class is only necessary if you need strict guarantees about the
+ * types of its properties; the resulting object is proxied to ensure
+ * the types match what one would expect from composing
+ * FromPropertyDescriptor and ToPropertyDescriptor in the Ecmascript
+ * specification.
+ */
+ toPropertyDescriptorRecord,
+} = (() => {
+ const proxyConstructor = Proxy;
+ const { add: weakSetAdd, has: weakSetHas } = WeakSet.prototype;
+ const propertyDescriptorRecords = new WeakSet();
+ const coercePropertyDescriptorValue = (P, V) => {
+ switch (P) {
+ case "configurable":
+ case "enumerable":
+ case "writable":
+ return !!V;
+ case "value":
+ return V;
+ case "get":
+ if (V !== undefined && typeof V !== "function") {
+ throw new TypeError(
+ "Piscēs: Getters must be callable.",
+ );
+ } else {
+ return V;
+ }
+ case "set":
+ if (V !== undefined && typeof V !== "function") {
+ throw new TypeError(
+ "Piscēs: Setters must be callable.",
+ );
+ } else {
+ return V;
+ }
+ default:
+ return V;
+ }
+ };
+ const propertyDescriptorProxyHandler = objectFreeze(
+ assign(
+ create(null),
+ {
+ defineProperty(O, P, Desc) {
+ if (
+ P === "configurable" || P === "enumerable" ||
+ P === "writable" || P === "value" ||
+ P === "get" || P === "set"
+ ) {
+ // P is a property descriptor attribute.
+ const desc = assign(objectCreate(null), Desc);
+ if ("get" in desc || "set" in desc) {
+ // Desc is an accessor property descriptor.
+ throw new TypeError(
+ "Piscēs: Property descriptor attributes must be data properties.",
+ );
+ } else if ("value" in desc || !(P in O)) {
+ // Desc has a value or P does not already exist on O.
+ desc.value = coercePropertyDescriptorValue(
+ P,
+ desc.value,
+ );
+ } else {
+ // Desc is not an accessor property descriptor and has no
+ // value, but an existing value is present on O.
+ /* do nothing */
+ }
+ const isAccessorDescriptor = "get" === P || "set" === P ||
+ "get" in O || "set" in O;
+ const isDataDescriptor = "value" === P ||
+ "writable" === P ||
+ "value" in O || "writable" in O;
+ if (isAccessorDescriptor && isDataDescriptor) {
+ // Both accessor and data attributes will be present on O
+ // after defining P.
+ throw new TypeError(
+ "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
+ );
+ } else {
+ // P can be safely defined on O.
+ return reflectDefineProperty(O, P, desc);
+ }
+ } else {
+ // P is not a property descriptor attribute.
+ return reflectDefineProperty(O, P, Desc);
+ }
+ },
+ setPrototypeOf(O, V) {
+ if (V !== null) {
+ // V is not the property descriptor prototype.
+ return false;
+ } else {
+ // V is the property descriptor prototype.
+ return reflectSetPrototypeOf(O, V);
+ }
+ },
+ },
+ ),
+ );
+
+ return {
+ isPropertyDescriptorRecord: ($) =>
+ call(weakSetHas, propertyDescriptorRecords, [$]),
+ toPropertyDescriptorRecord: (Obj) => {
+ if (type(Obj) !== "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.
- const desc = Object.create(propertyDescriptorPrototype);
+ const desc = create(null);
if ("enumerable" in Obj) {
// An enumerable property is specified.
- desc.enumerable = !!Obj.enumerable;
+ defineOwnDataProperty(desc, "enumerable", !!Obj.enumerable);
} else {
// An enumerable property is not specified.
/* do nothing */
}
if ("configurable" in Obj) {
// A configurable property is specified.
- desc.configurable = !!Obj.configurable;
+ defineOwnDataProperty(
+ desc,
+ "configurable",
+ !!Obj.configurable,
+ );
} else {
// A configurable property is not specified.
/* do nothing */
}
if ("value" in Obj) {
// A value property is specified.
- desc.value = Obj.value;
+ defineOwnDataProperty(desc, "value", Obj.value);
} else {
// A value property is not specified.
/* do nothing */
}
if ("writable" in Obj) {
// A writable property is specified.
- desc.writable = !!Obj.writable;
+ defineOwnDataProperty(desc, "writable", !!Obj.writable);
} else {
// A writable property is not specified.
/* do nothing */
if ("get" in Obj) {
// A get property is specified.
const getter = Obj.get;
- if (typeof getter != "function") {
+ if (getter !== UNDEFINED && typeof getter !== "function") {
// The getter is not callable.
throw new TypeError("Piscēs: Getters must be callable.");
} else {
// The getter is callable.
- desc.get = getter;
+ defineOwnDataProperty(desc, "get", Obj.get);
}
} else {
// A get property is not specified.
if ("set" in Obj) {
// A set property is specified.
const setter = Obj.set;
- if (typeof setter != "function") {
+ if (setter !== UNDEFINED && typeof setter !== "function") {
// The setter is not callable.
throw new TypeError("Piscēs: Setters must be callable.");
} else {
// The setter is callable.
- desc.set = setter;
+ defineOwnDataProperty(desc, "set", Obj.set);
}
} else {
// A set property is not specified.
);
} else {
// The property descriptor is valid.
- return new Proxy(desc, propertyDescriptorProxyHandler);
+ const record = new proxyConstructor(
+ desc,
+ propertyDescriptorProxyHandler,
+ );
+ call(weakSetAdd, propertyDescriptorRecords, [record]);
+ return record;
}
}
- }
+ },
+ };
+})();
- /**
- * Completes this property descriptor by setting missing values to
- * their defaults.
- *
- * This method modifies this object and returns undefined.
- */
- complete() {
- if (this !== undefined && !("get" in this || "set" in this)) {
- // This is a generic or data descriptor.
- if (!("value" in this)) {
- // `value` is not defined on this.
- this.value = undefined;
- } else {
- // `value` is already defined on this.
- /* do nothing */
- }
- if (!("writable" in this)) {
- // `writable` is not defined on this.
- this.writable = false;
- } else {
- // `writable` is already defined on this.
- /* do nothing */
- }
- } else {
- // This is not a generic or data descriptor.
- if (!("get" in this)) {
- // `get` is not defined on this.
- this.get = undefined;
- } else {
- // `get` is already defined on this.
- /* do nothing */
- }
- if (!("set" in this)) {
- // `set` is not defined on this.
- this.set = undefined;
- } else {
- // `set` is already defined on this.
- /* do nothing */
- }
- }
- if (!("enumerable" in this)) {
- // `enumerable` is not defined on this.
- this.enumerable = false;
- } else {
- // `enumerable` is already defined on this.
- /* do nothing */
- }
- if (!("configurable" in this)) {
- // `configurable` is not defined on this.
- this.configurable = false;
- } else {
- // `configurable` is already defined on this.
- /* do nothing */
- }
- }
+/**
+ * Returns whether the provided value is an unfrozen object.
+ *
+ * ※ This function returns false for nonobjects.
+ *
+ * ※ This is effectively an alias for `!Object.isFrozen`.
+ */
+export const isUnfrozenObject = (O) => !isFrozen(O);
- /** Returns whether this is an accessor descrtiptor. */
- get isAccessorDescriptor() {
- return this !== undefined && ("get" in this || "set" in this);
- }
+/**
+ * Returns whether the provided value is an unsealed object.
+ *
+ * ※ This function returns false for nonobjects.
+ *
+ * ※ This is effectively an alias for `!Object.isSealed`.
+ */
+export const isUnsealedObject = (O) => !isSealed(O);
- /** Returns whether this is a data descrtiptor. */
- get isDataDescriptor() {
- return this !== undefined &&
- ("value" in this || "writable" in this);
- }
+/**
+ * Returns the length of the provided arraylike value.
+ *
+ * This can produce larger lengths than can actually be stored in
+ * arrays, because no such restrictions exist on arraylike methods.
+ *
+ * ☡ This function throws if the provided value is not arraylike.
+ */
+export const lengthOfArraylike = ({ length }) => toLength(length);
- /**
- * Returns whether this is a fully‐populated property descriptor.
- */
- get isFullyPopulated() {
- return this !== undefined &&
- ("value" in this && "writable" in this ||
- "get" in this && "set" in this) &&
- "enumerable" in this && "configurable" in this;
- }
+/**
+ * Returns an array of key~value pairs for the enumerable,
+ * string‐valued property keys on the provided value.
+ *
+ * ※ This is effectively an alias for `Object.entries`.
+ */
+export const namedEntries = (O) => entries(O);
- /**
- * Returns whether this is a generic (not accessor or data)
- * descrtiptor.
- */
- get isGenericDescriptor() {
- return this !== undefined &&
- !("get" in this || "set" in this || "value" in this ||
- "writable" in this);
- }
- }
+/**
+ * Returns an array of the enumerable, string‐valued property keys on
+ * the provided value.
+ *
+ * ※ This is effectively an alias for `Object.keys`.
+ */
+export const namedKeys = (O) => keys(O);
- const coercePropretyDescriptorValue = (P, V) => {
- switch (P) {
- case "configurable":
- case "enumerable":
- case "writable":
- return !!V;
- case "value":
- return V;
- case "get":
- if (typeof V != "function") {
- throw new TypeError(
- "Piscēs: Getters must be callable.",
- );
- } else {
- return V;
- }
- case "set":
- if (typeof V != "function") {
- throw new TypeError(
- "Piscēs: Setters must be callable.",
- );
- } else {
- return V;
- }
- default:
- return V;
- }
- };
+/**
+ * Returns an array of property values for the enumerable,
+ * string‐valued property keys on the provided value.
+ *
+ * ※ This is effectively an alias for `Object.values`.
+ */
+export const namedValues = (O) => values(O);
- const propertyDescriptorPrototype = PropertyDescriptor.prototype;
+/**
+ * Returns a new object with the provided prototype and property
+ * descriptors.
+ *
+ * ※ This is effectively an alias for `Object.create`.
+ */
+export const objectCreate = (O, Properties) => create(O, Properties);
- const propertyDescriptorProxyHandler = Object.assign(
- Object.create(null),
- {
- defineProperty(O, P, Desc) {
- if (
- P === "configurable" || P === "enumerable" ||
- P === "writable" || P === "value" ||
- P === "get" || P === "set"
- ) {
- // P is a property descriptor attribute.
- const desc = new PropertyDescriptor(Desc);
- if ("get" in desc || "set" in desc) {
- // Desc is an accessor property descriptor.
- throw new TypeError(
- "Piscēs: Property descriptor attributes must be data properties.",
- );
- } else if ("value" in desc) {
- // Desc has a value.
- desc.value = coercePropretyDescriptorValue(P, desc.value);
- } else {
- // Desc is not an accessor property descriptor and has no
- // value.
- /* do nothing */
- }
- const isAccessorDescriptor = "get" === P || "set" === P ||
- "get" in O || "set" in O;
- const isDataDescriptor = "value" === P || "writable" === P ||
- "value" in O || "writable" in O;
- if (isAccessorDescriptor && isDataDescriptor) {
- // Both accessor and data attributes will be present on O
- // after defining P.
- throw new TypeError(
- "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
- );
- } else {
- // P can be safely defined on O.
- return Reflect.defineProperty(O, P, desc);
- }
- } else {
- // P is not a property descriptor attribute.
- return Reflect.defineProperty(O, P, Desc);
- }
- },
- set(O, P, V, Receiver) {
- const newValue = coercePropertyDescriptorValue(P, V);
- const isAccessorDescriptor = "get" === P || "set" === P ||
- "get" in O || "set" in O;
- const isDataDescriptor = "value" === P || "writable" === P ||
- "value" in O || "writable" in O;
- if (isAccessorDescriptor && isDataDescriptor) {
- // Both accessor and data attributes will be present on O
- // after defining P.
- throw new TypeError(
- "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
- );
- } else {
- // P can be safely defined on O.
- return Reflect.set(O, prop, newValue, Receiver);
- }
- },
- setPrototypeOf(O, V) {
- if (V !== propertyDescriptorPrototype) {
- // V is not the property descriptor prototype.
- return false;
- } else {
- // V is the property descriptor prototype.
- return Reflect.setPrototypeOf(O, V);
- }
- },
- },
- );
+/**
+ * Returns a new object with property keys and values from the provided
+ * iterable value.
+ *
+ * ※ This is effectively an alias for `Object.fromEntries`.
+ */
+export const objectFromEntries = (iterable) => fromEntries(iterable);
- return PropertyDescriptor;
-})();
+/**
+ * Marks the provided object as non·extensible, and returns the
+ * object.
+ *
+ * ※ This is effectively an alias for `Object.preventExtensions`.
+ */
+export const preventExtensions = (O) => objectPreventExtensions(O);
/**
- * Returns a new frozen shallow copy of the enumerable own properties
- * of the provided object, according to the following rules :—
+ * Marks the provided object as non·extensible and marks all its
+ * properties as nonconfigurable, and returns the object.
*
- * - For data properties, create a nonconfigurable, nonwritable
- * property with the same value.
+ * ※ This is effectively an alias for `Object.seal`.
+ */
+export const seal = (O) => objectSeal(O);
+
+/**
+ * Sets the provided property key to the provided value on the provided
+ * object and returns the object.
*
- * - For accessor properties, create a nonconfigurable accessor
- * property with the same getter *and* setter.
+ * ※ This function differs from `Reflect.set` in that it throws if the
+ * setting is unsuccessful.
*
- * 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.
+ * ☡ This function throws if the first argument is not an object.
*/
-export const frozenCopy = (O, constructor = O?.constructor) => {
- if (O == null) {
- // O is null or undefined.
+export const setPropertyValue = (O, P, V, Receiver = O) => {
+ if (type(O) !== "object") {
throw new TypeError(
- "Piscēs: Cannot copy properties of null or undefined.",
+ `Piscēs: Tried to set property but provided value was not an object: ${V}`,
);
- } 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?.[Symbol.species] ?? constructor;
- return Object.preventExtensions(
- Object.create(
- species == null || !("prototype" in species)
- ? null
- : species.prototype,
- Object.fromEntries(
- function* () {
- for (const P of Reflect.ownKeys(O)) {
- const Desc = Object.getOwnPropertyDescriptor(O, P);
- if (Desc.enumerable) {
- // P is an enumerable property.
- yield [
- P,
- "get" in Desc || "set" in 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 */
- }
- }
- }(),
- ),
- ),
+ } else if (!set(O, P, V, Receiver)) {
+ throw new TypeError(
+ `Piscēs: Tried to set property on object but [[Set]] returned false: ${P}`,
);
- }
-};
-
-/** Returns whether the provided value is a constructor. */
-export const isConstructor = ($) => {
- if (!isObject($)) {
- // The provided value is not an object.
- return false;
} else {
- // The provided value is an object.
- try {
- Reflect.construct(
- function () {},
- [],
- $,
- ); // will throw if $ is not a constructor
- return true;
- } catch {
- return false;
- }
+ return O;
}
};
-/** Returns whether the provided value is an object. */
-export const isObject = ($) => {
- return $ !== null &&
- (typeof $ == "function" || typeof $ == "object");
-};
-
-/**
- * Returns whether the provided object inherits from the prototype of
- * the provided function.
- */
-export const ordinaryHasInstance = Function.prototype.call.bind(
- Function.prototype[Symbol.hasInstance],
-);
-
/**
- * Returns the primitive value of the provided object per its
- * `toString` and `valueOf` methods.
- *
- * If the provided hint is "string", then `toString` takes precedence;
- * otherwise, `valueOf` does.
+ * Sets the values of the enumerable own properties of the provided
+ * additional objects on the provided values.
*
- * Throws an error if both of these methods are not callable or do not
- * return a primitive.
+ * ※ This is effectively an alias for `Object.assign`.
*/
-export const ordinaryToPrimitive = (O, hint) => {
- for (
- const name of hint == "string"
- ? ["toString", "valueOf"]
- : ["valueOf", "toString"]
- ) {
- const method = O[name];
- if (typeof method == "function") {
- // Method is callable.
- const result = method.call(O);
- if (!isObject(result)) {
- // Method returns a primitive.
- return result;
- } else {
- // Method returns an object.
- continue;
+export const setPropertyValues = (target, source, ...sources) => {
+ const to = toObject(target);
+ for (let i = -1; i < sources.length; ++i) {
+ // Iterate over each source and set the appropriate property
+ // values.
+ const nextSource = i === -1 ? source : sources[i];
+ if (nextSource != null) {
+ // The current source is not nullish; handle its own properties.
+ const from = toObject(nextSource);
+ const keys = ownKeys(from);
+ for (let k = 0; k < keys.length; ++k) {
+ // Iterate over each key in the current source and set it in
+ // the target object if it is enumerable.
+ const nextKey = keys[k];
+ const desc = reflectGetOwnPropertyDescriptor(from, nextKey);
+ if (desc !== UNDEFINED && desc.enumerable) {
+ // The current key is present and enumerable; set it to its
+ // corresponding value.
+ const propValue = from[nextKey];
+ to[nextKey] = propValue;
+ } else {
+ // The current key is not present or not enumerable.
+ /* do nothing */
+ }
}
} else {
- // Method is not callable.
- continue;
+ // The current source is nullish.
+ /* do nothing */
}
}
- throw new TypeError("Piscēs: Unable to convert object to primitive");
+ return to;
};
/**
- * Returns the provided value converted to a primitive, or throws if
- * no such conversion is possible.
+ * Sets the prototype of the provided object to the provided value and
+ * returns the object.
*
- * The provided preferred type, if specified, should be "string",
- * "number", or "default". If the provided input has a
- * `Symbol.toPrimitive` method, this function will throw rather than
- * calling that method with a preferred type other than one of the
- * above.
+ * ※ This is effectively an alias for `Object.setPrototypeOf`, but it
+ * won’t throw when setting the prototype of a primitive to its current
+ * value.
*/
-export const toPrimitive = ($, preferredType) => {
- if (isObject($)) {
- // The provided value is an object.
- const exoticToPrim = $[Symbol.toPrimitive] ?? undefined;
- if (exoticToPrim !== undefined) {
- // The provided value has an exotic primitive conversion method.
- if (typeof exoticToPrim != "function") {
- // The method is not callable.
- throw new TypeError(
- "Piscēs: Symbol.toPrimitive was neither nullish nor callable.",
- );
- } else {
- // The method is callable.
- const hint = `${preferredType ?? "default"}`;
- if (!["default", "string", "number"].includes(hint)) {
- // An invalid preferred type was specified.
- throw new TypeError(
- `Piscēs: Invalid preferred type: ${preferredType}.`,
- );
- } else {
- // The resulting hint is either default, string, or number.
- return exoticToPrim.call($, hint);
- }
- }
- } else {
- // Use the ordinary primitive conversion function.
- ordinaryToPrimitive($, hint);
- }
+export const setPrototype = (O, proto) => {
+ const obj = toObject(O);
+ if (O === obj) {
+ // The provided value is an object; set its prototype normally.
+ return setPrototypeOf(O, proto);
} else {
- // The provided value is already a primitive.
- return $;
+ // The provided value is not an object; attempt to set the
+ // prototype on a coerced version with extensions prevented, then
+ // return the provided value.
+ //
+ // This will throw if the given prototype does not match the
+ // existing one on the coerced object.
+ setPrototypeOf(objectPreventExtensions(obj), proto);
+ return O;
}
};
/**
- * Returns the property key (symbol or string) corresponding to the
- * provided value.
+ * Returns the provided value converted to an object.
+ *
+ * Existing objects are returned with no modification.
+ *
+ * ☡ This function throws if its argument is null or undefined.
*/
-export const toPropertyKey = ($) => {
- const key = toPrimitive($, "string");
- return typeof key == "symbol" ? key : `${key}`;
+export const toObject = ($) => {
+ if ($ == null) {
+ // The provided value is nullish; this is an error.
+ throw new TypeError(
+ `Piscēs: Cannot convert ${$} into an object.`,
+ );
+ } else {
+ // The provided value is not nullish; coerce it to an object.
+ return object($);
+ }
};