@oazmi/superbuild 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/_dnt.shims.d.ts +2 -0
- package/esm/_dnt.shims.d.ts.map +1 -0
- package/esm/_dnt.shims.js +57 -0
- package/esm/deps/jsr.io/@oazmi/esbuild-types/0.28.0/src/mod.d.ts +598 -0
- package/esm/deps/jsr.io/@oazmi/esbuild-types/0.28.0/src/mod.d.ts.map +1 -0
- package/esm/deps/jsr.io/@oazmi/esbuild-types/0.28.0/src/mod.js +1 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/alias.d.ts +617 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/alias.d.ts.map +1 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/alias.js +671 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/array1d.d.ts +487 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/array1d.d.ts.map +1 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/array1d.js +536 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/array2d.d.ts +266 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/array2d.d.ts.map +1 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/array2d.js +318 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/binder.d.ts +368 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/binder.d.ts.map +1 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/binder.js +380 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/crossenv.d.ts +711 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/crossenv.d.ts.map +1 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/crossenv.js +1173 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/cryptoman.d.ts +171 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/cryptoman.d.ts.map +1 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/cryptoman.js +308 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/deps.d.ts +10 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/deps.d.ts.map +1 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/deps.js +10 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/eightpack.d.ts +168 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/eightpack.d.ts.map +1 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/eightpack.js +236 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/eightpack_varint.d.ts +90 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/eightpack_varint.d.ts.map +1 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/eightpack_varint.js +237 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/numericarray.d.ts +213 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/numericarray.d.ts.map +1 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/numericarray.js +363 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/numericmethods.d.ts +233 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/numericmethods.d.ts.map +1 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/numericmethods.js +256 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/pathman.d.ts +1436 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/pathman.d.ts.map +1 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/pathman.js +1730 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/promiseman.d.ts +647 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/promiseman.d.ts.map +1 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/promiseman.js +762 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/stringman.d.ts +538 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/stringman.d.ts.map +1 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/stringman.js +626 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/struct.d.ts +734 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/struct.d.ts.map +1 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/struct.js +764 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/typedbuffer.d.ts +137 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/typedbuffer.d.ts.map +1 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/typedbuffer.js +234 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/typedefs.d.ts +391 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/typedefs.d.ts.map +1 -0
- package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/typedefs.js +8 -0
- package/esm/deps.d.ts +56 -0
- package/esm/deps.d.ts.map +1 -0
- package/esm/deps.js +37 -0
- package/esm/esbuild/metafile.d.ts +90 -0
- package/esm/esbuild/metafile.d.ts.map +1 -0
- package/esm/esbuild/metafile.js +275 -0
- package/esm/esbuild/native.d.ts +95 -0
- package/esm/esbuild/native.d.ts.map +1 -0
- package/esm/esbuild/native.js +127 -0
- package/esm/esbuild/outputfile.d.ts +86 -0
- package/esm/esbuild/outputfile.d.ts.map +1 -0
- package/esm/esbuild/outputfile.js +221 -0
- package/esm/esbuild/strongtypes.d.ts +90 -0
- package/esm/esbuild/strongtypes.d.ts.map +1 -0
- package/esm/esbuild/strongtypes.js +8 -0
- package/esm/esbuild/typedefs.d.ts +121 -0
- package/esm/esbuild/typedefs.d.ts.map +1 -0
- package/esm/esbuild/typedefs.js +55 -0
- package/esm/esbuild/weaktypes.d.ts +46 -0
- package/esm/esbuild/weaktypes.d.ts.map +1 -0
- package/esm/esbuild/weaktypes.js +8 -0
- package/esm/funcdefs.d.ts +89 -0
- package/esm/funcdefs.d.ts.map +1 -0
- package/esm/funcdefs.js +145 -0
- package/esm/mod.d.ts +4 -0
- package/esm/mod.d.ts.map +1 -0
- package/esm/mod.js +2 -0
- package/esm/package.json +3 -0
- package/esm/plugins/emissions_driver.d.ts +33 -0
- package/esm/plugins/emissions_driver.d.ts.map +1 -0
- package/esm/plugins/emissions_driver.js +260 -0
- package/esm/plugins/long_build.d.ts +138 -0
- package/esm/plugins/long_build.d.ts.map +1 -0
- package/esm/plugins/long_build.js +288 -0
- package/esm/plugins/native_replica.d.ts +48 -0
- package/esm/plugins/native_replica.d.ts.map +1 -0
- package/esm/plugins/native_replica.js +122 -0
- package/esm/super/build.d.ts +53 -0
- package/esm/super/build.d.ts.map +1 -0
- package/esm/super/build.js +39 -0
- package/esm/super/build_context.d.ts +83 -0
- package/esm/super/build_context.d.ts.map +1 -0
- package/esm/super/build_context.js +124 -0
- package/esm/super/mod.d.ts +12 -0
- package/esm/super/mod.d.ts.map +1 -0
- package/esm/super/mod.js +9 -0
- package/esm/super/plugin.d.ts +23 -0
- package/esm/super/plugin.d.ts.map +1 -0
- package/esm/super/plugin.js +31 -0
- package/esm/super/plugin_build.d.ts +38 -0
- package/esm/super/plugin_build.d.ts.map +1 -0
- package/esm/super/plugin_build.js +198 -0
- package/esm/super/typedefs.d.ts +323 -0
- package/esm/super/typedefs.d.ts.map +1 -0
- package/esm/super/typedefs.js +10 -0
- package/esm/typedefs.d.ts +21 -0
- package/esm/typedefs.d.ts.map +1 -0
- package/esm/typedefs.js +5 -0
- package/license.md +37 -0
- package/package.json +88 -0
- package/readme.md +181 -0
|
@@ -0,0 +1,764 @@
|
|
|
1
|
+
/** utility functions for common object structures and `Object` manipulation.
|
|
2
|
+
*
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
import { array_isArray, object_defineProperty, object_getOwnPropertyDescriptor, object_getOwnPropertyNames, object_getOwnPropertySymbols, object_getPrototypeOf } from "./alias.js";
|
|
6
|
+
import { resolveRange } from "./array1d.js";
|
|
7
|
+
import { max } from "./numericmethods.js";
|
|
8
|
+
/** get an equivalent rectangle where the `height` and `width` are positive.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { assertEquals } from "jsr:@std/assert"
|
|
13
|
+
*
|
|
14
|
+
* const
|
|
15
|
+
* my_rect: Rect = { x: -20, y: 100, width: 50, height: -30 },
|
|
16
|
+
* my_abs_rect = positiveRect(my_rect)
|
|
17
|
+
*
|
|
18
|
+
* assertEquals(my_abs_rect, {
|
|
19
|
+
* x: -20,
|
|
20
|
+
* y: 70,
|
|
21
|
+
* width: 50,
|
|
22
|
+
* height: 30,
|
|
23
|
+
* })
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export const positiveRect = (r) => {
|
|
27
|
+
let { x, y, width, height } = r;
|
|
28
|
+
if (width < 0) {
|
|
29
|
+
width *= -1; // width is now positive
|
|
30
|
+
x -= width; // x has been moved further to the left
|
|
31
|
+
}
|
|
32
|
+
if (height < 0) {
|
|
33
|
+
height *= -1; // height is now positive
|
|
34
|
+
y -= height; // y has been moved further to the top
|
|
35
|
+
}
|
|
36
|
+
return { x, y, width, height };
|
|
37
|
+
};
|
|
38
|
+
/** get the constructor of a class's instance.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* import { assertEquals } from "jsr:@std/assert"
|
|
43
|
+
*
|
|
44
|
+
* class K { constructor(public value: any) { } }
|
|
45
|
+
*
|
|
46
|
+
* const a = new K(1)
|
|
47
|
+
* const b = new (constructorOf(a))(2) // equivalent to `const b = new K(2)`
|
|
48
|
+
*
|
|
49
|
+
* a satisfies K
|
|
50
|
+
* b satisfies K
|
|
51
|
+
* assertEquals(a !== b, true)
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export const constructorOf = (class_instance) => {
|
|
55
|
+
return object_getPrototypeOf(class_instance).constructor;
|
|
56
|
+
};
|
|
57
|
+
/** use the constructor of a class's instance to construct a new instance.
|
|
58
|
+
*
|
|
59
|
+
* this is useful for avoiding pollution of code with `new` keyword along with some wonky placement of braces to make your code work.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* class K {
|
|
64
|
+
* value: number
|
|
65
|
+
*
|
|
66
|
+
* constructor(value1: number, value2: number) {
|
|
67
|
+
* this.value = value1 + value2
|
|
68
|
+
* }
|
|
69
|
+
* }
|
|
70
|
+
*
|
|
71
|
+
* const a = new K(1, 1)
|
|
72
|
+
* const b = constructFrom(a, 2, 2) // equivalent to `const b = new K(2, 2)`
|
|
73
|
+
*
|
|
74
|
+
* // vanilla way of constructing `const c = new K(3, 3)` using `a`
|
|
75
|
+
* const c = new (Object.getPrototypeOf(a).constructor)(3, 3)
|
|
76
|
+
*
|
|
77
|
+
* a satisfies K
|
|
78
|
+
* b satisfies K
|
|
79
|
+
* c satisfies K
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export const constructFrom = (class_instance, ...args) => {
|
|
83
|
+
return new (constructorOf(class_instance))(...args);
|
|
84
|
+
};
|
|
85
|
+
/** get the prototype object of a class.
|
|
86
|
+
*
|
|
87
|
+
* this is useful when you want to access bound-methods of an instance of a class, such as the ones declared as:
|
|
88
|
+
*
|
|
89
|
+
* ```ts ignore
|
|
90
|
+
* class X {
|
|
91
|
+
* methodOfProto() { }
|
|
92
|
+
* }
|
|
93
|
+
* ```
|
|
94
|
+
*
|
|
95
|
+
* these bound methods are not available via destructure of an instance, because they then lose their `this` context.
|
|
96
|
+
* the only functions that can be destructured without losing their `this` context are the ones declared via assignment:
|
|
97
|
+
*
|
|
98
|
+
* ```ts ignore
|
|
99
|
+
* class X {
|
|
100
|
+
* fn = () => { }
|
|
101
|
+
* fn2 = function () { }
|
|
102
|
+
* }
|
|
103
|
+
* ```
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* import { assertEquals } from "jsr:@std/assert"
|
|
108
|
+
*
|
|
109
|
+
* const
|
|
110
|
+
* array_proto = prototypeOfClass(Array<number>),
|
|
111
|
+
* arr = [1, 2, 3, 4, 5]
|
|
112
|
+
*
|
|
113
|
+
* array_proto.push.call(arr, 6)
|
|
114
|
+
* assertEquals(arr, [1, 2, 3, 4, 5, 6])
|
|
115
|
+
*
|
|
116
|
+
* const slow_push_to_arr = (...values: number[]) => (arr.push(...values))
|
|
117
|
+
* // the following declaration is more performant than `slow_push_to_arr`,
|
|
118
|
+
* // and it also has lower memory footprint.
|
|
119
|
+
* const fast_push_to_arr = array_proto.push.bind(arr)
|
|
120
|
+
*
|
|
121
|
+
* slow_push_to_arr(7, 8) // sloww & bigg
|
|
122
|
+
* fast_push_to_arr(9, 10) // quicc & smol
|
|
123
|
+
* assertEquals(arr, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
export const prototypeOfClass = (cls) => {
|
|
127
|
+
return cls.prototype;
|
|
128
|
+
};
|
|
129
|
+
/** get the prototype chain of an object, with optional slicing options.
|
|
130
|
+
*
|
|
131
|
+
* @param obj the object whose prototype chain is to be found.
|
|
132
|
+
* @param config optional configuration for slicing the full prototype chain.
|
|
133
|
+
* @returns the sliced prototype chain of the given object.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* import { assertEquals, assertThrows } from "jsr:@std/assert"
|
|
138
|
+
*
|
|
139
|
+
* // aliasing our functions for brevity
|
|
140
|
+
* const
|
|
141
|
+
* fn = prototypeChainOfObject,
|
|
142
|
+
* eq = assertEquals
|
|
143
|
+
*
|
|
144
|
+
* class A extends Array { }
|
|
145
|
+
* class B extends A { }
|
|
146
|
+
* class C extends B { }
|
|
147
|
+
* class D extends C { }
|
|
148
|
+
*
|
|
149
|
+
* const
|
|
150
|
+
* a = new A(0),
|
|
151
|
+
* b = new B(0),
|
|
152
|
+
* c = new C(0),
|
|
153
|
+
* d = new D(0)
|
|
154
|
+
*
|
|
155
|
+
* eq(fn(d), [D.prototype, C.prototype, B.prototype, A.prototype, Array.prototype, Object.prototype, null])
|
|
156
|
+
* eq(fn(b), [B.prototype, A.prototype, Array.prototype, Object.prototype, null])
|
|
157
|
+
*
|
|
158
|
+
* // slicing the prototype chain, starting from index 2 till the end
|
|
159
|
+
* eq(fn(d, { start: 2 }), [B.prototype, A.prototype, Array.prototype, Object.prototype, null])
|
|
160
|
+
*
|
|
161
|
+
* // slicing using a negative index
|
|
162
|
+
* eq(fn(d, { start: -2 }), [Object.prototype, null])
|
|
163
|
+
*
|
|
164
|
+
* // slicing using an object
|
|
165
|
+
* eq(fn(d, { start: B.prototype }), [B.prototype, A.prototype, Array.prototype, Object.prototype, null])
|
|
166
|
+
*
|
|
167
|
+
* // when the slicing object is not found, the start index will be assumed to be `0` (default value)
|
|
168
|
+
* eq(fn(d, { start: Set.prototype }), [D.prototype, C.prototype, B.prototype, A.prototype, Array.prototype, Object.prototype, null])
|
|
169
|
+
*
|
|
170
|
+
* // slicing between the `start` index (inclusive) and the end index
|
|
171
|
+
* eq(fn(d, { start: 2, end: 6 }), [B.prototype, A.prototype, Array.prototype, Object.prototype])
|
|
172
|
+
* eq(fn(d, { start: 2, end: -1 }), [B.prototype, A.prototype, Array.prototype, Object.prototype])
|
|
173
|
+
* eq(fn(d, { start: 2, end: null }), [B.prototype, A.prototype, Array.prototype, Object.prototype])
|
|
174
|
+
*
|
|
175
|
+
* // if the end index is not found, the slicing will occur till the end
|
|
176
|
+
* eq(fn(d, { end: Set.prototype}), [D.prototype, C.prototype, B.prototype, A.prototype, Array.prototype, Object.prototype, null])
|
|
177
|
+
*
|
|
178
|
+
* // slicing using a `delta` argument will let you define how many elements you wish to:
|
|
179
|
+
* // - traverse forward from the `start` index
|
|
180
|
+
* // - traverse backwards from the `end` index
|
|
181
|
+
* eq(fn(d, { start: 2, delta: 3 }), [B.prototype, A.prototype, Array.prototype])
|
|
182
|
+
* eq(fn(d, { end: -2, delta: 2 }), [A.prototype, Array.prototype])
|
|
183
|
+
* eq(fn(d, { end: null, delta: 4 }), [B.prototype, A.prototype, Array.prototype, Object.prototype])
|
|
184
|
+
*
|
|
185
|
+
* eq(fn(d, { start: 1, delta: 1 }), fn(c, { start: 0, delta: 1 }))
|
|
186
|
+
* eq(fn(c, { start: 1, delta: 1 }), fn(b, { start: 0, delta: 1 }))
|
|
187
|
+
* eq(fn(b, { start: 1, delta: 1 }), fn(a, { start: 0, delta: 1 }))
|
|
188
|
+
* eq(fn(a, { start: 1, delta: 1 }), fn([], { start: 0, delta: 1 }))
|
|
189
|
+
* eq(fn([], { start: 1, delta: 1 }), fn({}, { start: 0, delta: 1 }))
|
|
190
|
+
*
|
|
191
|
+
* // 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`,
|
|
192
|
+
* // since all class objects are effectively functions (i.e. their common ancestral prototype if `Function.prototype`).
|
|
193
|
+
* eq(fn(D, { start: 0, end: Function.prototype }), [C, B, A, Array])
|
|
194
|
+
* eq(fn(D), [C, B, A, Array, Function.prototype, Object.prototype, null])
|
|
195
|
+
* eq(fn(class {}), [Function.prototype, Object.prototype, null])
|
|
196
|
+
* eq(fn(class extends Object {}), [Object, Function.prototype, Object.prototype, null])
|
|
197
|
+
* eq(fn(class extends Map {}), [Map, Function.prototype, Object.prototype, null])
|
|
198
|
+
*
|
|
199
|
+
* // you cannot acquire the prototype chain of the `null` object
|
|
200
|
+
* assertThrows(() => { fn(null) })
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
export function prototypeChainOfObject(obj, config = {}) {
|
|
204
|
+
let { start, end, delta } = config;
|
|
205
|
+
const full_chain = [];
|
|
206
|
+
// collecting the full prototype chain, until `obj` becomes `null`, which is always the last thing in a prototype chain.
|
|
207
|
+
while ((obj = object_getPrototypeOf(obj))) {
|
|
208
|
+
full_chain.push(obj);
|
|
209
|
+
}
|
|
210
|
+
full_chain.push(null);
|
|
211
|
+
const full_chain_length = full_chain.length;
|
|
212
|
+
// NOTE: we not only check if `start` and `end` are of type `"object"`, but also if they're of type `"function"`.
|
|
213
|
+
// this is because `typeof Function.prototype === "function"`, and not `"object"`.
|
|
214
|
+
// and as it happens to be the case, I do use `Function.prototype` as the end point in my {@link subclassThroughComposition} function.
|
|
215
|
+
if (isComplex(start)) {
|
|
216
|
+
start = max(0, full_chain.indexOf(start));
|
|
217
|
+
}
|
|
218
|
+
if (isComplex(end)) {
|
|
219
|
+
const end_index = full_chain.indexOf(end);
|
|
220
|
+
end = end_index < 0 ? undefined : end_index;
|
|
221
|
+
}
|
|
222
|
+
// both `start` and `end` are either numbers or undefined now.
|
|
223
|
+
if ((delta !== undefined) && ((start ?? end) !== undefined)) {
|
|
224
|
+
// in here, `delta` is defined and one of either `start` or `end` is undefined
|
|
225
|
+
if (start !== undefined) {
|
|
226
|
+
end = start + delta;
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
start = end - delta;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// now that any potential `delta` has been resolved into a `start` and `end`,
|
|
233
|
+
// we only need to resolve possible negative indexes and then slice the prototype chain
|
|
234
|
+
[start, end] = resolveRange(start, end, full_chain_length);
|
|
235
|
+
return full_chain.slice(start, end);
|
|
236
|
+
}
|
|
237
|
+
/** get an object's list of **owned** keys (string keys and symbol keys).
|
|
238
|
+
*
|
|
239
|
+
* > [!note]
|
|
240
|
+
* > **owned** keys of an object are not the same as just _any_ key of the object.
|
|
241
|
+
* > an owned key is one that it **directly** owned by the object, not owned through inheritance, such as the class methods.
|
|
242
|
+
* > more precisely, in javascript, which ever member of an object that we call a _property_, is an **owned** key.
|
|
243
|
+
*
|
|
244
|
+
* if you wish to acquire **all remaining** inherited keys of an object, you will want use {@link getInheritedPropertyKeys}.
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* ```ts
|
|
248
|
+
* import { assertEquals } from "jsr:@std/assert"
|
|
249
|
+
*
|
|
250
|
+
* const
|
|
251
|
+
* symbol_h = Symbol("symbol h"),
|
|
252
|
+
* symbol_i = Symbol("symbol i"),
|
|
253
|
+
* symbol_j = Symbol("symbol j")
|
|
254
|
+
*
|
|
255
|
+
* class A {
|
|
256
|
+
* a = { v: 1 }
|
|
257
|
+
* b = { v: 2 }
|
|
258
|
+
* c: { v: number }
|
|
259
|
+
* d() { return { v: 4 } }
|
|
260
|
+
* e() { return { v: 5 } }
|
|
261
|
+
* f = () => { return { v: 6 } }
|
|
262
|
+
* g: () => ({ v: number })
|
|
263
|
+
* [symbol_h]() { return { v: 8 } }
|
|
264
|
+
* [symbol_i] = () => { return { v: 9 } }
|
|
265
|
+
*
|
|
266
|
+
* constructor() {
|
|
267
|
+
* this.c = { v: 3 }
|
|
268
|
+
* this.g = () => { return { v: 7 } }
|
|
269
|
+
* }
|
|
270
|
+
* }
|
|
271
|
+
*
|
|
272
|
+
* class B extends A {
|
|
273
|
+
* override a = { v: 11 }
|
|
274
|
+
* override e() { return { v: 15 } }
|
|
275
|
+
* //@ts-ignore: typescript does not permit defining a method over the name of an existing property
|
|
276
|
+
* override g() { return { v: 17 } }
|
|
277
|
+
* [symbol_j] = () => { return { v: 20 } }
|
|
278
|
+
*
|
|
279
|
+
* constructor() { super() }
|
|
280
|
+
* }
|
|
281
|
+
*
|
|
282
|
+
* const
|
|
283
|
+
* a = new A(),
|
|
284
|
+
* b = new B()
|
|
285
|
+
*
|
|
286
|
+
* assertEquals(b.a, { v: 11 })
|
|
287
|
+
* assertEquals(b.b, { v: 2 })
|
|
288
|
+
* assertEquals(b.c, { v: 3 })
|
|
289
|
+
* assertEquals(b.d(), { v: 4 })
|
|
290
|
+
* assertEquals(b.e(), { v: 15 })
|
|
291
|
+
* assertEquals(b.f(), { v: 6 })
|
|
292
|
+
* assertEquals(b.g(), { v: 7 }) // notice that the overridden method is not called, and the property is called instead
|
|
293
|
+
* assertEquals(Object.getPrototypeOf(b).g(), { v: 17 })
|
|
294
|
+
* assertEquals(b[symbol_h](), { v: 8 })
|
|
295
|
+
* assertEquals(b[symbol_i](), { v: 9 })
|
|
296
|
+
* assertEquals(b[symbol_j](), { v: 20 })
|
|
297
|
+
*
|
|
298
|
+
* assertEquals(
|
|
299
|
+
* new Set(getOwnPropertyKeys(a)),
|
|
300
|
+
* new Set(["a", "b", "c", "f", "g", symbol_i]),
|
|
301
|
+
* )
|
|
302
|
+
* assertEquals(
|
|
303
|
+
* new Set(getOwnPropertyKeys(Object.getPrototypeOf(a))),
|
|
304
|
+
* new Set(["constructor", "d", "e", symbol_h]),
|
|
305
|
+
* )
|
|
306
|
+
* assertEquals(
|
|
307
|
+
* new Set(getOwnPropertyKeys(b)),
|
|
308
|
+
* new Set(["a", "b", "c", "f", "g", symbol_i, symbol_j]),
|
|
309
|
+
* )
|
|
310
|
+
* assertEquals(
|
|
311
|
+
* new Set(getOwnPropertyKeys(Object.getPrototypeOf(b))),
|
|
312
|
+
* new Set(["constructor", "e", "g"]),
|
|
313
|
+
* )
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
export const getOwnPropertyKeys = (obj) => {
|
|
317
|
+
return [
|
|
318
|
+
...object_getOwnPropertyNames(obj),
|
|
319
|
+
...object_getOwnPropertySymbols(obj),
|
|
320
|
+
];
|
|
321
|
+
};
|
|
322
|
+
/** get all **inherited** list of keys (string keys and symbol keys) of an object, up till a certain `depth`.
|
|
323
|
+
*
|
|
324
|
+
* directly owned keys will not be returned.
|
|
325
|
+
* for that, you should use the {@link getOwnPropertyKeys} function.
|
|
326
|
+
*
|
|
327
|
+
* the optional `depth` parameter lets you control how deep you'd like to go collecting the inherited keys.
|
|
328
|
+
*
|
|
329
|
+
* @param obj the object whose inherited keys are to be listed.
|
|
330
|
+
* @param depth the inheritance depth until which the function will accumulate keys for.
|
|
331
|
+
* - if an object `A` is provided as the `depth`,
|
|
332
|
+
* then all inherited keys up until `A` is reached in the inheritance chain will be collected, but not including the keys of `A`.
|
|
333
|
+
* - if a number `N` is provided as the `depth`,
|
|
334
|
+
* then the function will collect keys from `N` number of prototypes up the inheritance chain.
|
|
335
|
+
* - a depth of `0` would imply no traversal.
|
|
336
|
+
* - a depth of `1` would only traverse the first direct prototype of `obj` (i.e. `getOwnPropertyKeys(Object.getPrototypeOf(obj))`).
|
|
337
|
+
* @defaultValue `Object.prototype`
|
|
338
|
+
* @returns an array of keys that the object has inherited (string or symbolic keys).
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* ```ts
|
|
342
|
+
* import { assertEquals } from "jsr:@std/assert"
|
|
343
|
+
*
|
|
344
|
+
* const
|
|
345
|
+
* symbol_h = Symbol("symbol h"),
|
|
346
|
+
* symbol_i = Symbol("symbol i"),
|
|
347
|
+
* symbol_j = Symbol("symbol j")
|
|
348
|
+
*
|
|
349
|
+
* class A {
|
|
350
|
+
* a = { v: 1 }
|
|
351
|
+
* b = { v: 2 }
|
|
352
|
+
* c: { v: number }
|
|
353
|
+
* d() { return { v: 4 } }
|
|
354
|
+
* e() { return { v: 5 } }
|
|
355
|
+
* f = () => { return { v: 6 } }
|
|
356
|
+
* g: () => ({ v: number })
|
|
357
|
+
* [symbol_h]() { return { v: 8 } }
|
|
358
|
+
* [symbol_i] = () => { return { v: 9 } }
|
|
359
|
+
*
|
|
360
|
+
* constructor() {
|
|
361
|
+
* this.c = { v: 3 }
|
|
362
|
+
* this.g = () => { return { v: 7 } }
|
|
363
|
+
* }
|
|
364
|
+
* }
|
|
365
|
+
*
|
|
366
|
+
* class B extends A {
|
|
367
|
+
* override a = { v: 11 }
|
|
368
|
+
* override e() { return { v: 15 } }
|
|
369
|
+
* //@ts-ignore: typescript does not permit defining a method over the name of an existing property
|
|
370
|
+
* override g() { return { v: 17 } }
|
|
371
|
+
* [symbol_j] = () => { return { v: 20 } }
|
|
372
|
+
*
|
|
373
|
+
* constructor() { super() }
|
|
374
|
+
* }
|
|
375
|
+
*
|
|
376
|
+
* const
|
|
377
|
+
* a = new A(),
|
|
378
|
+
* b = new B()
|
|
379
|
+
*
|
|
380
|
+
* assertEquals(
|
|
381
|
+
* new Set(getInheritedPropertyKeys(a)),
|
|
382
|
+
* new Set(["constructor", "d", "e", symbol_h]),
|
|
383
|
+
* )
|
|
384
|
+
*
|
|
385
|
+
* // below, notice how the inherited keys of `a` equal to its prototype's owned keys.
|
|
386
|
+
* // this is because methods of instances of `A` are defined on the prototype (i.e. properties of the prototype, rather than the instances').
|
|
387
|
+
* assertEquals(
|
|
388
|
+
* new Set(getInheritedPropertyKeys(a)),
|
|
389
|
+
* new Set(getOwnPropertyKeys(A.prototype)),
|
|
390
|
+
* )
|
|
391
|
+
*
|
|
392
|
+
* // also notice that inherited keys of `A.prototype` comes out as empty here,
|
|
393
|
+
* // even though it does techinally inherit members from its own prototype (which is `Object.prototype`).
|
|
394
|
+
* // the reason is that by default, we do not go deeper than `Object.prototype` to look for more keys.
|
|
395
|
+
* // this is because from an end-user's perspective, those keys are not useful.
|
|
396
|
+
* assertEquals(
|
|
397
|
+
* getInheritedPropertyKeys(A.prototype),
|
|
398
|
+
* [],
|
|
399
|
+
* )
|
|
400
|
+
*
|
|
401
|
+
* assertEquals(
|
|
402
|
+
* new Set(getInheritedPropertyKeys(b)),
|
|
403
|
+
* new Set(["constructor", "e", "g", "d", symbol_h]),
|
|
404
|
+
* )
|
|
405
|
+
* assertEquals(
|
|
406
|
+
* new Set(getInheritedPropertyKeys(b)),
|
|
407
|
+
* new Set([
|
|
408
|
+
* ...getOwnPropertyKeys(B.prototype),
|
|
409
|
+
* ...getInheritedPropertyKeys(B.prototype),
|
|
410
|
+
* ]),
|
|
411
|
+
* )
|
|
412
|
+
* assertEquals(
|
|
413
|
+
* new Set(getInheritedPropertyKeys(B.prototype)),
|
|
414
|
+
* new Set(["constructor", "e", "d", symbol_h]),
|
|
415
|
+
* )
|
|
416
|
+
*
|
|
417
|
+
* // testing out various depth
|
|
418
|
+
* assertEquals(
|
|
419
|
+
* new Set(getInheritedPropertyKeys(a, 1)),
|
|
420
|
+
* new Set(getOwnPropertyKeys(A.prototype)),
|
|
421
|
+
* )
|
|
422
|
+
* assertEquals(
|
|
423
|
+
* new Set(getInheritedPropertyKeys(b, 1)),
|
|
424
|
+
* new Set(getOwnPropertyKeys(B.prototype)),
|
|
425
|
+
* )
|
|
426
|
+
* assertEquals(
|
|
427
|
+
* new Set(getInheritedPropertyKeys(b, A.prototype)),
|
|
428
|
+
* new Set(getOwnPropertyKeys(B.prototype)),
|
|
429
|
+
* )
|
|
430
|
+
* assertEquals(
|
|
431
|
+
* new Set(getInheritedPropertyKeys(b, 2)),
|
|
432
|
+
* new Set([
|
|
433
|
+
* ...getOwnPropertyKeys(B.prototype),
|
|
434
|
+
* ...getOwnPropertyKeys(A.prototype),
|
|
435
|
+
* ]),
|
|
436
|
+
* )
|
|
437
|
+
* // below, we collect all inherited keys of `b`, including those that come from `Object.prototype`,
|
|
438
|
+
* // which is the base ancestral prototype of all objects.
|
|
439
|
+
* // to do that, we set the `depth` to `null`, which is the prototype of `Object.prototype`.
|
|
440
|
+
* // the test below may fail in new versions of javascript, where
|
|
441
|
+
* assertEquals(
|
|
442
|
+
* new Set(getInheritedPropertyKeys(b, null)),
|
|
443
|
+
* new Set([
|
|
444
|
+
* "constructor", "e", "g", "d", symbol_h,
|
|
445
|
+
* "__defineGetter__", "__defineSetter__", "hasOwnProperty", "__lookupGetter__", "__lookupSetter__",
|
|
446
|
+
* "isPrototypeOf", "propertyIsEnumerable", "toString", "valueOf", "toLocaleString",
|
|
447
|
+
* ]),
|
|
448
|
+
* )
|
|
449
|
+
* ```
|
|
450
|
+
*/
|
|
451
|
+
export const getInheritedPropertyKeys = (obj, depth = prototypeOfClass(Object)) => {
|
|
452
|
+
// weird but expected facts:
|
|
453
|
+
// - `Object.prototype !== Object.getPrototypeOf(Object)`
|
|
454
|
+
// - `Object.prototype === Object.getPrototypeOf({})`
|
|
455
|
+
// - `null === Object.getPrototypeOf(Object.prototype)`
|
|
456
|
+
// - `you === "ugly"`
|
|
457
|
+
const prototype_chain = prototypeChainOfObject(obj, { start: 0, end: depth }), inherited_keys = prototype_chain
|
|
458
|
+
.map((prototype) => (getOwnPropertyKeys(prototype)))
|
|
459
|
+
.flat(1);
|
|
460
|
+
return [...(new Set(inherited_keys))];
|
|
461
|
+
};
|
|
462
|
+
/** get all **owned** getter property keys of an object `obj` (string keys and symbol keys).
|
|
463
|
+
*
|
|
464
|
+
* TODO: in the future, consider creating a `getInheritedGetterKeys` function with the same signature as `getInheritedPropertyKeys`.
|
|
465
|
+
*
|
|
466
|
+
* > [!note]
|
|
467
|
+
* > inherited getter property keys will not be included.
|
|
468
|
+
* > only directly defined getter property keys (aka owned keys) will be listed.
|
|
469
|
+
*/
|
|
470
|
+
export const getOwnGetterKeys = (obj) => {
|
|
471
|
+
return getOwnPropertyKeys(obj).filter((key) => ("get" in object_getOwnPropertyDescriptor(obj, key)));
|
|
472
|
+
};
|
|
473
|
+
/** get all **owned** setter property keys of an object `obj` (string keys and symbol keys).
|
|
474
|
+
*
|
|
475
|
+
* TODO: in the future, consider creating a `getInheritedSetterKeys` function with the same signature as `getInheritedPropertyKeys`.
|
|
476
|
+
*
|
|
477
|
+
* > [!note]
|
|
478
|
+
* > inherited setter property keys will not be included.
|
|
479
|
+
* > only directly defined setter property keys (aka owned keys) will be listed.
|
|
480
|
+
*/
|
|
481
|
+
export const getOwnSetterKeys = (obj) => {
|
|
482
|
+
return getOwnPropertyKeys(obj).filter((key) => ("set" in object_getOwnPropertyDescriptor(obj, key)));
|
|
483
|
+
};
|
|
484
|
+
/** create a new object that mirrors that functionality of an existing object `obj`, through composition.
|
|
485
|
+
*
|
|
486
|
+
* @param obj the object that is to be mimicked and composed.
|
|
487
|
+
* @param config control how the mirroring composition should behave.
|
|
488
|
+
* refer to the docs of {@link MirrorObjectThroughCompositionConfig} for more details.
|
|
489
|
+
*
|
|
490
|
+
* @example
|
|
491
|
+
* ```ts
|
|
492
|
+
* import { assertEquals } from "jsr:@std/assert"
|
|
493
|
+
*
|
|
494
|
+
* type TypeofB = Array<number> & { getLength(): number }
|
|
495
|
+
* type TypeofC = TypeofB & { countZeros(): number }
|
|
496
|
+
*
|
|
497
|
+
* const a = Array.prototype
|
|
498
|
+
* const b = {
|
|
499
|
+
* getLength(): number { return this.length }
|
|
500
|
+
* } as TypeofB
|
|
501
|
+
* const c = {
|
|
502
|
+
* countZeros(): number { return [...this].filter((value) => (value === 0)).length }
|
|
503
|
+
* } as TypeofC
|
|
504
|
+
*
|
|
505
|
+
* Object.setPrototypeOf(b, a)
|
|
506
|
+
* Object.setPrototypeOf(c, b)
|
|
507
|
+
*
|
|
508
|
+
* // below, we create an object `d` that mirrors the methods and protperties of `c`, but does not actually inherit it.
|
|
509
|
+
* const d = mirrorObjectThroughComposition(c, {
|
|
510
|
+
* baseKey: "_super",
|
|
511
|
+
* propertyKeys: ["length"],
|
|
512
|
+
* })
|
|
513
|
+
*
|
|
514
|
+
* // notice that `d` does not inherit `c` as its prototype, despite being able to utilize its methods and properties.
|
|
515
|
+
* assertEquals(Object.getPrototypeOf(d), Object.prototype, "`d` does not inherit `c`")
|
|
516
|
+
*
|
|
517
|
+
* d.push(0, 0, 0, 0, 1)
|
|
518
|
+
* assertEquals([...d], [0, 0, 0, 0, 1], "`d` also mirrors symbolic keys (such as iterators)")
|
|
519
|
+
* assertEquals([...c], [0, 0, 0, 0, 1], "mutations made to `d` are applied to `c`")
|
|
520
|
+
* assertEquals(d._super, c, "`c` is accessible via the `baseKey` (\"_super\") property")
|
|
521
|
+
* assertEquals(d.length, 5)
|
|
522
|
+
* assertEquals(c.length, 5)
|
|
523
|
+
* assertEquals(d.getLength(), 5)
|
|
524
|
+
* assertEquals(d.countZeros(), 4)
|
|
525
|
+
* d.splice(0, 3)
|
|
526
|
+
* assertEquals(d.countZeros(), 1)
|
|
527
|
+
* assertEquals(c.countZeros(), 1)
|
|
528
|
+
*
|
|
529
|
+
* // you may even hot-swap the object that is being composed inside of `d`.
|
|
530
|
+
* const e = [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
531
|
+
* Object.setPrototypeOf(e, c)
|
|
532
|
+
* d._super = e as TypeofC
|
|
533
|
+
* assertEquals(d.length, 9)
|
|
534
|
+
* assertEquals(d.getLength(), 9)
|
|
535
|
+
* assertEquals(d.countZeros(), 0)
|
|
536
|
+
*
|
|
537
|
+
* // it is also possible for you to provide an existing `target` object onto which the mirroring should occur.
|
|
538
|
+
* // moreover, you can ignore specific keys which you would like not to be mirrored using the `ignoreKeys` option.
|
|
539
|
+
* class F {
|
|
540
|
+
* methodF() { return "press f for your fallen comrades" }
|
|
541
|
+
* // to stop typescript from complaining, we declare the instance methods and properties that will be mirrored.
|
|
542
|
+
* declare _super: typeof c
|
|
543
|
+
* declare length: number
|
|
544
|
+
* declare getLength: undefined
|
|
545
|
+
* declare countZeros: () => number
|
|
546
|
+
* }
|
|
547
|
+
* mirrorObjectThroughComposition(c, {
|
|
548
|
+
* target: F.prototype,
|
|
549
|
+
* baseKey: "_super",
|
|
550
|
+
* propertyKeys: ["length"],
|
|
551
|
+
* ignoreKeys: ["getLength"],
|
|
552
|
+
* })
|
|
553
|
+
* const f = new F()
|
|
554
|
+
* assertEquals(f instanceof F, true)
|
|
555
|
+
* assertEquals(f._super, c)
|
|
556
|
+
* assertEquals(f.length, 2)
|
|
557
|
+
* assertEquals(f.countZeros(), 1)
|
|
558
|
+
* assertEquals(f.getLength, undefined) // the `getLength` is not mirrored because it was present in the `ignoreKeys` option
|
|
559
|
+
* assertEquals(f.methodF(), "press f for your fallen comrades")
|
|
560
|
+
* ```
|
|
561
|
+
*/
|
|
562
|
+
export const mirrorObjectThroughComposition = (obj, config) => {
|
|
563
|
+
const { baseKey, mirrorPrototype = true, propertyKeys = [], ignoreKeys = [], target = {} } = config, mirror_obj = target, property_keys = new Set(propertyKeys), ignore_keys = new Set(ignoreKeys), prototype_chain = isBoolean(mirrorPrototype) ? (mirrorPrototype
|
|
564
|
+
? prototypeChainOfObject(obj, { end: prototypeOfClass(Object) })
|
|
565
|
+
: []) : isArray(mirrorPrototype)
|
|
566
|
+
? mirrorPrototype
|
|
567
|
+
: prototypeChainOfObject(obj, mirrorPrototype);
|
|
568
|
+
prototype_chain.unshift(obj);
|
|
569
|
+
mirror_obj[baseKey] = obj;
|
|
570
|
+
// dynamically defining the methods composed from the base object `obj`
|
|
571
|
+
for (const prototype of prototype_chain) {
|
|
572
|
+
const prototype_keys = getOwnPropertyKeys(prototype);
|
|
573
|
+
for (const key of prototype_keys) {
|
|
574
|
+
if ((key in mirror_obj) || property_keys.has(key) || ignore_keys.has(key)) {
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
const { value, ...description } = object_getOwnPropertyDescriptor(prototype, key);
|
|
578
|
+
if (isFunction(value)) {
|
|
579
|
+
// `value` is very likely to be a prototype-bound method.
|
|
580
|
+
// so we create an appropriate proxying/wrapper method for it.
|
|
581
|
+
object_defineProperty(mirror_obj, key, {
|
|
582
|
+
...description,
|
|
583
|
+
value(...args) {
|
|
584
|
+
// NOTE: the motivation behind returning `this[baseKey][key](...args)` instead of `obj[key](...args)` is that it would
|
|
585
|
+
// allow us to later on dynamically replace the underlying object that is being mirrored, which is a necessity when
|
|
586
|
+
// dealing with class-instance object mirroring that initially being defined on class's own prototype base constructor.
|
|
587
|
+
return this[baseKey][key](...args);
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
// `value` is either an instance-bound property or does not exist exist (i.e. there's a getter/setter method in its place instead)
|
|
593
|
+
property_keys.add(key);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
// dynamically defining the potentially transparent properties (i.e. later defined) of the composed object
|
|
598
|
+
for (const key of property_keys) {
|
|
599
|
+
// TODO: what if there's more to the description of the given key? we're certainly not preserving that information here unfortunately.
|
|
600
|
+
object_defineProperty(mirror_obj, key, {
|
|
601
|
+
get() { return this[baseKey][key]; },
|
|
602
|
+
set(new_value) { this[baseKey][key] = new_value; },
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
return mirror_obj;
|
|
606
|
+
};
|
|
607
|
+
/** create a subclass of the given `cls` class through composition rather than ES6 inheritance (aka `extends`).
|
|
608
|
+
* the composed instance resides under the property `this._super`, and all instance methods of the super class are copied/mirrored in the output class.
|
|
609
|
+
*
|
|
610
|
+
* TODO: provide a motivation/justification when compositional-mirroring subclass might be necessary to use instead of ES6 `extends` inheritance.
|
|
611
|
+
*
|
|
612
|
+
* @param cls the class to subclass through composition.
|
|
613
|
+
* @param property_keys specify additional property keys that exist within the instance of the class that need to be mirrored.
|
|
614
|
+
* @returns a class whose instances fully mirror the provided `cls` class's instance, without actually inheriting it.
|
|
615
|
+
*
|
|
616
|
+
* @example
|
|
617
|
+
* ```ts
|
|
618
|
+
* import { assertEquals } from "jsr:@std/assert"
|
|
619
|
+
*
|
|
620
|
+
* class A<T> extends Array<T> { constructor(quantity: number) { super(quantity) } }
|
|
621
|
+
* class B<T> extends subclassThroughComposition(A, {
|
|
622
|
+
* class: {},
|
|
623
|
+
* instance: { propertyKeys: ["length"] },
|
|
624
|
+
* })<T> { }
|
|
625
|
+
*
|
|
626
|
+
* // `B` mirrors the constructor of `A`, as well as provide an additional `"_super"` property to access the enclosed (class which is `A`)
|
|
627
|
+
* B satisfies { new(amount: number): B<any> }
|
|
628
|
+
* B._super satisfies typeof A
|
|
629
|
+
* // the following cannot be satisfied due to `A`'s restrictive constructor: `B satisfies { new(item1: string, item2: string, item3: string): B<string> }`
|
|
630
|
+
* // even though `A`'s super class (`Array`) does permit that kind of constructor signature:
|
|
631
|
+
* Array satisfies { new(item1: string, item2: string, item3: string): Array<string> }
|
|
632
|
+
*
|
|
633
|
+
* const my_array = new B<number>(5)
|
|
634
|
+
*
|
|
635
|
+
* // `my_array` encloses an instance of its "subclass" (which is an instance of class `A`) under the `"_super"` key.
|
|
636
|
+
* assertEquals(my_array instanceof B, true)
|
|
637
|
+
* assertEquals(my_array._super instanceof A, true)
|
|
638
|
+
* my_array._super satisfies A<unknown> // this should have been narrowed to `satisfies A<number>`, but typing class extensions with generics is nearly impossible
|
|
639
|
+
*
|
|
640
|
+
* // `my_array` mirrors the properties and methods of the instances of the "subclass" that it encloses.
|
|
641
|
+
* my_array.fill(1)
|
|
642
|
+
* my_array.push(2)
|
|
643
|
+
* my_array.push(3)
|
|
644
|
+
* assertEquals(my_array.at(-1), 3)
|
|
645
|
+
* assertEquals(my_array._super.at(-1), 3)
|
|
646
|
+
* assertEquals(my_array.length, 7)
|
|
647
|
+
* assertEquals(my_array._super.length, 7)
|
|
648
|
+
*
|
|
649
|
+
* // `my_array` mirrors the symbolic properties and methods as well.
|
|
650
|
+
* assertEquals([...my_array], [1, 1, 1, 1, 1, 2, 3]) // the `Symbol.iterator` method is mirrored successfully
|
|
651
|
+
*
|
|
652
|
+
* // `my_array` when a method that returns `new this.constructor(...)` (such as `Array.prototype.splice`), then an instance of
|
|
653
|
+
* // the enclosed subclass `A` will be created instead of `B` (had we had a regular inheritance via `class B extends A { }`).
|
|
654
|
+
* const
|
|
655
|
+
* splice_of_my_array = my_array.splice(0, 4, 0, 0),
|
|
656
|
+
* slice_of_my_array = my_array.slice(3, 4)
|
|
657
|
+
* assertEquals(splice_of_my_array instanceof A, true)
|
|
658
|
+
* assertEquals(slice_of_my_array instanceof A, true)
|
|
659
|
+
* assertEquals([...splice_of_my_array], [1, 1, 1, 1])
|
|
660
|
+
* assertEquals([...slice_of_my_array], [2])
|
|
661
|
+
* assertEquals([...my_array], [0, 0, 1, 2, 3])
|
|
662
|
+
* assertEquals([...my_array._super], [0, 0, 1, 2, 3])
|
|
663
|
+
*
|
|
664
|
+
* // the class `B` also mirrors static properties and static methods of `A` (and its inherited ones).
|
|
665
|
+
* // this means that any static method that creates a new instance via `new this(...)` will create an instance of `A` instead of `B`.
|
|
666
|
+
* const
|
|
667
|
+
* new_array_1 = B.from(["hello", "world"]),
|
|
668
|
+
* new_array_2 = B.of("goodbye", "world")
|
|
669
|
+
* assertEquals(new_array_1 instanceof A, true)
|
|
670
|
+
* assertEquals(new_array_2 instanceof A, true)
|
|
671
|
+
* assertEquals(new_array_1, A.from(["hello", "world"]))
|
|
672
|
+
* assertEquals(new_array_2, A.of("goodbye", "world"))
|
|
673
|
+
* ```
|
|
674
|
+
*/
|
|
675
|
+
export const subclassThroughComposition = (cls, config = {}) => {
|
|
676
|
+
const class_config = config.class ?? {}, instance_config = config.instance ?? {}, instance_base_key = instance_config.baseKey ?? "_super", class_ignore_keys = class_config.ignoreKeys ?? [], new_cls = class {
|
|
677
|
+
constructor(...args) {
|
|
678
|
+
const composite_instance = new cls(...args);
|
|
679
|
+
// @ts-ignore: dynamic computed key assignment is not permitted in typescript
|
|
680
|
+
this[instance_base_key] = composite_instance;
|
|
681
|
+
}
|
|
682
|
+
}, //as ConstructorOf<MirrorComposition<T, INSTANCE_KEY>, Args> & MirrorComposition<CLS, CLASS_KEY>,
|
|
683
|
+
cls_prototype = prototypeOfClass(cls), new_cls_prototype = prototypeOfClass(new_cls);
|
|
684
|
+
// mirroring the static class methods and static properties of `cls` onto `new_cls`
|
|
685
|
+
mirrorObjectThroughComposition(cls, {
|
|
686
|
+
// NOTE: all classes extend the `Function` constructor (i.e. if we had `class A {}`, then `Object.getPrototypeOf(A) === Function.prototype`).
|
|
687
|
+
// but obviously, we don't want to mirror the `Function` methods within `cls` (aka the static methods of `Function`),
|
|
688
|
+
// which is why we set our default class's config `mirrorPrototype.end` to `Function.prototype`.
|
|
689
|
+
mirrorPrototype: { start: 0, end: prototypeOfClass(Function) },
|
|
690
|
+
baseKey: "_super",
|
|
691
|
+
...class_config,
|
|
692
|
+
// the ignore list ensures that we don't overwrite existing properties of the `target` class.
|
|
693
|
+
// (even though there are checks in place that would prevent overwriting existing keys, it's better to be explicit)
|
|
694
|
+
ignoreKeys: [...class_ignore_keys, "length", "name", "prototype"],
|
|
695
|
+
target: new_cls,
|
|
696
|
+
});
|
|
697
|
+
// mirroring the instance methods and properties of `cls.prototype` onto `new_cls.prototype`
|
|
698
|
+
mirrorObjectThroughComposition(cls_prototype, {
|
|
699
|
+
baseKey: instance_base_key,
|
|
700
|
+
...instance_config,
|
|
701
|
+
target: new_cls_prototype,
|
|
702
|
+
});
|
|
703
|
+
return new_cls;
|
|
704
|
+
};
|
|
705
|
+
/** monkey patch the prototype of a class.
|
|
706
|
+
*
|
|
707
|
+
* TODO: give usage examples and situations where this will be useful.
|
|
708
|
+
*/
|
|
709
|
+
export const monkeyPatchPrototypeOfClass = (cls, key, value) => {
|
|
710
|
+
object_defineProperty(prototypeOfClass(cls), key, { value });
|
|
711
|
+
};
|
|
712
|
+
/** check if `obj` is either an object or function. */
|
|
713
|
+
export const isComplex = (obj) => {
|
|
714
|
+
const obj_type = typeof obj;
|
|
715
|
+
return obj_type === "object" || obj_type === "function";
|
|
716
|
+
};
|
|
717
|
+
/** check if `obj` is neither an object nor a function. */
|
|
718
|
+
export const isPrimitive = (obj) => {
|
|
719
|
+
return !isComplex(obj);
|
|
720
|
+
};
|
|
721
|
+
/** check if `obj` is a `function`.
|
|
722
|
+
*
|
|
723
|
+
* TODO: consider if it would be a good idea to include a generic parameter for the function's signature.
|
|
724
|
+
* i.e.: `<FN extends Function = Function>(obj: any): obj is FN`
|
|
725
|
+
*/
|
|
726
|
+
export const isFunction = (obj) => {
|
|
727
|
+
return typeof obj === "function";
|
|
728
|
+
};
|
|
729
|
+
/** check if `obj` is an `Object`. */
|
|
730
|
+
export const isObject = (obj) => {
|
|
731
|
+
return typeof obj === "object";
|
|
732
|
+
};
|
|
733
|
+
/** check if `obj` is an `Array`. */
|
|
734
|
+
export const isArray = array_isArray;
|
|
735
|
+
/** check if `obj` is a `Record`, which is any non-nullable object that isn't an array;
|
|
736
|
+
* kind of like a dictionary.
|
|
737
|
+
*/
|
|
738
|
+
export const isRecord = (obj) => {
|
|
739
|
+
return isObject(obj) && obj !== null && !isArray(obj);
|
|
740
|
+
};
|
|
741
|
+
/** check if `obj` is a `string`. */
|
|
742
|
+
export const isString = (obj) => {
|
|
743
|
+
return typeof obj === "string";
|
|
744
|
+
};
|
|
745
|
+
/** check if `obj` is a `number`. */
|
|
746
|
+
export const isNumber = (obj) => {
|
|
747
|
+
return typeof obj === "number";
|
|
748
|
+
};
|
|
749
|
+
/** check if `obj` is a `bigint`. */
|
|
750
|
+
export const isBigint = (obj) => {
|
|
751
|
+
return typeof obj === "bigint";
|
|
752
|
+
};
|
|
753
|
+
/** check if `obj` is either a `number` or a `bigint`. */
|
|
754
|
+
export const isNumeric = (obj) => {
|
|
755
|
+
return typeof obj === "number" || typeof obj === "bigint";
|
|
756
|
+
};
|
|
757
|
+
/** check if `obj` is `boolean`. */
|
|
758
|
+
export const isBoolean = (obj) => {
|
|
759
|
+
return typeof obj === "boolean";
|
|
760
|
+
};
|
|
761
|
+
/** check if `obj` is a `symbol`. */
|
|
762
|
+
export const isSymbol = (obj) => {
|
|
763
|
+
return typeof obj === "symbol";
|
|
764
|
+
};
|