UNPKG

@oazmi/kitchensink

Version:

a collection of personal utility functions

735 lines 32.2 kB
/** utility functions for common object structures and `Object` manipulation. * * @module */ import "./_dnt.polyfills.js"; import type { ConstructorOf, PrototypeOf } from "./typedefs.js"; /** represents a 2d rectangle. compatible with {@link DOMRect}, without its inherited annoying readonly fields. */ export interface Rect { x: number; y: number; width: number; height: number; } /** represents an `ImageData` with optional color space information. */ export interface SimpleImageData extends Omit<ImageData, "colorSpace" | "data"> { data: Uint8ClampedArray | Uint8Array; colorSpace?: PredefinedColorSpace; } /** get an equivalent rectangle where the `height` and `width` are positive. * * @example * ```ts * import { assertEquals } from "jsr:@std/assert" * * const * my_rect: Rect = { x: -20, y: 100, width: 50, height: -30 }, * my_abs_rect = positiveRect(my_rect) * * assertEquals(my_abs_rect, { * x: -20, * y: 70, * width: 50, * height: 30, * }) * ``` */ export declare const positiveRect: (r: Rect) => Rect; /** get the constructor of a class's instance. * * @example * ```ts * import { assertEquals } from "jsr:@std/assert" * * class K { constructor(public value: any) { } } * * const a = new K(1) * const b = new (constructorOf(a))(2) // equivalent to `const b = new K(2)` * * a satisfies K * b satisfies K * assertEquals(a !== b, true) * ``` */ export declare const constructorOf: <T, Args extends any[] = any[]>(class_instance: T) => ConstructorOf<T, Args>; /** use the constructor of a class's instance to construct a new instance. * * this is useful for avoiding pollution of code with `new` keyword along with some wonky placement of braces to make your code work. * * @example * ```ts * class K { * value: number * * constructor(value1: number, value2: number) { * this.value = value1 + value2 * } * } * * const a = new K(1, 1) * const b = constructFrom(a, 2, 2) // equivalent to `const b = new K(2, 2)` * * // vanilla way of constructing `const c = new K(3, 3)` using `a` * const c = new (Object.getPrototypeOf(a).constructor)(3, 3) * * a satisfies K * b satisfies K * c satisfies K * ``` */ export declare const constructFrom: <T, Args extends any[] = any[]>(class_instance: T, ...args: Args) => T; /** get the prototype object of a class. * * this is useful when you want to access bound-methods of an instance of a class, such as the ones declared as: * * ```ts ignore * class X { * methodOfProto() { } * } * ``` * * these bound methods are not available via destructure of an instance, because they then lose their `this` context. * the only functions that can be destructured without losing their `this` context are the ones declared via assignment: * * ```ts ignore * class X { * fn = () => { } * fn2 = function () { } * } * ``` * * @example * ```ts * import { assertEquals } from "jsr:@std/assert" * * const * array_proto = prototypeOfClass(Array<number>), * arr = [1, 2, 3, 4, 5] * * array_proto.push.call(arr, 6) * assertEquals(arr, [1, 2, 3, 4, 5, 6]) * * const slow_push_to_arr = (...values: number[]) => (arr.push(...values)) * // the following declaration is more performant than `slow_push_to_arr`, * // and it also has lower memory footprint. * const fast_push_to_arr = array_proto.push.bind(arr) * * slow_push_to_arr(7, 8) // sloww & bigg * fast_push_to_arr(9, 10) // quicc & smol * assertEquals(arr, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) * ``` */ export declare const prototypeOfClass: <T, Args extends any[] = any[]>(cls: ConstructorOf<T, Args>) => PrototypeOf<typeof cls>; /** configuration options for slicing the prototype chain returned by {@link prototypeChainOfObject}. * * only the following combination of options are supported: * - `start` and `end` * - `start` and `delta` * - `end` and `delta` * * if all three options are defined, then the `delta` option will be ignored. */ export interface PrototypeChainOfObjectConfig { /** the inclusive starting depth of the returned prototype chain. defaults to `0`. * * it can be one of the following: * - a positive integer: slices the full prototype chain starting from the given index. * - a negative integer: slices the full prototype chain starting from the end of the sequence. * - an object: slices the full prototype chain starting from the point where the given object is found (inclusive). * if the object is not found then the `start` option will be treated as `0` (i.e. starting from beginning of the chain array). */ start?: number | Object; /** the exclusive ending depth of the returned prototype chain. defaults to `-1`. * * it can be one of the following: * - a positive integer: slices the full prototype chain ending at the given index. * - a negative integer: slices the full prototype chain from the end of the sequence. * - an object or `null`: slices the full prototype chain ending at the point where the given object is found (exclusive). * if the object is not found then the `end` option will be treated as `-1` (i.e. ending at the end of the chain array). */ end?: number | Object | null; /** the additional depth to traverse on top of either {@link start} or {@link end}. * make sure that you always provide a positive number. * * - when the `start` option is specified, you will be given `delta` number of prototype elements after the starting point. * - when the `end` option is specified, you will be given `delta` number of prototype elements before the ending point. */ delta?: number; } /** get the prototype chain of an object, with optional slicing options. * * @param obj the object whose prototype chain is to be found. * @param config optional configuration for slicing the full prototype chain. * @returns the sliced prototype chain of the given object. * * @example * ```ts * import { assertEquals, assertThrows } from "jsr:@std/assert" * * // aliasing our functions for brevity * const * fn = prototypeChainOfObject, * eq = assertEquals * * class A extends Array { } * class B extends A { } * class C extends B { } * class D extends C { } * * const * a = new A(0), * b = new B(0), * c = new C(0), * d = new D(0) * * eq(fn(d), [D.prototype, C.prototype, B.prototype, A.prototype, Array.prototype, Object.prototype, null]) * eq(fn(b), [B.prototype, A.prototype, Array.prototype, Object.prototype, null]) * * // slicing the prototype chain, starting from index 2 till the end * eq(fn(d, { start: 2 }), [B.prototype, A.prototype, Array.prototype, Object.prototype, null]) * * // slicing using a negative index * eq(fn(d, { start: -2 }), [Object.prototype, null]) * * // slicing using an object * eq(fn(d, { start: B.prototype }), [B.prototype, A.prototype, Array.prototype, Object.prototype, null]) * * // when the slicing object is not found, the start index will be assumed to be `0` (default value) * eq(fn(d, { start: Set.prototype }), [D.prototype, C.prototype, B.prototype, A.prototype, Array.prototype, Object.prototype, null]) * * // slicing between the `start` index (inclusive) and the end index * eq(fn(d, { start: 2, end: 6 }), [B.prototype, A.prototype, Array.prototype, Object.prototype]) * eq(fn(d, { start: 2, end: -1 }), [B.prototype, A.prototype, Array.prototype, Object.prototype]) * eq(fn(d, { start: 2, end: null }), [B.prototype, A.prototype, Array.prototype, Object.prototype]) * * // if the end index is not found, the slicing will occur till the end * eq(fn(d, { end: Set.prototype}), [D.prototype, C.prototype, B.prototype, A.prototype, Array.prototype, Object.prototype, null]) * * // slicing using a `delta` argument will let you define how many elements you wish to: * // - traverse forward from the `start` index * // - traverse backwards from the `end` index * eq(fn(d, { start: 2, delta: 3 }), [B.prototype, A.prototype, Array.prototype]) * eq(fn(d, { end: -2, delta: 2 }), [A.prototype, Array.prototype]) * eq(fn(d, { end: null, delta: 4 }), [B.prototype, A.prototype, Array.prototype, Object.prototype]) * * eq(fn(d, { start: 1, delta: 1 }), fn(c, { start: 0, delta: 1 })) * eq(fn(c, { start: 1, delta: 1 }), fn(b, { start: 0, delta: 1 })) * eq(fn(b, { start: 1, delta: 1 }), fn(a, { start: 0, delta: 1 })) * eq(fn(a, { start: 1, delta: 1 }), fn([], { start: 0, delta: 1 })) * eq(fn([], { start: 1, delta: 1 }), fn({}, { start: 0, delta: 1 })) * * // you may also traverse through the inheritance chain of a class, but it will be a good idea to set your `end` point to `Function.prototype`, * // since all class objects are effectively functions (i.e. their common ancestral prototype if `Function.prototype`). * eq(fn(D, { start: 0, end: Function.prototype }), [C, B, A, Array]) * eq(fn(D), [C, B, A, Array, Function.prototype, Object.prototype, null]) * eq(fn(class {}), [Function.prototype, Object.prototype, null]) * eq(fn(class extends Object {}), [Object, Function.prototype, Object.prototype, null]) * eq(fn(class extends Map {}), [Map, Function.prototype, Object.prototype, null]) * * // you cannot acquire the prototype chain of the `null` object * assertThrows(() => { fn(null) }) * ``` */ export declare function prototypeChainOfObject(obj: any, config?: PrototypeChainOfObjectConfig): object[]; /** get an object's list of **owned** keys (string keys and symbol keys). * * > [!note] * > **owned** keys of an object are not the same as just _any_ key of the object. * > an owned key is one that it **directly** owned by the object, not owned through inheritance, such as the class methods. * > more precisely, in javascript, which ever member of an object that we call a _property_, is an **owned** key. * * if you wish to acquire **all remaining** inherited keys of an object, you will want use {@link getInheritedPropertyKeys}. * * @example * ```ts * import { assertEquals } from "jsr:@std/assert" * * const * symbol_h = Symbol("symbol h"), * symbol_i = Symbol("symbol i"), * symbol_j = Symbol("symbol j") * * class A { * a = { v: 1 } * b = { v: 2 } * c: { v: number } * d() { return { v: 4 } } * e() { return { v: 5 } } * f = () => { return { v: 6 } } * g: () => ({ v: number }) * [symbol_h]() { return { v: 8 } } * [symbol_i] = () => { return { v: 9 } } * * constructor() { * this.c = { v: 3 } * this.g = () => { return { v: 7 } } * } * } * * class B extends A { * override a = { v: 11 } * override e() { return { v: 15 } } * //@ts-ignore: typescript does not permit defining a method over the name of an existing property * override g() { return { v: 17 } } * [symbol_j] = () => { return { v: 20 } } * * constructor() { super() } * } * * const * a = new A(), * b = new B() * * assertEquals(b.a, { v: 11 }) * assertEquals(b.b, { v: 2 }) * assertEquals(b.c, { v: 3 }) * assertEquals(b.d(), { v: 4 }) * assertEquals(b.e(), { v: 15 }) * assertEquals(b.f(), { v: 6 }) * assertEquals(b.g(), { v: 7 }) // notice that the overridden method is not called, and the property is called instead * assertEquals(Object.getPrototypeOf(b).g(), { v: 17 }) * assertEquals(b[symbol_h](), { v: 8 }) * assertEquals(b[symbol_i](), { v: 9 }) * assertEquals(b[symbol_j](), { v: 20 }) * * assertEquals( * new Set(getOwnPropertyKeys(a)), * new Set(["a", "b", "c", "f", "g", symbol_i]), * ) * assertEquals( * new Set(getOwnPropertyKeys(Object.getPrototypeOf(a))), * new Set(["constructor", "d", "e", symbol_h]), * ) * assertEquals( * new Set(getOwnPropertyKeys(b)), * new Set(["a", "b", "c", "f", "g", symbol_i, symbol_j]), * ) * assertEquals( * new Set(getOwnPropertyKeys(Object.getPrototypeOf(b))), * new Set(["constructor", "e", "g"]), * ) * ``` */ export declare const getOwnPropertyKeys: <T extends object>(obj: T) => (keyof T)[]; /** get all **inherited** list of keys (string keys and symbol keys) of an object, up till a certain `depth`. * * directly owned keys will not be returned. * for that, you should use the {@link getOwnPropertyKeys} function. * * the optional `depth` parameter lets you control how deep you'd like to go collecting the inherited keys. * * @param obj the object whose inherited keys are to be listed. * @param depth the inheritance depth until which the function will accumulate keys for. * - if an object `A` is provided as the `depth`, * then all inherited keys up until `A` is reached in the inheritance chain will be collected, but not including the keys of `A`. * - if a number `N` is provided as the `depth`, * then the function will collect keys from `N` number of prototypes up the inheritance chain. * - a depth of `0` would imply no traversal. * - a depth of `1` would only traverse the first direct prototype of `obj` (i.e. `getOwnPropertyKeys(Object.getPrototypeOf(obj))`). * @defaultValue `Object.prototype` * @returns an array of keys that the object has inherited (string or symbolic keys). * * @example * ```ts * import { assertEquals } from "jsr:@std/assert" * * const * symbol_h = Symbol("symbol h"), * symbol_i = Symbol("symbol i"), * symbol_j = Symbol("symbol j") * * class A { * a = { v: 1 } * b = { v: 2 } * c: { v: number } * d() { return { v: 4 } } * e() { return { v: 5 } } * f = () => { return { v: 6 } } * g: () => ({ v: number }) * [symbol_h]() { return { v: 8 } } * [symbol_i] = () => { return { v: 9 } } * * constructor() { * this.c = { v: 3 } * this.g = () => { return { v: 7 } } * } * } * * class B extends A { * override a = { v: 11 } * override e() { return { v: 15 } } * //@ts-ignore: typescript does not permit defining a method over the name of an existing property * override g() { return { v: 17 } } * [symbol_j] = () => { return { v: 20 } } * * constructor() { super() } * } * * const * a = new A(), * b = new B() * * assertEquals( * new Set(getInheritedPropertyKeys(a)), * new Set(["constructor", "d", "e", symbol_h]), * ) * * // below, notice how the inherited keys of `a` equal to its prototype's owned keys. * // this is because methods of instances of `A` are defined on the prototype (i.e. properties of the prototype, rather than the instances'). * assertEquals( * new Set(getInheritedPropertyKeys(a)), * new Set(getOwnPropertyKeys(A.prototype)), * ) * * // also notice that inherited keys of `A.prototype` comes out as empty here, * // even though it does techinally inherit members from its own prototype (which is `Object.prototype`). * // the reason is that by default, we do not go deeper than `Object.prototype` to look for more keys. * // this is because from an end-user's perspective, those keys are not useful. * assertEquals( * getInheritedPropertyKeys(A.prototype), * [], * ) * * assertEquals( * new Set(getInheritedPropertyKeys(b)), * new Set(["constructor", "e", "g", "d", symbol_h]), * ) * assertEquals( * new Set(getInheritedPropertyKeys(b)), * new Set([ * ...getOwnPropertyKeys(B.prototype), * ...getInheritedPropertyKeys(B.prototype), * ]), * ) * assertEquals( * new Set(getInheritedPropertyKeys(B.prototype)), * new Set(["constructor", "e", "d", symbol_h]), * ) * * // testing out various depth * assertEquals( * new Set(getInheritedPropertyKeys(a, 1)), * new Set(getOwnPropertyKeys(A.prototype)), * ) * assertEquals( * new Set(getInheritedPropertyKeys(b, 1)), * new Set(getOwnPropertyKeys(B.prototype)), * ) * assertEquals( * new Set(getInheritedPropertyKeys(b, A.prototype)), * new Set(getOwnPropertyKeys(B.prototype)), * ) * assertEquals( * new Set(getInheritedPropertyKeys(b, 2)), * new Set([ * ...getOwnPropertyKeys(B.prototype), * ...getOwnPropertyKeys(A.prototype), * ]), * ) * // below, we collect all inherited keys of `b`, including those that come from `Object.prototype`, * // which is the base ancestral prototype of all objects. * // to do that, we set the `depth` to `null`, which is the prototype of `Object.prototype`. * // the test below may fail in new versions of javascript, where * assertEquals( * new Set(getInheritedPropertyKeys(b, null)), * new Set([ * "constructor", "e", "g", "d", symbol_h, * "__defineGetter__", "__defineSetter__", "hasOwnProperty", "__lookupGetter__", "__lookupSetter__", * "isPrototypeOf", "propertyIsEnumerable", "toString", "valueOf", "toLocaleString", * ]), * ) * ``` */ export declare const getInheritedPropertyKeys: <T extends object>(obj: T, depth?: (number | Object | null)) => (keyof T)[]; /** get all **owned** getter property keys of an object `obj` (string keys and symbol keys). * * TODO: in the future, consider creating a `getInheritedGetterKeys` function with the same signature as `getInheritedPropertyKeys`. * * > [!note] * > inherited getter property keys will not be included. * > only directly defined getter property keys (aka owned keys) will be listed. */ export declare const getOwnGetterKeys: <T extends object>(obj: T) => (keyof T)[]; /** get all **owned** setter property keys of an object `obj` (string keys and symbol keys). * * TODO: in the future, consider creating a `getInheritedSetterKeys` function with the same signature as `getInheritedPropertyKeys`. * * > [!note] * > inherited setter property keys will not be included. * > only directly defined setter property keys (aka owned keys) will be listed. */ export declare const getOwnSetterKeys: <T extends object>(obj: T) => (keyof T)[]; /** a utility type that mirrors a type `T`, in addition to enclosing the object `T` under the composition property key `P`. * * this utility type is used by the functions {@link mirrorObjectThroughComposition} and {@link subclassThroughComposition}. * * @example * ```ts * const obj_a = { * hello: "world", * goodbye: "earth", * answer() { return 42 }, * } * * const obj_b = { * _super: obj_a, // composing/enclosing `obj_a` * ...obj_a, // mirroring the methods and properties of `obj_a` * } * * obj_b satisfies MirrorComposition<typeof obj_a, "_super"> * ``` */ export type MirrorComposition<T, P extends PropertyKey> = (T & { [composition_key in P]: T; }); /** configuration options for the function {@link mirrorObjectThroughComposition}. */ export interface MirrorObjectThroughCompositionConfig<P extends PropertyKey = string> { /** the key to use to access the composed base object. */ baseKey: P; /** specify whether or not the given object's prototype methods and properties should also be mimicked/mirrored. * * for fine-grained control of _which_ prototypes from the full prototype chain get mimicked, you can provide one of the following configuration objects: * - `Array<Object>`: a manually assembled array of prototype objects, starting with immediate prototype of `obj`, and ending with the last ancestral prototype object. * - {@link PrototypeChainOfObjectConfig | `PrototypeChainOfObjectConfig`}: a description object for a slice of continuous prototype chain, used by the function {@link prototypeChainOfObject}. * - `true`: equivalent to the {@link PrototypeChainOfObjectConfig | prototype slice description} `{ start: 0, end: Object.prototype }`. * - `false`: equivalent to the manually assembled prototype chain `[]` (empty array). * * @defaultValue `true` */ mirrorPrototype?: boolean | PrototypeChainOfObjectConfig | Array<Object>; /** specify additional property keys that should be mirrored. * * this would be needed in situations where not all property keys of the given `obj` are immediately discoverable via `Object.getOwnPropertyNames` and `Object.getOwnPropertySymbols`. * for instance, dynamically assigned property keys, and class-instance bound keys cannot be deduced beforehand. * and so, for such cases, you will need to address those non-discoverable keys in this config option, should you wish for them to be mirrored later on. * * @defaultValue `[]` (empty array) */ propertyKeys?: Iterable<PropertyKey>; /** specify a set of keys that should **not** be mirrored. * * @defaultValue `[]` (empty array) */ ignoreKeys?: Iterable<PropertyKey>; /** specify an optional existing target object on which the provided `obj` gets mirrored onto. * * if none is specified, then a new empty object is used as the target. * if a target object is provided, then the same object will be returned by the {@link mirrorObjectThroughComposition} function. * * @defaultValue `{}` (new empty object) */ target?: object; } /** create a new object that mirrors that functionality of an existing object `obj`, through composition. * * @param obj the object that is to be mimicked and composed. * @param config control how the mirroring composition should behave. * refer to the docs of {@link MirrorObjectThroughCompositionConfig} for more details. * * @example * ```ts * import { assertEquals } from "jsr:@std/assert" * * type TypeofB = Array<number> & { getLength(): number } * type TypeofC = TypeofB & { countZeros(): number } * * const a = Array.prototype * const b = { * getLength(): number { return this.length } * } as TypeofB * const c = { * countZeros(): number { return [...this].filter((value) => (value === 0)).length } * } as TypeofC * * Object.setPrototypeOf(b, a) * Object.setPrototypeOf(c, b) * * // below, we create an object `d` that mirrors the methods and protperties of `c`, but does not actually inherit it. * const d = mirrorObjectThroughComposition(c, { * baseKey: "_super", * propertyKeys: ["length"], * }) * * // notice that `d` does not inherit `c` as its prototype, despite being able to utilize its methods and properties. * assertEquals(Object.getPrototypeOf(d), Object.prototype, "`d` does not inherit `c`") * * d.push(0, 0, 0, 0, 1) * assertEquals([...d], [0, 0, 0, 0, 1], "`d` also mirrors symbolic keys (such as iterators)") * assertEquals([...c], [0, 0, 0, 0, 1], "mutations made to `d` are applied to `c`") * assertEquals(d._super, c, "`c` is accessible via the `baseKey` (\"_super\") property") * assertEquals(d.length, 5) * assertEquals(c.length, 5) * assertEquals(d.getLength(), 5) * assertEquals(d.countZeros(), 4) * d.splice(0, 3) * assertEquals(d.countZeros(), 1) * assertEquals(c.countZeros(), 1) * * // you may even hot-swap the object that is being composed inside of `d`. * const e = [1, 2, 3, 4, 5, 6, 7, 8, 9] * Object.setPrototypeOf(e, c) * d._super = e as TypeofC * assertEquals(d.length, 9) * assertEquals(d.getLength(), 9) * assertEquals(d.countZeros(), 0) * * // it is also possible for you to provide an existing `target` object onto which the mirroring should occur. * // moreover, you can ignore specific keys which you would like not to be mirrored using the `ignoreKeys` option. * class F { * methodF() { return "press f for your fallen comrades" } * // to stop typescript from complaining, we declare the instance methods and properties that will be mirrored. * declare _super: typeof c * declare length: number * declare getLength: undefined * declare countZeros: () => number * } * mirrorObjectThroughComposition(c, { * target: F.prototype, * baseKey: "_super", * propertyKeys: ["length"], * ignoreKeys: ["getLength"], * }) * const f = new F() * assertEquals(f instanceof F, true) * assertEquals(f._super, c) * assertEquals(f.length, 2) * assertEquals(f.countZeros(), 1) * assertEquals(f.getLength, undefined) // the `getLength` is not mirrored because it was present in the `ignoreKeys` option * assertEquals(f.methodF(), "press f for your fallen comrades") * ``` */ export declare const mirrorObjectThroughComposition: <T, P extends PropertyKey>(obj: T, config: MirrorObjectThroughCompositionConfig<P>) => MirrorComposition<T, P>; /** configuration options for the function {@link subclassThroughComposition}. * * this configuration effectively specifies the {@link MirrorObjectThroughCompositionConfig} configuration for the `class` and its `instance`s. * * by default, the following subconfiguration is used for both `class` and `instance`: * `{ baseKey: "_super", mirrorPrototype: true, propertyKeys: [] }` * * - if your class's instances contain properties that are not defined in the prototype (i.e. dynamically assigned properties), * then you will need to define them in {@link MirrorObjectThroughCompositionConfig.propertyKeys | `instance.propertyKeys`}. * in a similar way, if your class has dynamically assigned static properties, then you'll need to define them in * {@link MirrorObjectThroughCompositionConfig.propertyKeys | `class.propertyKeys`} for them to get mirrored. * * - the `baseKey` option lets you pick the key that is used for accessing the composed base object from the derived subclass. * assuming that `class.baseKey = "_super"` and `instance.baseKey = "_super"`, then the generated "subclass" will be able to access the enclosed class in the following way: * - inside a static class method: `this._super satisfies typeof cls` * - inside an instance method: `this._super satisfies InstanceType<typeof cls>` */ export interface SubclassThroughCompositionConfig<CLASS_KEY extends PropertyKey = string, INSTANCE_KEY extends PropertyKey = string> { class?: Partial<Omit<MirrorObjectThroughCompositionConfig<CLASS_KEY>, "target">>; instance?: Partial<Omit<MirrorObjectThroughCompositionConfig<INSTANCE_KEY>, "target">>; } /** a utility type for the return type of {@link subclassThroughComposition}. */ export type MirrorCompositionClass<CLS extends ConstructorOf<any, any>, CLASS_KEY extends PropertyKey, INSTANCE_KEY extends PropertyKey> = CLS extends (new (...args: any[]) => infer T) ? MirrorComposition<CLS, CLASS_KEY> & { new (...args: any[]): MirrorComposition<T, INSTANCE_KEY>; } : MirrorComposition<CLS, CLASS_KEY>; /** create a subclass of the given `cls` class through composition rather than ES6 inheritance (aka `extends`). * the composed instance resides under the property `this._super`, and all instance methods of the super class are copied/mirrored in the output class. * * TODO: provide a motivation/justification when compositional-mirroring subclass might be necessary to use instead of ES6 `extends` inheritance. * * @param cls the class to subclass through composition. * @param property_keys specify additional property keys that exist within the instance of the class that need to be mirrored. * @returns a class whose instances fully mirror the provided `cls` class's instance, without actually inheriting it. * * @example * ```ts * import { assertEquals } from "jsr:@std/assert" * * class A<T> extends Array<T> { constructor(quantity: number) { super(quantity) } } * class B<T> extends subclassThroughComposition(A, { * class: {}, * instance: { propertyKeys: ["length"] }, * })<T> { } * * // `B` mirrors the constructor of `A`, as well as provide an additional `"_super"` property to access the enclosed (class which is `A`) * B satisfies { new(amount: number): B<any> } * B._super satisfies typeof A * // the following cannot be satisfied due to `A`'s restrictive constructor: `B satisfies { new(item1: string, item2: string, item3: string): B<string> }` * // even though `A`'s super class (`Array`) does permit that kind of constructor signature: * Array satisfies { new(item1: string, item2: string, item3: string): Array<string> } * * const my_array = new B<number>(5) * * // `my_array` encloses an instance of its "subclass" (which is an instance of class `A`) under the `"_super"` key. * assertEquals(my_array instanceof B, true) * assertEquals(my_array._super instanceof A, true) * my_array._super satisfies A<unknown> // this should have been narrowed to `satisfies A<number>`, but typing class extensions with generics is nearly impossible * * // `my_array` mirrors the properties and methods of the instances of the "subclass" that it encloses. * my_array.fill(1) * my_array.push(2) * my_array.push(3) * assertEquals(my_array.at(-1), 3) * assertEquals(my_array._super.at(-1), 3) * assertEquals(my_array.length, 7) * assertEquals(my_array._super.length, 7) * * // `my_array` mirrors the symbolic properties and methods as well. * assertEquals([...my_array], [1, 1, 1, 1, 1, 2, 3]) // the `Symbol.iterator` method is mirrored successfully * * // `my_array` when a method that returns `new this.constructor(...)` (such as `Array.prototype.splice`), then an instance of * // the enclosed subclass `A` will be created instead of `B` (had we had a regular inheritance via `class B extends A { }`). * const * splice_of_my_array = my_array.splice(0, 4, 0, 0), * slice_of_my_array = my_array.slice(3, 4) * assertEquals(splice_of_my_array instanceof A, true) * assertEquals(slice_of_my_array instanceof A, true) * assertEquals([...splice_of_my_array], [1, 1, 1, 1]) * assertEquals([...slice_of_my_array], [2]) * assertEquals([...my_array], [0, 0, 1, 2, 3]) * assertEquals([...my_array._super], [0, 0, 1, 2, 3]) * * // the class `B` also mirrors static properties and static methods of `A` (and its inherited ones). * // this means that any static method that creates a new instance via `new this(...)` will create an instance of `A` instead of `B`. * const * new_array_1 = B.from(["hello", "world"]), * new_array_2 = B.of("goodbye", "world") * assertEquals(new_array_1 instanceof A, true) * assertEquals(new_array_2 instanceof A, true) * assertEquals(new_array_1, A.from(["hello", "world"])) * assertEquals(new_array_2, A.of("goodbye", "world")) * ``` */ export declare const subclassThroughComposition: <CLS extends ConstructorOf<any, any>, CLASS_KEY extends PropertyKey = "_super", INSTANCE_KEY extends PropertyKey = "_super">(cls: CLS, config?: SubclassThroughCompositionConfig<CLASS_KEY, INSTANCE_KEY>) => MirrorCompositionClass<CLS, CLASS_KEY, INSTANCE_KEY>; /** monkey patch the prototype of a class. * * TODO: give usage examples and situations where this will be useful. */ export declare const monkeyPatchPrototypeOfClass: <T, Args extends any[] = any[]>(cls: ConstructorOf<T, Args>, key: keyof T, value: T[typeof key]) => void; /** type definition of a primitive javascript object. */ export type PrimitiveObject = string | number | bigint | boolean | symbol | undefined; /** type definition of a non-primitive javascript object. */ export type ComplexObject = object | Function; /** check if `obj` is either an object or function. */ export declare const isComplex: (obj: any) => obj is ComplexObject; /** check if `obj` is neither an object nor a function. */ export declare const isPrimitive: (obj: any) => obj is PrimitiveObject; /** check if `obj` is a `function`. * * TODO: consider if it would be a good idea to include a generic parameter for the function's signature. * i.e.: `<FN extends Function = Function>(obj: any): obj is FN` */ export declare const isFunction: (obj: any) => obj is Function; /** check if `obj` is an `Object`. */ export declare const isObject: <T extends object = object>(obj: any) => obj is T; /** check if `obj` is an `Array`. */ export declare const isArray: (<T = any>(obj: any) => obj is Array<T>); /** check if `obj` is a `Record`, which is any non-nullable object that isn't an array; * kind of like a dictionary. */ export declare const isRecord: <R extends Record<any, any> = any>(obj: any) => obj is R; /** check if `obj` is a `string`. */ export declare const isString: (obj: any) => obj is string; /** check if `obj` is a `number`. */ export declare const isNumber: (obj: any) => obj is number; /** check if `obj` is a `bigint`. */ export declare const isBigint: (obj: any) => obj is bigint; /** check if `obj` is either a `number` or a `bigint`. */ export declare const isNumeric: (obj: any) => obj is (number | bigint); /** check if `obj` is `boolean`. */ export declare const isBoolean: (obj: any) => obj is boolean; /** check if `obj` is a `symbol`. */ export declare const isSymbol: (obj: any) => obj is symbol; //# sourceMappingURL=struct.d.ts.map