@oscarpalmer/mora 0.22.0 → 0.24.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 (44) hide show
  1. package/dist/batch.js +8 -9
  2. package/dist/constants.js +36 -0
  3. package/dist/effect.js +7 -8
  4. package/dist/helpers/is.js +9 -19
  5. package/dist/helpers/proxy.js +12 -7
  6. package/dist/helpers/value.js +7 -8
  7. package/dist/index.js +1 -1
  8. package/dist/models.js +0 -0
  9. package/dist/mora.full.js +248 -148
  10. package/dist/subscription.js +7 -5
  11. package/dist/value/array.js +27 -29
  12. package/dist/value/computed.js +18 -21
  13. package/dist/value/reactive.js +2 -1
  14. package/dist/value/signal.js +2 -2
  15. package/dist/value/store.js +12 -3
  16. package/package.json +9 -11
  17. package/src/batch.ts +11 -13
  18. package/src/constants.ts +53 -0
  19. package/src/effect.ts +12 -17
  20. package/src/helpers/is.ts +45 -22
  21. package/src/helpers/proxy.ts +62 -39
  22. package/src/helpers/value.ts +19 -14
  23. package/src/index.ts +1 -0
  24. package/src/models.ts +66 -0
  25. package/src/subscription.ts +14 -16
  26. package/src/value/array.ts +92 -55
  27. package/src/value/computed.ts +29 -39
  28. package/src/value/reactive.ts +11 -25
  29. package/src/value/signal.ts +9 -3
  30. package/src/value/store.ts +55 -13
  31. package/types/batch.d.ts +3 -5
  32. package/types/constants.d.ts +16 -0
  33. package/types/effect.d.ts +2 -1
  34. package/types/helpers/is.d.ts +23 -10
  35. package/types/helpers/proxy.d.ts +4 -3
  36. package/types/helpers/value.d.ts +1 -1
  37. package/types/index.d.ts +1 -0
  38. package/types/models.d.ts +56 -0
  39. package/types/subscription.d.ts +3 -6
  40. package/types/value/array.d.ts +48 -7
  41. package/types/value/computed.d.ts +2 -11
  42. package/types/value/reactive.d.ts +8 -17
  43. package/types/value/signal.d.ts +7 -1
  44. package/types/value/store.d.ts +38 -7
@@ -4,50 +4,76 @@ import type {
4
4
  PlainObject,
5
5
  } from '@oscarpalmer/atoms/models';
6
6
  import {startBatch, stopBatch} from '../batch';
7
+ import {PROPERTY_LENGTH} from '../constants';
8
+ import type {
9
+ InternalComputed,
10
+ ReactiveState,
11
+ SetValueInProxyParameters,
12
+ } from '../models';
7
13
  import type {ReactiveArray} from '../value/array';
8
14
  import {type Computed, computed} from '../value/computed';
9
- import type {ReactiveState} from '../value/reactive';
10
- import type {Signal} from '../value/signal';
11
15
  import type {Store} from '../value/store';
12
16
  import {emitValue} from './value';
13
17
 
18
+ export function emityProxyValues<Value>(
19
+ state: ReactiveState<Value, Value>,
20
+ mapped: Map<Key, Computed<unknown>>,
21
+ ): void;
22
+
23
+ export function emityProxyValues<Value>(
24
+ state: ReactiveState<Value[], Value>,
25
+ mapped: Map<Key, Computed<unknown>>,
26
+ ): void;
27
+
28
+ export function emityProxyValues(
29
+ state: ReactiveState<unknown, unknown>,
30
+ mapped: Map<Key, Computed<unknown>>,
31
+ ): void {
32
+ const values = [...mapped.values()];
33
+ const {length} = values;
34
+
35
+ for (let index = 0; index < length; index += 1) {
36
+ (values[index] as unknown as InternalComputed).effect.dirty = true;
37
+ }
38
+
39
+ emitValue(state);
40
+ }
41
+
14
42
  export function getReactiveValueInProxy<Value>(
15
- array: ReactiveArray<Value>,
16
- mapped: Map<Key, Computed<unknown>>,
17
- index: number,
18
- isArray: true,
19
- ): Computed<unknown>;
43
+ array: ReactiveArray<Value>,
44
+ mapped: Map<Key, Computed<unknown>>,
45
+ index: number,
46
+ isArray: true,
47
+ ): Computed<unknown>;
20
48
 
