@zeix/cause-effect 0.16.1 → 0.17.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.
Files changed (66) hide show
  1. package/.ai-context.md +85 -21
  2. package/.cursorrules +11 -5
  3. package/.github/copilot-instructions.md +64 -13
  4. package/CLAUDE.md +143 -163
  5. package/LICENSE +1 -1
  6. package/README.md +248 -333
  7. package/archive/benchmark.ts +688 -0
  8. package/archive/collection.ts +312 -0
  9. package/{src → archive}/computed.ts +21 -21
  10. package/archive/list.ts +551 -0
  11. package/archive/memo.ts +139 -0
  12. package/{src → archive}/state.ts +13 -11
  13. package/archive/store.ts +368 -0
  14. package/archive/task.ts +194 -0
  15. package/eslint.config.js +1 -0
  16. package/index.dev.js +938 -509
  17. package/index.js +1 -1
  18. package/index.ts +50 -23
  19. package/package.json +1 -1
  20. package/src/classes/collection.ts +282 -0
  21. package/src/classes/composite.ts +176 -0
  22. package/src/classes/computed.ts +333 -0
  23. package/src/classes/list.ts +305 -0
  24. package/src/classes/ref.ts +68 -0
  25. package/src/classes/state.ts +98 -0
  26. package/src/classes/store.ts +210 -0
  27. package/src/diff.ts +26 -53
  28. package/src/effect.ts +9 -9
  29. package/src/errors.ts +71 -25
  30. package/src/match.ts +5 -12
  31. package/src/resolve.ts +3 -2
  32. package/src/signal.ts +58 -41
  33. package/src/system.ts +79 -42
  34. package/src/util.ts +16 -34
  35. package/test/batch.test.ts +15 -17
  36. package/test/benchmark.test.ts +4 -4
  37. package/test/collection.test.ts +853 -0
  38. package/test/computed.test.ts +138 -130
  39. package/test/diff.test.ts +2 -2
  40. package/test/effect.test.ts +36 -35
  41. package/test/list.test.ts +754 -0
  42. package/test/match.test.ts +25 -25
  43. package/test/ref.test.ts +227 -0
  44. package/test/resolve.test.ts +17 -19
  45. package/test/signal.test.ts +70 -119
  46. package/test/state.test.ts +44 -44
  47. package/test/store.test.ts +253 -929
  48. package/types/index.d.ts +12 -9
  49. package/types/src/classes/collection.d.ts +46 -0
  50. package/types/src/classes/composite.d.ts +15 -0
  51. package/types/src/classes/computed.d.ts +97 -0
  52. package/types/src/classes/list.d.ts +41 -0
  53. package/types/src/classes/ref.d.ts +39 -0
  54. package/types/src/classes/state.d.ts +52 -0
  55. package/types/src/classes/store.d.ts +51 -0
  56. package/types/src/diff.d.ts +8 -12
  57. package/types/src/errors.d.ts +17 -11
  58. package/types/src/signal.d.ts +27 -14
  59. package/types/src/system.d.ts +41 -20
  60. package/types/src/util.d.ts +6 -4
  61. package/src/store.ts +0 -474
  62. package/types/src/collection.d.ts +0 -26
  63. package/types/src/computed.d.ts +0 -33
  64. package/types/src/scheduler.d.ts +0 -55
  65. package/types/src/state.d.ts +0 -24
  66. package/types/src/store.d.ts +0 -65
@@ -1,7 +1,11 @@
1
- import { isEqual } from './diff'
2
- import { InvalidCallbackError, NullishSignalValueError } from './errors'
3
- import { notify, subscribe, type Watcher } from './system'
4
- import { isFunction, isObjectOfType, UNSET, valueString } from './util'
1
+ import { isEqual } from '../src/diff'
2
+ import { InvalidCallbackError, NullishSignalValueError } from '../src/errors'
3
+ import {
4
+ notifyWatchers,
5
+ subscribeActiveWatcher,
6
+ type Watcher,
7
+ } from '../src/system'
8
+ import { isFunction, isObjectOfType, UNSET } from '../src/util'
5
9
 
6
10
  /* === Types === */
7
11
 
