mutts 1.0.0 → 1.0.1
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/dist/chunks/{decorator-BXsign4Z.js → decorator-8qjFb7dw.js} +2 -2
- package/dist/chunks/decorator-8qjFb7dw.js.map +1 -0
- package/dist/chunks/{decorator-CPbZNnsX.esm.js → decorator-AbRkXM5O.esm.js} +2 -2
- package/dist/chunks/decorator-AbRkXM5O.esm.js.map +1 -0
- package/dist/decorator.d.ts +1 -1
- package/dist/decorator.esm.js +1 -1
- package/dist/decorator.js +1 -1
- package/dist/destroyable.esm.js +1 -1
- package/dist/destroyable.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +2 -2
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/mutts.umd.js +1 -1
- package/dist/mutts.umd.js.map +1 -1
- package/dist/mutts.umd.min.js +1 -1
- package/dist/mutts.umd.min.js.map +1 -1
- package/dist/reactive.d.ts +4 -3
- package/dist/reactive.esm.js +61 -57
- package/dist/reactive.esm.js.map +1 -1
- package/dist/reactive.js +61 -56
- package/dist/reactive.js.map +1 -1
- package/dist/std-decorators.esm.js +1 -1
- package/dist/std-decorators.js +1 -1
- package/docs/reactive.md +616 -0
- package/package.json +1 -2
- package/dist/chunks/decorator-BXsign4Z.js.map +0 -1
- package/dist/chunks/decorator-CPbZNnsX.esm.js.map +0 -1
- package/src/decorator.test.ts +0 -495
- package/src/decorator.ts +0 -205
- package/src/destroyable.test.ts +0 -155
- package/src/destroyable.ts +0 -158
- package/src/eventful.test.ts +0 -380
- package/src/eventful.ts +0 -69
- package/src/index.ts +0 -7
- package/src/indexable.test.ts +0 -388
- package/src/indexable.ts +0 -124
- package/src/promiseChain.test.ts +0 -201
- package/src/promiseChain.ts +0 -99
- package/src/reactive/array.test.ts +0 -923
- package/src/reactive/array.ts +0 -352
- package/src/reactive/core.test.ts +0 -1663
- package/src/reactive/core.ts +0 -866
- package/src/reactive/index.ts +0 -28
- package/src/reactive/interface.test.ts +0 -1477
- package/src/reactive/interface.ts +0 -231
- package/src/reactive/map.test.ts +0 -866
- package/src/reactive/map.ts +0 -162
- package/src/reactive/set.test.ts +0 -289
- package/src/reactive/set.ts +0 -142
- package/src/std-decorators.test.ts +0 -679
- package/src/std-decorators.ts +0 -182
- package/src/utils.ts +0 -52
package/src/indexable.test.ts
DELETED
|
@@ -1,388 +0,0 @@
|
|
|
1
|
-
import { getAt, Indexable, setAt } from './indexable'
|
|
2
|
-
|
|
3
|
-
// TODO: get/set became this: based
|
|
4
|
-
describe('Indexable', () => {
|
|
5
|
-
describe('Indexable(base, accessor)', () => {
|
|
6
|
-
it('should create indexable class with custom accessor', () => {
|
|
7
|
-
class Base {
|
|
8
|
-
constructor(public items: string[]) {}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const IndexableBase = Indexable(Base, {
|
|
12
|
-
get: function (this: Base, index) {
|
|
13
|
-
return this.items[index]
|
|
14
|
-
},
|
|
15
|
-
set: function (this: Base, index, value) {
|
|
16
|
-
this.items[index] = value
|
|
17
|
-
},
|
|
18
|
-
})
|
|
19
|
-
const instance = new IndexableBase(['a', 'b', 'c'])
|
|
20
|
-
|
|
21
|
-
expect(instance[0]).toBe('a')
|
|
22
|
-
expect(instance[1]).toBe('b')
|
|
23
|
-
expect(instance[2]).toBe('c')
|
|
24
|
-
expect(instance.items).toEqual(['a', 'b', 'c'])
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it('should handle different item types', () => {
|
|
28
|
-
class Base {
|
|
29
|
-
constructor(public numbers: number[]) {}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const IndexableBase = Indexable(Base, {
|
|
33
|
-
get: function (this: Base, index) {
|
|
34
|
-
return this.numbers[index] * 2
|
|
35
|
-
},
|
|
36
|
-
set: function (this: Base, index, value) {
|
|
37
|
-
this.numbers[index] = value / 2
|
|
38
|
-
},
|
|
39
|
-
})
|
|
40
|
-
const instance = new IndexableBase([1, 2, 3])
|
|
41
|
-
|
|
42
|
-
expect(instance[0]).toBe(2)
|
|
43
|
-
expect(instance[1]).toBe(4)
|
|
44
|
-
expect(instance[2]).toBe(6)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('should preserve base class methods', () => {
|
|
48
|
-
class Base {
|
|
49
|
-
constructor(public items: string[]) {}
|
|
50
|
-
getLength() {
|
|
51
|
-
return this.items.length
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const IndexableBase = Indexable(Base, {
|
|
56
|
-
get: function (this: Base, index) {
|
|
57
|
-
return this.items[index]
|
|
58
|
-
},
|
|
59
|
-
set: function (this: Base, index, value) {
|
|
60
|
-
this.items[index] = value
|
|
61
|
-
},
|
|
62
|
-
})
|
|
63
|
-
const instance = new IndexableBase(['a', 'b'])
|
|
64
|
-
|
|
65
|
-
expect(instance.getLength()).toBe(2)
|
|
66
|
-
expect(instance[0]).toBe('a')
|
|
67
|
-
})
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
describe('Indexable(base with getAt and setAt methods)', () => {
|
|
71
|
-
it('should use the base class getAt and setAt methods', () => {
|
|
72
|
-
class Base {
|
|
73
|
-
constructor(public items: string[]) {}
|
|
74
|
-
[getAt](index: number): string {
|
|
75
|
-
return this.items[index]
|
|
76
|
-
}
|
|
77
|
-
[setAt](index: number, value: string): void {
|
|
78
|
-
this.items[index] = value
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const IndexableBase = Indexable(Base)
|
|
83
|
-
const instance = new IndexableBase(['x', 'y', 'z'])
|
|
84
|
-
|
|
85
|
-
expect(instance[0]).toBe('x')
|
|
86
|
-
instance[0] = 'a'
|
|
87
|
-
expect(instance[0]).toBe('a')
|
|
88
|
-
expect(instance.items[0]).toBe('a')
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
it('should work with custom getAt and setAt implementation', () => {
|
|
92
|
-
class Base {
|
|
93
|
-
constructor(public numbers: number[]) {}
|
|
94
|
-
[getAt](index: number): number {
|
|
95
|
-
return this.numbers[index] * 3
|
|
96
|
-
}
|
|
97
|
-
[setAt](index: number, value: number): void {
|
|
98
|
-
this.numbers[index] = value / 3
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const IndexableBase = Indexable(Base)
|
|
103
|
-
const instance = new IndexableBase([1, 2, 3])
|
|
104
|
-
|
|
105
|
-
expect(instance[0]).toBe(3)
|
|
106
|
-
instance[0] = 9
|
|
107
|
-
expect(instance[0]).toBe(9)
|
|
108
|
-
expect(instance.numbers[0]).toBe(3)
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
it('should throw when setAt method is missing', () => {
|
|
112
|
-
class Base {
|
|
113
|
-
constructor(public items: string[]) {}
|
|
114
|
-
[getAt](index: number): string {
|
|
115
|
-
return this.items[index]
|
|
116
|
-
}
|
|
117
|
-
// Missing setAt method
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const IndexableBase = Indexable(Base)
|
|
121
|
-
const instance = new IndexableBase(['x', 'y'])
|
|
122
|
-
|
|
123
|
-
expect(instance[0]).toBe('x')
|
|
124
|
-
expect(() => {
|
|
125
|
-
instance[0] = 'z'
|
|
126
|
-
}).toThrow('Indexable class has read-only numeric index access')
|
|
127
|
-
})
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
describe('Indexable()', () => {
|
|
131
|
-
it('should create abstract class with abstract getAt method', () => {
|
|
132
|
-
const AbstractIndexable = Indexable<string>()
|
|
133
|
-
|
|
134
|
-
//@ts-expect-error Should be abstract
|
|
135
|
-
void new AbstractIndexable()
|
|
136
|
-
|
|
137
|
-
// Should have abstract getAt method
|
|
138
|
-
class Concrete extends AbstractIndexable {
|
|
139
|
-
constructor(private items: string[]) {
|
|
140
|
-
super()
|
|
141
|
-
}
|
|
142
|
-
[getAt](index: number): string {
|
|
143
|
-
return this.items[index]
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const instance = new Concrete(['p', 'q', 'r'])
|
|
148
|
-
expect(instance[0]).toBe('p')
|
|
149
|
-
expect(instance[1]).toBe('q')
|
|
150
|
-
expect(instance[2]).toBe('r')
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
it('should enforce getAt method implementation', () => {
|
|
154
|
-
const AbstractIndexable = Indexable<number>()
|
|
155
|
-
//@ts-expect-error Should be abstract
|
|
156
|
-
class Invalid extends AbstractIndexable {}
|
|
157
|
-
|
|
158
|
-
// JavaScript doesn't enforce abstract methods at runtime
|
|
159
|
-
// So instantiation won't throw, but calling the missing method will
|
|
160
|
-
const instance = new Invalid()
|
|
161
|
-
expect(() => instance[0]).toThrow()
|
|
162
|
-
})
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
describe('edge cases', () => {
|
|
166
|
-
it('should handle out of bounds access', () => {
|
|
167
|
-
class Base {
|
|
168
|
-
constructor(public items: string[]) {}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const IndexableBase = Indexable(Base, {
|
|
172
|
-
get: function (this: Base, index) {
|
|
173
|
-
return this.items[index]
|
|
174
|
-
},
|
|
175
|
-
set: function (this: Base, index, value) {
|
|
176
|
-
this.items[index] = value
|
|
177
|
-
},
|
|
178
|
-
})
|
|
179
|
-
const instance = new IndexableBase(['a', 'b'])
|
|
180
|
-
|
|
181
|
-
expect(instance[5]).toBeUndefined()
|
|
182
|
-
expect(instance[-1]).toBeUndefined()
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
it('should handle empty arrays', () => {
|
|
186
|
-
class Base {
|
|
187
|
-
constructor(public items: string[]) {}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const IndexableBase = Indexable(Base, {
|
|
191
|
-
get: function (this: Base, index) {
|
|
192
|
-
return this.items[index]
|
|
193
|
-
},
|
|
194
|
-
set: function (this: Base, index, value) {
|
|
195
|
-
this.items[index] = value
|
|
196
|
-
},
|
|
197
|
-
})
|
|
198
|
-
const instance = new IndexableBase([])
|
|
199
|
-
|
|
200
|
-
expect(instance[0]).toBeUndefined()
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
it('should not interfere with non-numeric properties', () => {
|
|
204
|
-
class Base {
|
|
205
|
-
constructor(public items: string[]) {}
|
|
206
|
-
length = 42
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const IndexableBase = Indexable(Base, {
|
|
210
|
-
get: function (this: Base, index) {
|
|
211
|
-
return this.items[index]
|
|
212
|
-
},
|
|
213
|
-
set: function (this: Base, index, value) {
|
|
214
|
-
this.items[index] = value
|
|
215
|
-
},
|
|
216
|
-
})
|
|
217
|
-
const instance = new IndexableBase(['a', 'b'])
|
|
218
|
-
|
|
219
|
-
expect(instance.length).toBe(42)
|
|
220
|
-
expect(instance.items).toEqual(['a', 'b'])
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
it('should not interfere with setting non-numeric properties', () => {
|
|
224
|
-
class Base {
|
|
225
|
-
constructor(public items: string[]) {}
|
|
226
|
-
length = 42
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const IndexableBase = Indexable(Base, {
|
|
230
|
-
get: function (this: Base, index) {
|
|
231
|
-
return this.items[index]
|
|
232
|
-
},
|
|
233
|
-
set: function (this: Base, index, value) {
|
|
234
|
-
this.items[index] = value
|
|
235
|
-
},
|
|
236
|
-
})
|
|
237
|
-
const instance = new IndexableBase(['a', 'b'])
|
|
238
|
-
|
|
239
|
-
instance.length = 100
|
|
240
|
-
expect(instance.length).toBe(100)
|
|
241
|
-
})
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
describe('integration tests', () => {
|
|
245
|
-
it('should work with complex objects', () => {
|
|
246
|
-
class Person {
|
|
247
|
-
constructor(
|
|
248
|
-
public name: string,
|
|
249
|
-
public age: number
|
|
250
|
-
) {}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
class PersonList {
|
|
254
|
-
constructor(public people: Person[]) {}
|
|
255
|
-
[getAt](index: number): Person {
|
|
256
|
-
return this.people[index]
|
|
257
|
-
}
|
|
258
|
-
[setAt](index: number, person: Person): void {
|
|
259
|
-
this.people[index] = person
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const IndexablePersonList = Indexable(PersonList)
|
|
264
|
-
const people = [new Person('Alice', 30), new Person('Bob', 25), new Person('Charlie', 35)]
|
|
265
|
-
const instance = new IndexablePersonList(people)
|
|
266
|
-
|
|
267
|
-
expect((instance[0] as Person).name).toBe('Alice')
|
|
268
|
-
instance[0] = new Person('Alice Updated', 31)
|
|
269
|
-
expect((instance[0] as Person).name).toBe('Alice Updated')
|
|
270
|
-
expect((instance[0] as Person).age).toBe(31)
|
|
271
|
-
expect(instance.people[0].name).toBe('Alice Updated')
|
|
272
|
-
})
|
|
273
|
-
|
|
274
|
-
it('should support multiple inheritance levels', () => {
|
|
275
|
-
class Base {
|
|
276
|
-
constructor(public items: string[]) {}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
class Extended extends Indexable(Base, {
|
|
280
|
-
get: function (this: Base, index) {
|
|
281
|
-
return this.items[index]
|
|
282
|
-
},
|
|
283
|
-
set: function (this: Base, index, value) {
|
|
284
|
-
this.items[index] = value
|
|
285
|
-
},
|
|
286
|
-
}) {
|
|
287
|
-
constructor(
|
|
288
|
-
items: string[],
|
|
289
|
-
public extra: string
|
|
290
|
-
) {
|
|
291
|
-
super(items)
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const instance = new Extended(['a', 'b'], 'extra')
|
|
296
|
-
expect(instance[0]).toBe('a')
|
|
297
|
-
expect(instance.extra).toBe('extra')
|
|
298
|
-
})
|
|
299
|
-
|
|
300
|
-
it('should support setting with custom logic', () => {
|
|
301
|
-
class Base {
|
|
302
|
-
constructor(public items: string[]) {}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const IndexableBase = Indexable(Base, {
|
|
306
|
-
get: function (this: Base, index) {
|
|
307
|
-
return this.items[index]
|
|
308
|
-
},
|
|
309
|
-
set: function (this: Base, index, value) {
|
|
310
|
-
// Custom logic: convert to uppercase
|
|
311
|
-
this.items[index] = value.toUpperCase()
|
|
312
|
-
},
|
|
313
|
-
})
|
|
314
|
-
const instance = new IndexableBase(['a', 'b', 'c'])
|
|
315
|
-
|
|
316
|
-
instance[1] = 'x'
|
|
317
|
-
expect(instance[1]).toBe('X')
|
|
318
|
-
expect(instance.items[1]).toBe('X')
|
|
319
|
-
})
|
|
320
|
-
})
|
|
321
|
-
|
|
322
|
-
describe('Indexable(accessor)', () => {
|
|
323
|
-
it('should create indexable object with custom accessor', () => {
|
|
324
|
-
const IndexableObj = Indexable({
|
|
325
|
-
get: function (this: any, index: number) {
|
|
326
|
-
return this._arr?.[index]
|
|
327
|
-
},
|
|
328
|
-
set: function (this: any, index: number, value: any) {
|
|
329
|
-
if (!this._arr) this._arr = []
|
|
330
|
-
this._arr[index] = value
|
|
331
|
-
},
|
|
332
|
-
})
|
|
333
|
-
const instance = new IndexableObj() as any
|
|
334
|
-
instance._arr = ['a', 'b', 'c']
|
|
335
|
-
expect(instance[0]).toBe('a')
|
|
336
|
-
expect(instance[1]).toBe('b')
|
|
337
|
-
expect(instance[2]).toBe('c')
|
|
338
|
-
})
|
|
339
|
-
|
|
340
|
-
it('should create indexable object with custom getter and setter', () => {
|
|
341
|
-
const IndexableObj = Indexable({
|
|
342
|
-
get: function (this: any, index: number) {
|
|
343
|
-
return this._arr?.[index]
|
|
344
|
-
},
|
|
345
|
-
set: function (this: any, index: number, value: string) {
|
|
346
|
-
if (!this._arr) this._arr = []
|
|
347
|
-
this._arr[index] = value
|
|
348
|
-
},
|
|
349
|
-
})
|
|
350
|
-
const instance = new IndexableObj() as any
|
|
351
|
-
instance[0] = 'x'
|
|
352
|
-
instance[1] = 'y'
|
|
353
|
-
expect(instance[0]).toBe('x')
|
|
354
|
-
expect(instance[1]).toBe('y')
|
|
355
|
-
expect(instance._arr[0]).toBe('x')
|
|
356
|
-
expect(instance._arr[1]).toBe('y')
|
|
357
|
-
})
|
|
358
|
-
|
|
359
|
-
it('should support transformation in setter', () => {
|
|
360
|
-
const IndexableObj = Indexable({
|
|
361
|
-
get: function (this: any, index: number) {
|
|
362
|
-
return this._arr?.[index] * 2
|
|
363
|
-
},
|
|
364
|
-
set: function (this: any, index: number, value: number) {
|
|
365
|
-
if (!this._arr) this._arr = []
|
|
366
|
-
this._arr[index] = value / 2
|
|
367
|
-
},
|
|
368
|
-
})
|
|
369
|
-
const instance = new IndexableObj() as any
|
|
370
|
-
instance._arr = [1, 2, 3]
|
|
371
|
-
instance[0] = 10
|
|
372
|
-
expect(instance[0]).toBe(10)
|
|
373
|
-
expect(instance._arr[0]).toBe(5)
|
|
374
|
-
})
|
|
375
|
-
|
|
376
|
-
it('should throw if setter is missing and assignment is attempted', () => {
|
|
377
|
-
const IndexableObj = Indexable({
|
|
378
|
-
get: function (this: any, index: number) {
|
|
379
|
-
return this._arr?.[index]
|
|
380
|
-
},
|
|
381
|
-
})
|
|
382
|
-
const instance = new IndexableObj() as any
|
|
383
|
-
expect(() => {
|
|
384
|
-
instance[0] = 'fail'
|
|
385
|
-
}).toThrow('Indexable class has read-only numeric index access')
|
|
386
|
-
})
|
|
387
|
-
})
|
|
388
|
-
})
|
package/src/indexable.ts
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
export const getAt = Symbol('getAt')
|
|
2
|
-
export const setAt = Symbol('setAt')
|
|
3
|
-
|
|
4
|
-
interface IndexingAt<Items = any> {
|
|
5
|
-
[getAt](index: number): Items
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
interface Accessor<T, Items> {
|
|
9
|
-
get(this: T, index: number): Items
|
|
10
|
-
set?(this: T, index: number, value: Items): void
|
|
11
|
-
getLength?(this: T): number
|
|
12
|
-
setLength?(this: T, value: number): void
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
abstract class AbstractGetAt<Items = any> {
|
|
16
|
-
abstract [getAt](index: number): Items
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function Indexable<Items, Base extends abstract new (...args: any[]) => any>(
|
|
20
|
-
base: Base,
|
|
21
|
-
accessor: Accessor<InstanceType<Base>, Items>
|
|
22
|
-
): new (
|
|
23
|
-
...args: ConstructorParameters<Base>
|
|
24
|
-
) => InstanceType<Base> & { [x: number]: Items }
|
|
25
|
-
|
|
26
|
-
export function Indexable<Items>(accessor: Accessor<any, Items>): new () => { [x: number]: Items }
|
|
27
|
-
|
|
28
|
-
export function Indexable<Base extends new (...args: any[]) => IndexingAt>(
|
|
29
|
-
base: Base
|
|
30
|
-
): new (
|
|
31
|
-
...args: ConstructorParameters<Base>
|
|
32
|
-
) => InstanceType<Base> & { [x: number]: AtReturnType<InstanceType<Base>> }
|
|
33
|
-
|
|
34
|
-
export function Indexable<Items>(): abstract new (
|
|
35
|
-
...args: any[]
|
|
36
|
-
) => AbstractGetAt & { [x: number]: Items }
|
|
37
|
-
|
|
38
|
-
export function Indexable<Items, Base extends abstract new (...args: any[]) => any>(
|
|
39
|
-
base?: Base | Accessor<Base, Items>,
|
|
40
|
-
accessor?: Accessor<Base, Items>
|
|
41
|
-
) {
|
|
42
|
-
if (base && typeof base !== 'function') {
|
|
43
|
-
accessor = base as Accessor<Base, Items>
|
|
44
|
-
base = undefined
|
|
45
|
-
}
|
|
46
|
-
if (!base) {
|
|
47
|
-
//@ts-expect-error
|
|
48
|
-
base = class {} as Base
|
|
49
|
-
}
|
|
50
|
-
if (!accessor) {
|
|
51
|
-
accessor = {
|
|
52
|
-
get(this: any, index: number) {
|
|
53
|
-
if (typeof this[getAt] !== 'function') {
|
|
54
|
-
throw new Error('Indexable class must have an [getAt] method')
|
|
55
|
-
}
|
|
56
|
-
return this[getAt](index)
|
|
57
|
-
},
|
|
58
|
-
set(this: any, index: number, value: Items) {
|
|
59
|
-
if (typeof this[setAt] !== 'function') {
|
|
60
|
-
throw new Error('Indexable class has read-only numeric index access')
|
|
61
|
-
}
|
|
62
|
-
this[setAt](index, value)
|
|
63
|
-
},
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
abstract class Indexable extends (base as Base) {
|
|
68
|
-
[x: number]: Items
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
Object.setPrototypeOf(
|
|
72
|
-
Indexable.prototype,
|
|
73
|
-
new Proxy((base as Base).prototype, {
|
|
74
|
-
//@ts-expect-error
|
|
75
|
-
[Symbol.toStringTag]: 'MutTs Indexable',
|
|
76
|
-
get(target, prop, receiver) {
|
|
77
|
-
if (prop in target) {
|
|
78
|
-
const getter = Object.getOwnPropertyDescriptor(target, prop)?.get
|
|
79
|
-
return getter ? getter.call(receiver) : target[prop]
|
|
80
|
-
}
|
|
81
|
-
if (typeof prop === 'string') {
|
|
82
|
-
if (prop === 'length' && accessor.getLength) return accessor.getLength.call(receiver)
|
|
83
|
-
const numProp = Number(prop)
|
|
84
|
-
if (!Number.isNaN(numProp)) {
|
|
85
|
-
return accessor.get!.call(receiver, numProp) as Items
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return undefined
|
|
89
|
-
},
|
|
90
|
-
set(target, prop, value, receiver) {
|
|
91
|
-
if (prop in target) {
|
|
92
|
-
const setter = Object.getOwnPropertyDescriptor(target, prop)?.set
|
|
93
|
-
if (setter) setter.call(receiver, value)
|
|
94
|
-
else target[prop] = value
|
|
95
|
-
return true
|
|
96
|
-
}
|
|
97
|
-
if (typeof prop === 'string') {
|
|
98
|
-
if (prop === 'length' && accessor.setLength) {
|
|
99
|
-
accessor.setLength.call(receiver, value)
|
|
100
|
-
return true
|
|
101
|
-
}
|
|
102
|
-
const numProp = Number(prop)
|
|
103
|
-
if (!Number.isNaN(numProp)) {
|
|
104
|
-
if (!accessor.set) {
|
|
105
|
-
throw new Error('Indexable class has read-only numeric index access')
|
|
106
|
-
}
|
|
107
|
-
accessor.set!.call(receiver, numProp, value)
|
|
108
|
-
return true
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
Object.defineProperty(receiver, prop, {
|
|
112
|
-
value,
|
|
113
|
-
writable: true,
|
|
114
|
-
enumerable: true,
|
|
115
|
-
configurable: true,
|
|
116
|
-
})
|
|
117
|
-
return true
|
|
118
|
-
},
|
|
119
|
-
})
|
|
120
|
-
)
|
|
121
|
-
return Indexable
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
type AtReturnType<T> = T extends { [getAt](index: number): infer R } ? R : never
|
package/src/promiseChain.test.ts
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
import { chainPromise } from './promiseChain'
|
|
2
|
-
|
|
3
|
-
describe('chain', () => {
|
|
4
|
-
describe('basic functionality', () => {
|
|
5
|
-
it('should return non-promise values as-is', () => {
|
|
6
|
-
const value = { name: 'test', value: 42 }
|
|
7
|
-
const result = chainPromise(value)
|
|
8
|
-
expect(result).toEqual(value)
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
it('should chain method calls on resolved promise values', async () => {
|
|
12
|
-
const obj = {
|
|
13
|
-
getName: () => 'John',
|
|
14
|
-
getAge: () => 30,
|
|
15
|
-
getInfo: () => ({ name: 'John', age: 30 }),
|
|
16
|
-
}
|
|
17
|
-
const promise = Promise.resolve(obj)
|
|
18
|
-
|
|
19
|
-
const result = await chainPromise(promise).getName()
|
|
20
|
-
expect(result).toBe('John')
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
it('should support multiple method chaining', async () => {
|
|
24
|
-
const obj = {
|
|
25
|
-
getName: () => 'John',
|
|
26
|
-
getAge: () => 30,
|
|
27
|
-
getInfo: () => ({ name: 'John', age: 30 }),
|
|
28
|
-
}
|
|
29
|
-
const promise = Promise.resolve(obj)
|
|
30
|
-
|
|
31
|
-
const result = await chainPromise(promise).getInfo().name
|
|
32
|
-
expect(result).toBe('John')
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it('should handle async methods', async () => {
|
|
36
|
-
const obj = {
|
|
37
|
-
getName: async () => 'John',
|
|
38
|
-
getAge: async () => 30,
|
|
39
|
-
}
|
|
40
|
-
const promise = Promise.resolve(obj)
|
|
41
|
-
|
|
42
|
-
//const result = await chainPromise(promise).getName()
|
|
43
|
-
const chained = chainPromise(promise)
|
|
44
|
-
const getName = chained.getName
|
|
45
|
-
const result = await getName()
|
|
46
|
-
expect(result).toBe('John')
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('should handle methods with parameters', async () => {
|
|
50
|
-
const obj = {
|
|
51
|
-
add: (a: number, b: number) => a + b,
|
|
52
|
-
multiply: (a: number, b: number) => a * b,
|
|
53
|
-
}
|
|
54
|
-
const promise = Promise.resolve(obj)
|
|
55
|
-
|
|
56
|
-
const result = await chainPromise(promise).add(5, 3)
|
|
57
|
-
expect(result).toBe(8)
|
|
58
|
-
})
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
describe('function calls', () => {
|
|
62
|
-
it('should allow calling the resolved value as a function', async () => {
|
|
63
|
-
const func = (name: string, age: number) => `${name} is ${age} years old`
|
|
64
|
-
const promise = Promise.resolve(func)
|
|
65
|
-
|
|
66
|
-
const result = await (chainPromise(promise) as any)('John', 30)
|
|
67
|
-
expect(result).toBe('John is 30 years old')
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('should handle async functions', async () => {
|
|
71
|
-
const asyncFunc = async (name: string) => `Hello ${name}`
|
|
72
|
-
const promise = Promise.resolve(asyncFunc)
|
|
73
|
-
|
|
74
|
-
const result = await (chainPromise(promise) as any)('World')
|
|
75
|
-
expect(result).toBe('Hello World')
|
|
76
|
-
})
|
|
77
|
-
})
|
|
78
|
-
/*
|
|
79
|
-
describe('caching behavior', () => {
|
|
80
|
-
it('should return the same proxy for the same promise', () => {
|
|
81
|
-
const promise = Promise.resolve({ test: () => 'value' })
|
|
82
|
-
const chain1 = chainPromise(promise)
|
|
83
|
-
const chain2 = chainPromise(promise)
|
|
84
|
-
|
|
85
|
-
expect(chain1).toBe(chain2)
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
it('should not cache different promises', async () => {
|
|
89
|
-
const promise1 = Promise.resolve({ test: () => 'value1' })
|
|
90
|
-
const promise2 = Promise.resolve({ test: () => 'value2' })
|
|
91
|
-
const chain1 = chainPromise(promise1)
|
|
92
|
-
const chain2 = chainPromise(promise2)
|
|
93
|
-
|
|
94
|
-
expect(chain1).not.toBe(chain2)
|
|
95
|
-
expect(await chain1).not.toBe(await chain2)
|
|
96
|
-
})
|
|
97
|
-
})*/
|
|
98
|
-
|
|
99
|
-
describe('complex scenarios', () => {
|
|
100
|
-
it('should handle nested object methods', async () => {
|
|
101
|
-
const obj = {
|
|
102
|
-
user: {
|
|
103
|
-
getName: () => 'John',
|
|
104
|
-
getProfile: () => ({
|
|
105
|
-
age: 30,
|
|
106
|
-
email: 'john@example.com',
|
|
107
|
-
}),
|
|
108
|
-
},
|
|
109
|
-
}
|
|
110
|
-
const promise = Promise.resolve(obj)
|
|
111
|
-
|
|
112
|
-
const result = await chainPromise(promise).user.getProfile().email
|
|
113
|
-
expect(result).toBe('john@example.com')
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
it('should handle array methods', async () => {
|
|
117
|
-
const arr = [1, 2, 3, 4, 5]
|
|
118
|
-
const promise = Promise.resolve(arr)
|
|
119
|
-
|
|
120
|
-
//const result = await chainPromise(promise).filter(x => x > 2).map(x => x * 2)
|
|
121
|
-
|
|
122
|
-
const chained = chainPromise(promise)
|
|
123
|
-
const filtered = chained.filter((x) => {
|
|
124
|
-
return x > 2
|
|
125
|
-
})
|
|
126
|
-
const result = filtered.map((x) => {
|
|
127
|
-
return x * 2
|
|
128
|
-
})
|
|
129
|
-
const awaitResult = await result
|
|
130
|
-
expect(awaitResult).toEqual([6, 8, 10])
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
it('should handle methods that return promises', async () => {
|
|
134
|
-
const obj = {
|
|
135
|
-
getData: () => Promise.resolve({ id: 1, name: 'test' }),
|
|
136
|
-
processData: (data: any) => Promise.resolve({ ...data, processed: true }),
|
|
137
|
-
}
|
|
138
|
-
const promise = Promise.resolve(obj)
|
|
139
|
-
|
|
140
|
-
const result = await chainPromise(promise).getData().name
|
|
141
|
-
expect(result).toBe('test')
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
it('should handle methods that return promises recursively', async () => {
|
|
145
|
-
const obj = {
|
|
146
|
-
getData: () => Promise.resolve({ id: 1, name: 'test' }),
|
|
147
|
-
processData: (data: any) => Promise.resolve({ ...data, processed: true }),
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
//const result = await chainPromise(obj).getData().name
|
|
151
|
-
const chain = chainPromise(obj)
|
|
152
|
-
const getData = chain.getData
|
|
153
|
-
const data = getData()
|
|
154
|
-
const result = await data.name
|
|
155
|
-
expect(result).toBe('test')
|
|
156
|
-
})
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
describe('error handling', () => {
|
|
160
|
-
it('should handle methods that throw errors', async () => {
|
|
161
|
-
const obj = {
|
|
162
|
-
errorMethod: () => {
|
|
163
|
-
throw new Error('Method error')
|
|
164
|
-
},
|
|
165
|
-
}
|
|
166
|
-
const promise = Promise.resolve(obj)
|
|
167
|
-
|
|
168
|
-
await expect(chainPromise(promise).errorMethod()).rejects.toThrow('Method error')
|
|
169
|
-
})
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
describe('edge cases', () => {
|
|
173
|
-
it('should handle null and undefined values', () => {
|
|
174
|
-
expect(chainPromise(null)).toBeNull()
|
|
175
|
-
expect(chainPromise(undefined)).toBeUndefined()
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
it('should handle primitive values', () => {
|
|
179
|
-
expect(chainPromise(42)).toBe(42)
|
|
180
|
-
expect(chainPromise('string')).toBe('string')
|
|
181
|
-
expect(chainPromise(true)).toBe(true)
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
it('should handle objects without methods', async () => {
|
|
185
|
-
const obj = { name: 'test', value: 42 }
|
|
186
|
-
const promise = Promise.resolve(obj)
|
|
187
|
-
|
|
188
|
-
const result = await chainPromise(promise).name
|
|
189
|
-
expect(result).toBe('test')
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
it('should handle symbols as property names', async () => {
|
|
193
|
-
const sym = Symbol('test')
|
|
194
|
-
const obj = { [sym]: () => 'symbol value' }
|
|
195
|
-
const promise = Promise.resolve(obj)
|
|
196
|
-
|
|
197
|
-
const result = await chainPromise(promise)[sym]()
|
|
198
|
-
expect(result).toBe('symbol value')
|
|
199
|
-
})
|
|
200
|
-
})
|
|
201
|
-
})
|