mutts 1.0.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.
Files changed (82) hide show
  1. package/README.md +150 -0
  2. package/dist/chunks/decorator-BXsign4Z.js +176 -0
  3. package/dist/chunks/decorator-BXsign4Z.js.map +1 -0
  4. package/dist/chunks/decorator-CPbZNnsX.esm.js +168 -0
  5. package/dist/chunks/decorator-CPbZNnsX.esm.js.map +1 -0
  6. package/dist/decorator.d.ts +50 -0
  7. package/dist/decorator.esm.js +2 -0
  8. package/dist/decorator.esm.js.map +1 -0
  9. package/dist/decorator.js +11 -0
  10. package/dist/decorator.js.map +1 -0
  11. package/dist/destroyable.d.ts +48 -0
  12. package/dist/destroyable.esm.js +91 -0
  13. package/dist/destroyable.esm.js.map +1 -0
  14. package/dist/destroyable.js +98 -0
  15. package/dist/destroyable.js.map +1 -0
  16. package/dist/eventful.d.ts +11 -0
  17. package/dist/eventful.esm.js +88 -0
  18. package/dist/eventful.esm.js.map +1 -0
  19. package/dist/eventful.js +90 -0
  20. package/dist/eventful.js.map +1 -0
  21. package/dist/index.d.ts +15 -0
  22. package/dist/index.esm.js +7 -0
  23. package/dist/index.esm.js.map +1 -0
  24. package/dist/index.js +52 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/indexable.d.ts +31 -0
  27. package/dist/indexable.esm.js +85 -0
  28. package/dist/indexable.esm.js.map +1 -0
  29. package/dist/indexable.js +89 -0
  30. package/dist/indexable.js.map +1 -0
  31. package/dist/mutts.umd.js +2 -0
  32. package/dist/mutts.umd.js.map +1 -0
  33. package/dist/mutts.umd.min.js +2 -0
  34. package/dist/mutts.umd.min.js.map +1 -0
  35. package/dist/promiseChain.d.ts +11 -0
  36. package/dist/promiseChain.esm.js +72 -0
  37. package/dist/promiseChain.esm.js.map +1 -0
  38. package/dist/promiseChain.js +74 -0
  39. package/dist/promiseChain.js.map +1 -0
  40. package/dist/reactive.d.ts +114 -0
  41. package/dist/reactive.esm.js +1455 -0
  42. package/dist/reactive.esm.js.map +1 -0
  43. package/dist/reactive.js +1472 -0
  44. package/dist/reactive.js.map +1 -0
  45. package/dist/std-decorators.d.ts +17 -0
  46. package/dist/std-decorators.esm.js +161 -0
  47. package/dist/std-decorators.esm.js.map +1 -0
  48. package/dist/std-decorators.js +169 -0
  49. package/dist/std-decorators.js.map +1 -0
  50. package/docs/decorator.md +300 -0
  51. package/docs/destroyable.md +294 -0
  52. package/docs/events.md +225 -0
  53. package/docs/indexable.md +561 -0
  54. package/docs/promiseChain.md +218 -0
  55. package/docs/reactive.md +2072 -0
  56. package/docs/std-decorators.md +558 -0
  57. package/package.json +132 -0
  58. package/src/decorator.test.ts +495 -0
  59. package/src/decorator.ts +205 -0
  60. package/src/destroyable.test.ts +155 -0
  61. package/src/destroyable.ts +158 -0
  62. package/src/eventful.test.ts +380 -0
  63. package/src/eventful.ts +69 -0
  64. package/src/index.ts +7 -0
  65. package/src/indexable.test.ts +388 -0
  66. package/src/indexable.ts +124 -0
  67. package/src/promiseChain.test.ts +201 -0
  68. package/src/promiseChain.ts +99 -0
  69. package/src/reactive/array.test.ts +923 -0
  70. package/src/reactive/array.ts +352 -0
  71. package/src/reactive/core.test.ts +1663 -0
  72. package/src/reactive/core.ts +866 -0
  73. package/src/reactive/index.ts +28 -0
  74. package/src/reactive/interface.test.ts +1477 -0
  75. package/src/reactive/interface.ts +231 -0
  76. package/src/reactive/map.test.ts +866 -0
  77. package/src/reactive/map.ts +162 -0
  78. package/src/reactive/set.test.ts +289 -0
  79. package/src/reactive/set.ts +142 -0
  80. package/src/std-decorators.test.ts +679 -0
  81. package/src/std-decorators.ts +182 -0
  82. package/src/utils.ts +52 -0