@@ -14,7 +18,7 @@ type State<T extends {}> = {
14
18
 
15
19
  /* === Constants === */
16
20
 
17
- const TYPE_STATE = 'State'
21
+ const TYPE_STATE = 'State' as const
18
22
 
19
23
  /* === Functions === */
20
24
 
@@ -35,7 +39,7 @@ const createState = /*#__PURE__*/ <T extends {}>(initialValue: T): State<T> => {
35
39
  if (newValue == null) throw new NullishSignalValueError('state')
36
40
  if (isEqual(value, newValue)) return
37
41
  value = newValue
38
- notify(watchers)
42
+ notifyWatchers(watchers)
39
43
 
40
44
  // Setting to UNSET clears the watchers so the signal can be garbage collected
41
45
  if (UNSET === value) watchers.clear()
@@ -48,7 +52,7 @@ const createState = /*#__PURE__*/ <T extends {}>(initialValue: T): State<T> => {
48
52
  },
49
53
  get: {
50
54
  value: () => {
51
- subscribe(watchers)
55
+ subscribeActiveWatcher(watchers)
52
56
  return value
53
57
  },
54
58
  },
@@ -60,10 +64,8 @@ const createState = /*#__PURE__*/ <T extends {}>(initialValue: T): State<T> => {
60
64
  update: {
61
65
  value: (updater: (oldValue: T) => T) => {
62
66
  if (!isFunction(updater))
63
- throw new InvalidCallbackError(
64
- 'state update',
65
- valueString(updater),
66
- )
67
+ throw new InvalidCallbackError('state update', updater)
68
+
67
69
  setValue(updater(value))
68
70
  },
69
71
  },
@@ -0,0 +1,368 @@
1
+ import { type DiffResult, diff, type UnknownRecord } from '../src/diff'
2
+ import {
3
+ DuplicateKeyError,
4
+ InvalidSignalValueError,
5
+ NullishSignalValueError,
6
+ ReadonlySignalError,
7
+ } from '../src/errors'
8
+ import { isMutableSignal, type MutableSignal } from '../src/signal'
9
+ import {
10
+ batchSignalWrites,
11
+ type Cleanup,
12
+ createWatcher,
13
+ emitNotification,
14
+ type Listener,
15
+ type Listeners,
16
+ type Notifications,
17
+ notifyWatchers,
18
+ subscribeActiveWatcher,
19
+ trackSignalReads,
20
+ type Watcher,
21
+ } from '../src/system'
22
+ import {
23
+ isFunction,
24
+ isObjectOfType,
25
+ isRecord,
26
+ isSymbol,
27
+ UNSET,
28
+ } from '../src/util'
29
+ import { isComputed } from './computed'
30
+ import { createList, isList, type List } from './list'
31
+ import { createState, isState, type State } from './state'
32
+
33
+ /* === Types === */
34
+
35
+ type StoreKeySignal<T extends {}> = T extends readonly (infer U extends {})[]
36
+ ? List<U>
37
+ : T extends UnknownRecord
38
+ ? Store<T>
39
+ : State<T>
40
+
41
+ type Store<T extends UnknownRecord> = {
42
+ [K in keyof T]: T[K] extends readonly (infer U extends {})[]
43
+ ? List<U>
44
+ : T extends Record<string, unknown>
45
+ ? Store<T[K] & {}>
46
+ : State<T[K] & {}>
47
+ } & {
48
+ readonly [Symbol.toStringTag]: 'Store'
49
+ [Symbol.iterator](): IterableIterator<
50
+ [
51
+ Extract<keyof T, string>,
52
+ StoreKeySignal<T[Extract<keyof T, string>] & {}>,
53
+ ]
54
+ >
55
+ add<K extends Extract<keyof T, string>>(key: K, value: T[K]): K
56
+ byKey<K extends Extract<keyof T, string>>(key: K): StoreKeySignal<T[K] & {}>
57
+ get(): T
58
+ keyAt(index: number): string | undefined
59
+ indexOfKey(key: string): number
60
+ set(value: T): void
61
+ update(fn: (value: T) => T): void
62
+ sort<U = T[Extract<keyof T, string>]>(
63
+ compareFn?: (a: U, b: U) => number,
64
+ ): void
65
+ on<K extends keyof Notifications>(type: K, listener: Listener<K>): Cleanup
66
+ remove<K extends Extract<keyof T, string>>(key: K): void
67
+ }
68
+
69
+ /* === Constants === */
70
+
71
+ const TYPE_STORE = 'Store' as const
72
+
73
+ /* === Functions === */
74
+
75
+ /**
76
+ * Create a new store with deeply nested reactive properties
77
+ *
78
+ * Supports both objects and arrays as initial values. Arrays are converted
79
+ * to records internally for storage but maintain their array type through
80
+ * the .get() method, which automatically converts objects with consecutive
81
+ * numeric keys back to arrays.
82
+ *
83
+ * For array-like stores, an optional keyConfig parameter can be provided to
84
+ * generate stable keys for array items. This creates persistent references
85
+ * that remain stable across sort and compact operations.
86
+ *
87
+ * @since 0.15.0
88
+ * @param {T} initialValue - initial object or array value of the store
89
+ * @returns {Store<T>} - new store with reactive properties that preserves the original type T
90
+ */
91
+ const createStore = <T extends UnknownRecord>(initialValue: T): Store<T> => {
92
+ if (initialValue == null) throw new NullishSignalValueError('store')
93
+
94
+ const watchers = new Set<Watcher>()
95
+ const listeners: Omit<Listeners, 'sort'> = {
96
+ add: new Set<Listener<'add'>>(),
97
+ change: new Set<Listener<'change'>>(),
98
+ remove: new Set<Listener<'remove'>>(),
99
+ }
100
+ const signals = new Map<
101
+ string,
102
+ MutableSignal<T[Extract<keyof T, string>] & {}>
103
+ >()
104
+ const ownWatchers = new Map<string, Watcher>()
105
+
106
+ // Get current record
107
+ const current = (): T => {
108
+ const record = {} as Record<string, unknown>
109
+ for (const [key, signal] of signals) record[key] = signal.get()
110
+ return record as T
111
+ }
112
+
113
+ // Validate input
114
+ const isValidValue = <T>(
115
+ key: string,
116
+ value: T,
117
+ ): value is NonNullable<T> => {
118
+ if (value == null)
119
+ throw new NullishSignalValueError(`store for key "${key}"`)
120
+ if (value === UNSET) return true
121
+ if (isSymbol(value) || isFunction(value) || isComputed(value))
122
+ throw new InvalidSignalValueError(`store for key "${key}"`, value)
123
+ return true
124
+ }
125
+
126
+ // Add own watcher for nested signal
127
+ const addOwnWatcher = <K extends keyof T & string>(
128
+ key: K,
129
+ signal: MutableSignal<T[K] & {}>,
130
+ ) => {
131
+ const watcher = createWatcher(() => {
132
+ trackSignalReads(watcher, () => {
133
+ signal.get() // Subscribe to the signal
134
+ emitNotification(listeners.change, [key])
135
+ })
136
+ })
137
+ ownWatchers.set(key, watcher)
138
+ }
139
+
140
+ // Add nested signal and effect
141
+ const addProperty = <K extends keyof T & string>(
142
+ key: K,
143
+ value: T[K],
144
+ single = false,
145
+ ): boolean => {
146
+ if (!isValidValue(key, value)) return false
147
+
148
+ // Create signal for key
149
+ // @ts-expect-error non-matching signal types
150
+ const signal: MutableSignal<T[K] & {}> =
151
+ isState(value) || isStore(value) || isList(value)
152
+ ? (value as unknown as MutableSignal<T[K] & {}>)
153
+ : isRecord(value)
154
+ ? createStore(value)
155
+ : Array.isArray(value)
156
+ ? createList(value)
157
+ : createState(value)
158
+
159
+ // Set internal states
160
+ // @ts-expect-error non-matching signal types
161
+ signals.set(key, signal)
162
+ if (listeners.change.size) addOwnWatcher(key, signal)
163
+
164
+ if (single) {
165
+ notifyWatchers(watchers)
166
+ emitNotification(listeners.add, [key])
167
+ }
168
+ return true
169
+ }
170
+
171
+ // Remove nested signal and effect
172
+ const removeProperty = (key: string, single = false) => {
173
+ // Remove signal for key
174
+ const ok = signals.delete(key)
175
+ if (!ok) return
176
+
177
+ // Clean up internal states
178
+ const watcher = ownWatchers.get(key)
179
+ if (watcher) {
180
+ watcher.stop()
181
+ ownWatchers.delete(key)
182
+ }
183
+
184
+ if (single) {
185
+ notifyWatchers(watchers)
186
+ emitNotification(listeners.remove, [key])
187
+ }
188
+ }
189
+
190
+ // Commit batched changes and emit notifications
191
+ const batchChanges = (changes: DiffResult, initialRun?: boolean) => {
192
+ // Additions
193
+ if (Object.keys(changes.add).length) {
194
+ for (const key in changes.add)
195
+ addProperty(
196
+ key,
197
+ changes.add[key] as T[Extract<keyof T, string>] & {},
198
+ false,
199
+ )
200
+
201
+ // Queue initial additions event to allow listeners to be added first
202
+ if (initialRun)
203
+ setTimeout(() => {
204
+ emitNotification(listeners.add, Object.keys(changes.add))
205
+ }, 0)
206
+ else emitNotification(listeners.add, Object.keys(changes.add))
207
+ }
208
+
209
+ // Changes
210
+ if (Object.keys(changes.change).length) {
211
+ batchSignalWrites(() => {
212
+ for (const key in changes.change) {
213
+ const value = changes.change[key] as T[Extract<
214
+ keyof T,
215
+ string
216
+ >]
217
+ if (!isValidValue(key, value)) continue
218
+
219
+ const signal = signals.get(key)
220
+ if (isMutableSignal(signal)) signal.set(value)
221
+ else throw new ReadonlySignalError(key, value)
222
+ }
223
+ emitNotification(listeners.change, Object.keys(changes.change))
224
+ })
225
+ }
226
+
227
+ // Removals
228
+ if (Object.keys(changes.remove).length) {
229
+ for (const key in changes.remove) removeProperty(key)
230
+ emitNotification(listeners.remove, Object.keys(changes.remove))
231
+ }
232
+
233
+ return changes.changed
234
+ }
235
+
236
+ // Reconcile data and dispatch events
237
+ const reconcile = (
238
+ oldValue: T,
239
+ newValue: T,
240
+ initialRun?: boolean,
241
+ ): boolean => batchChanges(diff(oldValue, newValue), initialRun)
242
+
243
+ // Initialize data
244
+ reconcile({} as T, initialValue, true)
245
+
246
+ // Methods and Properties
247
+ const prototype: Record<PropertyKey, unknown> = {}
248
+ Object.defineProperties(prototype, {
249
+ [Symbol.toStringTag]: {
250
+ value: TYPE_STORE,
251
+ },
252
+ [Symbol.iterator]: {
253
+ value: function* () {
254
+ for (const [key, signal] of signals) yield [key, signal]
255
+ },
256
+ },
257
+ add: {
258
+ value: <K extends Extract<keyof T, string>>(
259
+ key: K,
260
+ value: T[K],
261
+ ): K => {
262
+ if (signals.has(key))
263
+ throw new DuplicateKeyError('store', key, value)
264
+
265
+ addProperty(key, value, true)
266
+ return key
267
+ },
268
+ },
269
+ byKey: {
270
+ value: (key: string) => {
271
+ return signals.get(key)
272
+ },
273
+ },
274
+ get: {
275
+ value: (): T => {
276
+ subscribeActiveWatcher(watchers)
277
+ return current()
278
+ },
279
+ },
280
+ remove: {
281
+ value: (key: string): void => {
282
+ if (signals.has(key)) removeProperty(key, true)
283
+ },
284
+ },
285
+ set: {
286
+ value: (newValue: T): void => {
287
+ if (reconcile(current(), newValue)) {
288
+ notifyWatchers(watchers)
289
+ if (UNSET === newValue) watchers.clear()
290
+ }
291
+ },
292
+ },
293
+ update: {
294
+ value: (fn: (oldValue: T) => T): void => {
295
+ store.set(fn(current()))
296
+ },
297
+ },
298
+ on: {
299
+ value: <K extends keyof Omit<Listeners, 'sort'>>(
300
+ type: K,
301
+ listener: Listener<K>,
302
+ ): Cleanup => {
303
+ listeners[type].add(listener)
304
+ if (type === 'change' && !ownWatchers.size) {
305
+ for (const [key, signal] of signals)
306
+ // @ts-expect-error ignore
307
+ addOwnWatcher(key, signal)
308
+ }
309
+ return () => {
310
+ listeners[type].delete(listener)
311
+ if (type === 'change' && !listeners.change.size) {
312
+ if (ownWatchers.size) {
313
+ for (const watcher of ownWatchers.values())
314
+ watcher.stop()
315
+ ownWatchers.clear()
316
+ }
317
+ }
318
+ }
319
+ },
320
+ },
321
+ })
322
+
323
+ // Return proxy directly with integrated signal methods
324
+ const store = new Proxy(prototype as Store<T>, {
325
+ get(target, prop) {
326
+ if (prop in target) return Reflect.get(target, prop)
327
+ if (!isSymbol(prop)) return signals.get(prop)
328
+ },
329
+ has(target, prop) {
330
+ if (prop in target) return true
331
+ return signals.has(String(prop))
332
+ },
333
+ ownKeys(target) {
334
+ const staticKeys = Reflect.ownKeys(target)
335
+ return [...new Set([...signals.keys(), ...staticKeys])]
336
+ },
337
+ getOwnPropertyDescriptor(target, prop) {
338
+ if (prop in target)
339
+ return Reflect.getOwnPropertyDescriptor(target, prop)
340
+ if (isSymbol(prop)) return undefined
341
+
342
+ const signal = signals.get(prop)
343
+ return signal
344
+ ? {
345
+ enumerable: true,
346
+ configurable: true,
347
+ writable: true,
348
+ value: signal,
349
+ }
350
+ : undefined
351
+ },
352
+ })
353
+ return store
354
+ }
355
+
356
+ /**
357
+ * Check if the provided value is a Store instance
358
+ *
359
+ * @since 0.15.0
360
+ * @param {unknown} value - Value to check
361
+ * @returns {boolean} - True if the value is a Store instance, false otherwise
362
+ */
363
+ const isStore = <T extends UnknownRecord>(value: unknown): value is Store<T> =>
364
+ isObjectOfType(value, TYPE_STORE)
365
+
366
+ /* === Exports === */
367
+
368
+ export { TYPE_STORE, isStore, createStore, type Store }
@@ -0,0 +1,194 @@
1
+ import { isEqual } from '../src/diff'
2
+ import {
3
+ CircularDependencyError,
4
+ createError,
5
+ InvalidCallbackError,
6
+ NullishSignalValueError,
7
+ } from '../src/errors'
8
+ import {
9
+ createWatcher,
10
+ flushPendingReactions,
11
+ notifyWatchers,
12
+ subscribeActiveWatcher,
13
+ trackSignalReads,
14
+ type Watcher,
15
+ } from '../src/system'
16
+ import {
17
+ isAbortError,
18
+ isAsyncFunction,
19
+ isObjectOfType,
20
+ UNSET,
21
+ } from '../src/util'
22
+
23
+ /* === Types === */
24
+
25
+ type Task<T extends {}> = {
26
+ readonly [Symbol.toStringTag]: 'Task'
27
+ get(): T
28
+ }
29
+
30
+ type TaskCallback<T extends {} & { then?: undefined }> = (
31
+ oldValue: T,
32
+ abort: AbortSignal,
33
+ ) => Promise<T>
34
+
35
+ /* === Constants === */
36
+
37
+ const TYPE_TASK = 'Task' as const
38
+
39
+ /* === Functions === */
40
+
41
+ /**
42
+ * Create a derived signal from existing signals
43
+ *
44
+ * @since 0.9.0
45
+ * @param {TaskCallback<T>} callback - Computation callback function
46
+ * @returns {Task<T>} - Computed signal
47
+ */
48
+ const createTask = <T extends {}>(
49
+ callback: TaskCallback<T>,
50
+ initialValue: T = UNSET,
51
+ ): Task<T> => {
52
+ if (!isTaskCallback(callback))
53
+ throw new InvalidCallbackError('task', callback)
54
+ if (initialValue == null) throw new NullishSignalValueError('task')
55
+
56
+ const watchers: Set<Watcher> = new Set()
57
+
58
+ // Internal state
59
+ let value: T = initialValue
60
+ let error: Error | undefined
61
+ let controller: AbortController | undefined
62
+ let dirty = true
63
+ let changed = false
64
+ let computing = false
65
+
66
+ // Functions to update internal state
67
+ const ok = (v: T): undefined => {
68
+ if (!isEqual(v, value)) {
69
+ value = v
70
+ changed = true
71
+ }
72
+ error = undefined
73
+ dirty = false
74
+ }
75
+ const nil = (): undefined => {
76
+ changed = UNSET !== value
77
+ value = UNSET
78
+ error = undefined
79
+ }
80
+ const err = (e: unknown): undefined => {
81
+ const newError = createError(e)
82
+ changed =
83
+ !error ||
84
+ newError.name !== error.name ||
85
+ newError.message !== error.message
86
+ value = UNSET
87
+ error = newError
88
+ }
89
+ const settle =
90
+ <T>(fn: (arg: T) => void) =>
91
+ (arg: T) => {
92
+ computing = false
93
+ controller = undefined
94
+ fn(arg)
95
+ if (changed) notifyWatchers(watchers)
96
+ }
97
+
98
+ // Own watcher: called when notified from sources (push)
99
+ const watcher = createWatcher(() => {
100
+ dirty = true
101
+ controller?.abort()
102
+ if (watchers.size) notifyWatchers(watchers)
103
+ else watcher.stop()
104
+ })
105
+ watcher.onCleanup(() => {
106
+ controller?.abort()
107
+ })
108
+
109
+ // Called when requested by dependencies (pull)
110
+ const compute = () =>
111
+ trackSignalReads(watcher, () => {
112
+ if (computing) throw new CircularDependencyError('computed')
113
+ changed = false
114
+ // Return current value until promise resolves
115
+ if (controller) return value
116
+
117
+ controller = new AbortController()
118
+ controller.signal.addEventListener(
119
+ 'abort',
120
+ () => {
121
+ computing = false
122
+ controller = undefined
123
+ compute() // Retry computation with updated state
124
+ },
125
+ {
126
+ once: true,
127
+ },
128
+ )
129
+ let result: T | Promise<T>
130
+ computing = true
131
+ try {
132
+ result = callback(value, controller.signal)
133
+ } catch (e) {
134
+ if (isAbortError(e)) nil()
135
+ else err(e)
136
+ computing = false
137
+ return
138
+ }
139
+
140
+ if (result instanceof Promise) result.then(settle(ok), settle(err))
141
+ else if (null == result || UNSET === result) nil()
142
+ else ok(result)
143
+ computing = false
144
+ })
145
+
146
+ const task: Record<PropertyKey, unknown> = {}
147
+ Object.defineProperties(task, {
148
+ [Symbol.toStringTag]: {
149
+ value: TYPE_TASK,
150
+ },
151
+ get: {
152
+ value: (): T => {
153
+ subscribeActiveWatcher(watchers)
154
+ flushPendingReactions()
155
+ if (dirty) compute()
156
+ if (error) throw error
157
+ return value
158
+ },
159
+ },
160
+ })
161
+ return task as Task<T>
162
+ }
163
+
164
+ /**
165
+ * Check if a value is a task signal
166
+ *
167
+ * @since 0.9.0
168
+ * @param {unknown} value - Value to check
169
+ * @returns {boolean} - True if value is a task signal, false otherwise
170
+ */
171
+ const isTask = /*#__PURE__*/ <T extends {}>(value: unknown): value is Task<T> =>
172
+ isObjectOfType(value, TYPE_TASK)
173
+
174
+ /**
175
+ * Check if the provided value is a callback that may be used as input for toSignal() to derive a computed state
176
+ *
177
+ * @since 0.12.0
178
+ * @param {unknown} value - Value to check
179
+ * @returns {boolean} - True if value is an async callback, false otherwise
180
+ */
181
+ const isTaskCallback = /*#__PURE__*/ <T extends {}>(
182
+ value: unknown,
183
+ ): value is TaskCallback<T> => isAsyncFunction(value) && value.length < 3
184
+
185
+ /* === Exports === */
186
+
187
+ export {
188
+ TYPE_TASK,
189
+ createTask,
190
+ isTask,
191
+ isTaskCallback,
192
+ type Task,
193
+ type TaskCallback,
194
+ }
package/eslint.config.js CHANGED
@@ -18,6 +18,7 @@ export default [
18
18
  // we know what we're doing ;-)
19
19
  '@typescript-eslint/no-empty-object-type': 'off',
20
20
  '@typescript-eslint/no-explicit-any': 'off',
21
+ '@typescript-eslint/no-this-alias': 'off',
21
22
  '@typescript-eslint/no-unused-vars': [
22
23
  'error',
23
24
  {