21
49
  export function getReactiveValueInProxy<Value extends PlainObject>(
22
- store: Store<Value>,
23
- mapped: Map<Key, Computed<unknown>>,
24
- key: Key,
25
- isArray: false,
26
- ): Computed<unknown>;
50
+ store: Store<Value>,
51
+ mapped: Map<Key, Computed<unknown>>,
52
+ key: Key,
53
+ isArray: false,
54
+ ): Computed<unknown>;
27
55
 
28
56
  export function getReactiveValueInProxy(
29
- reactive: ReactiveArray<unknown> | Store<PlainObject>,
30
- mapped: Map<Key, Computed<unknown>>,
31
- key: Key,
32
- isArray: boolean,
33
- ): Computed<unknown> {
34
- let item = mapped.get(key);
35
-
36
- if (item == null) {
37
- item = computed(() => {
38
- const value = reactive.get();
39
-
40
- return isArray
41
- ? (value as unknown[]).at(key as number)
42
- : (value as PlainObject)[key];
43
- }) as Computed<unknown>;
44
-
45
- mapped.set(key, item);
46
- }
57
+ reactive: ReactiveArray<unknown> | Store<PlainObject>,
58
+ mapped: Map<Key, Computed<unknown>>,
59
+ key: Key,
60
+ isArray: boolean,
61
+ ): Computed<unknown> {
62
+ let item = mapped.get(key);
63
+
64
+ if (item == null) {
65
+ item = computed(() =>
66
+ isArray
67
+ ? (reactive.get() as unknown[]).at(key as number)
68
+ : (reactive.get() as PlainObject)[key],
69
+ ) as Computed<unknown>;
47
70
 
48
- return item;
71
+ mapped.set(key, item);
49
72
  }
50
73
 
74
+ return item;
75
+ }
76
+
51
77
  export function setProxyValue(
52
78
  proxy: ArrayOrPlainObject,
53
79
  value: ArrayOrPlainObject,
@@ -83,18 +109,15 @@ export function setProxyValue(
83
109
  }
84
110
 
85
111
  export function setValueInProxy<Value extends ArrayOrPlainObject, Equal>(
86
- target: Value,
87
- property: PropertyKey,
88
- value: unknown,
89
- state: ReactiveState<Value, Equal>,
90
- isArray: boolean,
91
- length?: Signal<number>,
112
+ parameters: SetValueInProxyParameters<Value, Equal>,
92
113
  ): boolean {
114
+ const {isArray, length, property, state, target, value} = parameters;
115
+
93
116
  if (isArray) {
94
117
  const isIndex = !Number.isNaN(Number(property));
95
- const isLength = property === 'length';
118
+ const isLength = property === PROPERTY_LENGTH;
96
119
 
97
- if (!isIndex && !isLength) {
120
+ if (!(isIndex || isLength)) {
98
121
  return Reflect.set(target, property, value);
99
122
  }
100
123
  }
@@ -1,7 +1,12 @@
1
- import {batchDepth, batchedHandlers, flushHandlers} from '../batch';
2
- import {activeEffect} from '../effect';
3
- import {activeComputed, type InternalComputed} from '../value/computed';
4
- import type {ReactiveState} from '../value/reactive';
1
+ import {flushHandlers} from '../batch';
2
+ import {
3
+ ACTIVE,
4
+ ARRAY_OFFSET,
5
+ ARRAY_PEEK,
6
+ ARRAY_THRESHOLD,
7
+ BATCH,
8
+ } from '../constants';
9
+ import type {InternalComputed, ReactiveState} from '../models';
5
10
 
6
11
  export function emitValue<Value>(state: ReactiveState<Value, never>): void {
7
12
  for (const computed of state.computeds) {
@@ -9,14 +14,14 @@ export function emitValue<Value>(state: ReactiveState<Value, never>): void {
9
14
  }
10
15
 
11
16
  for (const effect of state.effects) {
12
- batchedHandlers.add(effect);
17
+ BATCH.handlers.add(effect);
13
18
  }
14
19
 
15
20
  for (const [, subscription] of state.subscriptions) {
16
- batchedHandlers.add(subscription as never);
21
+ BATCH.handlers.add(subscription);
17
22
  }
18
23
 
19
- if (batchDepth === 0) {
24
+ if (BATCH.depth === 0) {
20
25
  flushHandlers();
21
26
  }
22
27
  }
@@ -34,9 +39,9 @@ export function equalArrays<Value>(
34
39
 
35
40
  let offset = 0;
36
41
 
37
- if (length >= 100) {
38
- offset = Math.round(length / 10);
39
- offset = offset > 25 ? 25 : offset;
42
+ if (length >= ARRAY_THRESHOLD) {
43
+ offset = Math.round(length / ARRAY_PEEK);
44
+ offset = offset > ARRAY_OFFSET ? ARRAY_OFFSET : offset;
40
45
 
41
46
  for (let index = 0; index < offset; index += 1) {
42
47
  if (!state.equal(first[index], second[index])) {
@@ -57,12 +62,12 @@ export function equalArrays<Value>(
57
62
  }
58
63
 
59
64
  export function getValue<Value>(state: ReactiveState<Value, never>): Value {
60
- if (activeComputed != null) {
61
- state.computeds.add(activeComputed);
65
+ if (ACTIVE.computed != null) {
66
+ state.computeds.add(ACTIVE.computed);
62
67
  }
63
68
 
64
- if (activeEffect != null) {
65
- state.effects.add(activeEffect);
69
+ if (ACTIVE.effect != null) {
70
+ state.effects.add(ACTIVE.effect);
66
71
  }
67
72
 
68
73
  return state.value;
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ export {
7
7
  isReactive,
8
8
  isSignal,
9
9
  } from './helpers/is';
10
+ export type {Unsubscribe} from './models';
10
11
  export {array, type ReactiveArray} from './value/array';
11
12
  export {computed, type Computed} from './value/computed';
12
13
  export type {Reactive} from './value/reactive';
package/src/models.ts ADDED
@@ -0,0 +1,66 @@
1
+ import type {GenericCallback} from '@oscarpalmer/atoms/models';
2
+ import type {Effect} from './effect';
3
+ import type {Subscription} from './subscription';
4
+ import type {Computed} from './value/computed';
5
+ import type {Signal} from './value/signal';
6
+
7
+ export type Active = {
8
+ computed?: Computed<unknown>;
9
+ effect?: Effect;
10
+ };
11
+
12
+ export type Batch = {
13
+ depth: number;
14
+ handlers: Set<Effect | Subscription>;
15
+ };
16
+
17
+ export type ComputedEffect = {
18
+ dirty: boolean;
19
+ instance: Effect;
20
+ };
21
+
22
+ export type EffectState = {
23
+ callback: GenericCallback;
24
+ };
25
+
26
+ export type InternalComputed = {
27
+ readonly effect: ComputedEffect;
28
+ readonly state: ReactiveState<unknown, unknown>;
29
+ };
30
+
31
+ export type InternalEffect = {
32
+ state: EffectState;
33
+ };
34
+
35
+ export type ReactiveOptions<Value> = {
36
+ /**
37
+ * Method for comparing values for equality
38
+ * @param first First value
39
+ * @param second Second value
40
+ * @returns Are the values equal?
41
+ * @default Object.is
42
+ */
43
+ equal?: (first: Value, second: Value) => boolean;
44
+ };
45
+
46
+ export type ReactiveState<Value, Equal> = {
47
+ computeds: Set<Computed<unknown>>;
48
+ effects: Set<Effect>;
49
+ equal: (first: Equal, second: Equal) => boolean;
50
+ subscriptions: Map<GenericCallback, Subscription>;
51
+ value: Value;
52
+ };
53
+
54
+ export type SetValueInProxyParameters<Value, Equal> = {
55
+ target: Value;
56
+ property: PropertyKey;
57
+ value: unknown;
58
+ state: ReactiveState<Value, Equal>;
59
+ isArray: boolean;
60
+ length?: Signal<number>;
61
+ };
62
+
63
+ /**
64
+ * Unsubscribe from changes
65
+ */
66
+ export type Unsubscribe = () => void;
@@ -1,19 +1,22 @@
1
1
  import type {GenericCallback} from '@oscarpalmer/atoms/models';
2
- import type {ReactiveState} from './value/reactive';
2
+ import type {ReactiveState, Unsubscribe} from './models';
3
3
 
4
4
  export class Subscription {
5
- constructor(
6
- public state: ReactiveState<unknown, never>,
7
- public callback: GenericCallback,
8
- ) {
5
+ callback: GenericCallback;
6
+ state: ReactiveState<unknown, never>;
7
+
8
+ constructor(state: ReactiveState<unknown, never>, callback: GenericCallback) {
9
+ this.state = state;
10
+ this.callback = callback;
11
+
9
12
  callback(state.value);
10
13
  }
11
- }
12
14
 
13
- /**
14
- * Unsubscribe from changes
15
- */
16
- export type Unsubscribe = () => void;
15
+ destroy(): void {
16
+ this.callback = noop;
17
+ this.state = undefined as never;
18
+ }
19
+ }
17
20
 
18
21
  export function noop(): void {}
19
22
 
@@ -36,12 +39,7 @@ export function unsubscribe<Value>(
36
39
  state: ReactiveState<Value, never>,
37
40
  callback: (value: Value) => void,
38
41
  ): void {
39
- const subscription = state.subscriptions.get(callback);
42
+ state.subscriptions.get(callback)?.destroy();
40
43
 
41
44
  state.subscriptions.delete(callback);
42
-
43
- if (subscription != null) {
44
- subscription.callback = noop;
45
- subscription.state = undefined as never;
46
- }
47
45
  }
@@ -1,15 +1,22 @@
1
1
  import type {GenericCallback} from '@oscarpalmer/atoms/models';
2
- import {arrayName} from '../helpers/is';
3
- import {getReactiveValueInProxy, setValueInProxy} from '../helpers/proxy';
2
+ import {
3
+ METHODS_AFFECTING_LENGTH,
4
+ METHODS_UPDATE,
5
+ NAME_ARRAY,
6
+ PROPERTY_LENGTH,
7
+ } from '../constants';
8
+ import {emityProxyValues, getReactiveValueInProxy, setValueInProxy} from '../helpers/proxy';
4
9
  import {emitValue, equalArrays, getValue} from '../helpers/value';
5
- import {type Unsubscribe, noop, subscribe} from '../subscription';
10
+ import type {ReactiveOptions, ReactiveState, Unsubscribe} from '../models';
11
+ import {noop, subscribe} from '../subscription';
6
12
  import {type Computed, computed} from './computed';
7
- import {Reactive, type ReactiveOptions, type ReactiveState} from './reactive';
13
+ import {Reactive} from './reactive';
8
14
  import {type Signal, signal} from './signal';
9
15
 
10
16
  export class ReactiveArray<Item> extends Reactive<Item[], Item> {
11
- #indiced = new Map<number, Computed<unknown>>();
12
- #size = signal(0);
17
+ readonly #indiced = new Map<number, Computed<unknown>>();
18
+
19
+ readonly #size = signal(0);
13
20
 
14
21
  /**
15
22
  * The length of the array
@@ -33,21 +40,21 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
33
40
 
34
41
  constructor(value: Item[], options?: ReactiveOptions<Item>) {
35
42
  super(
36
- arrayName,
43
+ NAME_ARRAY,
37
44
  new Proxy(value, {
38
- get: (target, property) =>
39
- updateMethods.has(property as string)
45
+ get: (target: Item[], property: PropertyKey) =>
46
+ METHODS_UPDATE.has(property as string)
40
47
  ? updateArray(property as string, target, this.state, this.#size)
41
48
  : Reflect.get(target, property),
42
- set: (target, property, value) =>
43
- setValueInProxy(
44
- target,
49
+ set: (target: Item[], property: PropertyKey, value: Item) =>
50
+ setValueInProxy({
45
51
  property,
52
+ target,
46
53
  value,
47
- this.state,
48
- true,
49
- this.#size,
50
- ),
54
+ isArray: true,
55
+ state: this.state,
56
+ length: this.#size,
57
+ }),
51
58
  }),
52
59
  options,
53
60
  );
@@ -62,6 +69,17 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
62
69
  this.length = 0;
63
70
  }
64
71
 
72
+ /**
73
+ * Create a computed, filtered array
74
+ * @param callback Callback to evaluate each item
75
+ * @return Computed array of filtered items
76
+ */
77
+ filter(
78
+ callback: (item: Item, index: number, array: Item[]) => boolean,
79
+ ): Computed<Item[]> {
80
+ return computed(() => this.get().filter(callback));
81
+ }
82
+
65
83
  /**
66
84
  * @inheritdoc
67
85
  */
@@ -69,11 +87,14 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
69
87
 
70
88
  /**
71
89
  * Get the value at an index
90
+ * @param index Index of item to get _(if negative, starts from the end)_
91
+ * @returns Item at index, or `undefined` if it doesn't exist
72
92
  */
73
93
  get(index: number): Item | undefined;
74
94
 
75
95
  /**
76
96
  * Get the length of the array
97
+ * @returns Length of the array
77
98
  */
78
99
  get(property: 'length'): number;
79
100
 
@@ -82,24 +103,13 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
82
103
  return getReactiveValueInProxy(this, this.#indiced, first, true).get();
83
104
  }
84
105
 
85
- if (first === 'length') {
86
- return this.length;
87
- }
88
-
89
- return getValue(this.state);
90
- }
91
-
92
- /**
93
- * Create a computed, filtered array
94
- */
95
- filter(
96
- callback: (item: Item, index: number, array: Item[]) => boolean,
97
- ): Computed<Item[]> {
98
- return computed(() => this.get().filter(callback));
106
+ return first === PROPERTY_LENGTH ? this.length : getValue(this.state);
99
107
  }
100
108
 
101
109
  /**
102
110
  * Create a computed, mapped array
111
+ * @param callback Callback to transform each item
112
+ * @return Computed array of mapped items
103
113
  */
104
114
  map<Mapped>(
105
115
  callback: (item: Item, index: number, array: Item[]) => Mapped,
@@ -107,32 +117,47 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
107
117
  return computed(() => this.get().map(callback));
108
118
  }
109
119
 
120
+ /**
121
+ * Notify dependents of changes
122
+ *
123
+ * _This bypasses equality checks and will immediately notify dependents.
124
+ * Use this only if you're modifying nested data that would be ignored by equality checks._
125
+ */
126
+ notify(): void {
127
+ emityProxyValues(this.state, this.#indiced);
128
+ }
129
+
110
130
  /**
111
131
  * @inheritdoc
112
132
  */
113
133
  peek(): Item[];
114
134
 
135
+ /**
136
+ * Get the value at an index _(without reactivity)_
137
+ * @param index Index of item to get _(if negative, starts from the end)_
138
+ * @returns Item at index, or `undefined` if it doesn't exist
139
+ */
115
140
  peek(index: number): Item | undefined;
116
141
 
117
142
  /**
118
143
  * Get the length of the array _(without reactivity)_
144
+ * @returns Length of the array
119
145
  */
120
- peek(length: true): number;
146
+ peek(property: 'length'): number;
121
147
 
122
148
  peek(value?: unknown): unknown {
123
- if (value === true) {
149
+ if (value === 'length') {
124
150
  return this.#size.peek();
125
151
  }
126
152
 
127
- if (typeof value === 'number') {
128
- return this.state.value.at(value);
129
- }
130
-
131
- return [...this.state.value];
153
+ return typeof value === 'number'
154
+ ? this.state.value.at(value)
155
+ : [...this.state.value];
132
156
  }
133
157
 
134
158
  /**
135
159
  * Remove and return the last item of the array
160
+ * @returns Removed item, or `undefined` if the array is empty
136
161
  */
137
162
  pop(): Item | undefined {
138
163
  return this.state.value.pop();
@@ -140,6 +165,8 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
140
165
 
141
166
  /**
142
167
  * Add items to the end of the array
168
+ * @param items Items to add
169
+ * @returns New array length
143
170
  */
144
171
  push(...items: Item[]): number {
145
172
  return this.state.value.push(...items);
@@ -147,16 +174,20 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
147
174
 
148
175
  /**
149
176
  * Set the value
177
+ * @param value New array of items _(defaults to an empty array)_
150
178
  */
151
179
  set(value?: Item[]): void;
152
180
 
153
181
  /**
154
182
  * Set the value at an index
183
+ * @param index Index of item to set __(if negative, starts from the end)_
184
+ * @param value New item
155
185
  */
156
186
  set(index: number, value: Item): void;
157
187
 
158
188
  /**
159
189
  * Set the length of the array
190
+ * @param value New array length
160
191
  */
161
192
  set(property: 'length', value: number): void;
162
193
 
@@ -165,13 +196,14 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
165
196
  this.state.value.splice(0, this.state.value.length, ...(first ?? []));
166
197
  } else if (first === 'length') {
167
198
  this.length = second as number;
168
- } else if (typeof first === 'number') {
169
- this.state.value[first] = second as Item;
199
+ } else if (typeof first === 'number' && !Number.isNaN(first)) {
200
+ setAtIndex(this.state.value, first, second as Item);
170
201
  }
171
202
  }
172
203
 
173
204
  /**
174
205
  * Remove and return the first item of the array
206
+ * @returns Removed item, or `undefined` if the array is empty
175
207
  */
176
208
  shift(): Item | undefined {
177
209
  return this.state.value.shift();
@@ -179,6 +211,10 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
179
211
 
180
212
  /**
181
213
  * Remove and return items from the array _(and optionally add new items)_
214
+ * @param from Index to start removing items from
215
+ * @param to Index to stop removing items at _(defaults to the end of the array)_
216
+ * @param items Optional items to add
217
+ * @returns Removed items
182
218
  */
183
219
  splice(from: number, to?: number, ...items: Item[]): Item[] {
184
220
  return this.state.value.splice(
@@ -195,6 +231,9 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
195
231
 
196
232
  /**
197
233
  * Subscribe to changes at a specific index
234
+ * @param index Index of item to subscribe to
235
+ * @param callback Callback for changes
236
+ * @returns Unsubscribe callback
198
237
  */
199
238
  subscribe(
200
239
  index: number,
@@ -219,6 +258,8 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
219
258
 
220
259
  /**
221
260
  * Add items to the beginning of the array
261
+ * @param items Items to add
262
+ * @returns New array length
222
263
  */
223
264
  unshift(...items: Item[]): number {
224
265
  return this.state.value.unshift(...items);
@@ -226,6 +267,7 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
226
267
 
227
268
  /**
228
269
  * Update the value _(based on the current value)_
270
+ * @param callback Callback to update the value
229
271
  */
230
272
  update(callback: (value: Item[]) => Item[]): void {
231
273
  const updated = callback(this.state.value);
@@ -238,6 +280,9 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
238
280
 
239
281
  /**
240
282
  * Create a reactive array
283
+ * @param value Initial array of items
284
+ * @param options Optional reactivity options
285
+ * @returns Reactive array
241
286
  */
242
287
  export function array<Item>(
243
288
  value: Item[],
@@ -252,7 +297,7 @@ function updateArray<Item>(
252
297
  state: ReactiveState<Item[], Item>,
253
298
  length: Signal<number>,
254
299
  ): unknown {
255
- const affectsLength = lengthAffectingMethods.has(type);
300
+ const affectsLength = METHODS_AFFECTING_LENGTH.has(type);
256
301
  const previousArray = affectsLength ? [] : [...array];
257
302
  const previousLength = array.length;
258
303
 
@@ -275,18 +320,10 @@ function updateArray<Item>(
275
320
  };
276
321
  }
277
322
 
278
- const lengthAffectingMethods = new Set<string>([
279
- 'pop',
280
- 'push',
281
- 'shift',
282
- 'unshift',
283
- ]);
284
-
285
- const updateMethods = new Set<string>([
286
- ...lengthAffectingMethods,
287
- 'copyWithin',
288
- 'fill',
289
- 'reverse',
290
- 'sort',
291
- 'splice',
292
- ]);
323
+ function setAtIndex<Item>(array: Item[], index: number, value: Item): void {
324
+ const actual = index < 0 ? array.length + index : index;
325
+
326
+ if (actual > -1) {
327
+ array[actual] = value;
328
+ }
329
+ }