@@ -0,0 +1,201 @@
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
+ })
@@ -0,0 +1,99 @@
1
+ type Resolved<T> = T extends Promise<infer U>
2
+ ? Resolved<U>
3
+ : T extends (...args: infer Args) => infer R
4
+ ? (...args: Args) => Resolved<R>
5
+ : T extends object
6
+ ? {
7
+ [k in keyof T]: k extends 'then' | 'catch' | 'finally' ? T[k] : Resolved<T[k]>
8
+ }
9
+ : T
10
+ type PromiseAnd<T> = Resolved<T> & Promise<Resolved<T>>
11
+ export type PromiseChain<T> = T extends (...args: infer Args) => infer R
12
+ ? PromiseAnd<(...args: Args) => PromiseChain<Resolved<R>>>
13
+ : T extends object
14
+ ? PromiseAnd<{
15
+ [k in keyof T]: k extends 'then' | 'catch' | 'finally' ? T[k] : PromiseChain<Resolved<T[k]>>
16
+ }>
17
+ : Promise<Resolved<T>>
18
+
19
+ const forward =
20
+ (name: string, target: any) =>
21
+ (...args: any[]) => {
22
+ return target[name](...args)
23
+ }
24
+
25
+ const alreadyChained = new WeakMap<any, PromiseChain<any>>()
26
+ const originals = new WeakMap<Promise<any>, any>()
27
+
28
+ function cache(target: any, rv: PromiseChain<any>) {
29
+ originals.set(rv, target)
30
+ alreadyChained.set(target, rv)
31
+ }
32
+
33
+ type ChainedFunction<T> = ((...args: any[]) => PromiseChain<T>) & {
34
+ then: Promise<T>['then']
35
+ catch: Promise<T>['catch']
36
+ finally: Promise<T>['finally']
37
+ }
38
+
39
+ const promiseProxyHandler: ProxyHandler<ChainedFunction<any>> = {
40
+ //@ts-expect-error
41
+ [Symbol.toStringTag]: 'MutTs PromiseChain function',
42
+ get(target, prop) {
43
+ if (prop === Symbol.toStringTag) return 'PromiseProxy'
44
+ if (typeof prop === 'string' && ['then', 'catch', 'finally'].includes(prop))
45
+ return target[prop as keyof typeof target]
46
+ return chainPromise(target.then((r) => r[prop as keyof typeof r]))
47
+ },
48
+ }
49
+ const promiseForward = (target: any) => ({
50
+ // biome-ignore lint/suspicious/noThenProperty: This one is the whole point
51
+ then: forward('then', target),
52
+ catch: forward('catch', target),
53
+ finally: forward('finally', target),
54
+ })
55
+ const objectProxyHandler: ProxyHandler<any> = {
56
+ //@ts-expect-error
57
+ [Symbol.toStringTag]: 'MutTs PromiseChain object',
58
+ get(target, prop, receiver) {
59
+ const getter = Object.getOwnPropertyDescriptor(target, prop)?.get
60
+ const rv = getter ? getter.call(receiver) : target[prop]
61
+ // Allows fct.call or fct.apply to bypass the chain system
62
+ if (typeof target === 'function') return rv
63
+ return chainPromise(rv)
64
+ },
65
+ apply(target, thisArg, args) {
66
+ return chainPromise(target.apply(thisArg, args))
67
+ },
68
+ }
69
+ function chainObject<T extends object | Function>(given: T): PromiseChain<T> {
70
+ const rv = new Proxy(given, objectProxyHandler) as PromiseChain<T>
71
+ cache(given, rv)
72
+ return rv
73
+ }
74
+
75
+ function chainable(x: any): x is object | Function {
76
+ return x && ['function', 'object'].includes(typeof x)
77
+ }
78
+ export function chainPromise<T>(given: Promise<T> | T): PromiseChain<T> {
79
+ if (!chainable(given)) return given as PromiseChain<T>
80
+ if (alreadyChained.has(given)) return alreadyChained.get(given) as PromiseChain<T>
81
+ if (!(given instanceof Promise)) return chainObject(given)
82
+ // @ts-expect-error It's ok as we check if it's an object above
83
+ given = given.then((r) => (chainable(r) ? chainObject(r) : r))
84
+ const target = Object.assign(function (this: any, ...args: any[]) {
85
+ return chainPromise(
86
+ given.then((r) => {
87
+ return this?.then
88
+ ? this.then((t: any) => (r as any).apply(t, args))
89
+ : (r as any).apply(this, args)
90
+ })
91
+ )
92
+ }, promiseForward(given)) as ChainedFunction<T>
93
+ const chained = new Proxy(
94
+ target,
95
+ promiseProxyHandler as ProxyHandler<ChainedFunction<T>>
96
+ ) as PromiseChain<T>
97
+ cache(given, chained as PromiseChain<any>)
98
+ return chained
99
+ }