@oscarpalmer/mora 0.29.0 → 0.31.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 (51) hide show
  1. package/dist/batch.mjs +15 -6
  2. package/dist/constants.d.mts +2 -2
  3. package/dist/constants.mjs +7 -4
  4. package/dist/effect.d.mts +5 -18
  5. package/dist/effect.mjs +23 -19
  6. package/dist/helpers/is.d.mts +17 -16
  7. package/dist/helpers/is.mjs +20 -11
  8. package/dist/helpers/proxy.d.mts +5 -9
  9. package/dist/helpers/proxy.mjs +4 -4
  10. package/dist/helpers/value.d.mts +3 -3
  11. package/dist/helpers/value.mjs +22 -5
  12. package/dist/index.d.mts +8 -9
  13. package/dist/index.mjs +2 -2
  14. package/dist/models.d.mts +423 -19
  15. package/dist/mora.full.mjs +532 -424
  16. package/dist/subscription.d.mts +1 -9
  17. package/dist/subscription.mjs +14 -15
  18. package/dist/value/array.d.mts +5 -133
  19. package/dist/value/array.mjs +108 -138
  20. package/dist/value/computed.d.mts +4 -12
  21. package/dist/value/computed.mjs +54 -50
  22. package/dist/value/reactive.d.mts +3 -49
  23. package/dist/value/reactive.mjs +10 -54
  24. package/dist/value/readonly.d.mts +7 -0
  25. package/dist/value/readonly.mjs +37 -0
  26. package/dist/value/signal.d.mts +5 -21
  27. package/dist/value/signal.mjs +33 -49
  28. package/dist/value/store.d.mts +9 -92
  29. package/dist/value/store.mjs +58 -49
  30. package/package.json +7 -8
  31. package/src/batch.ts +22 -9
  32. package/src/constants.ts +12 -5
  33. package/src/effect.ts +35 -40
  34. package/src/helpers/is.ts +36 -20
  35. package/src/helpers/proxy.ts +23 -18
  36. package/src/helpers/value.ts +39 -5
  37. package/src/index.ts +23 -8
  38. package/src/models.ts +520 -17
  39. package/src/subscription.ts +20 -20
  40. package/src/value/array.ts +220 -266
  41. package/src/value/computed.ts +80 -74
  42. package/src/value/reactive.ts +16 -77
  43. package/src/value/readonly.ts +71 -0
  44. package/src/value/signal.ts +43 -71
  45. package/src/value/store.ts +130 -177
  46. package/types/constants.d.ts +1 -1
  47. package/types/index.d.ts +1 -1
  48. package/types/value/array.d.ts +1 -1
  49. package/types/value/computed.d.ts +3 -0
  50. package/types/value/signal.d.ts +1 -1
  51. package/types/value/store.d.ts +1 -1
@@ -1,268 +1,40 @@
1
+ import {select} from '@oscarpalmer/atoms/array';
2
+ import {filter} from '@oscarpalmer/atoms/array/filter';
3
+ import {noop} from '@oscarpalmer/atoms/function';
4
+ import {isPlainObject} from '@oscarpalmer/atoms/is';
1
5
  import type {GenericCallback} from '@oscarpalmer/atoms/models';
2
- import {METHODS_AFFECTING_LENGTH, METHODS_UPDATE, NAME_ARRAY, PROPERTY_LENGTH} from '../constants';
6
+ import {
7
+ METHODS_AFFECTING_LENGTH,
8
+ METHODS_UPDATE,
9
+ NAME_ARRAY,
10
+ NAME_MORA,
11
+ PROPERTY_LENGTH,
12
+ } from '../constants';
3
13
  import {
4
14
  emityProxyValues,
5
15
  getReactiveValueInProxy,
6
16
  setProxyValue,
7
17
  setValueInProxy,
8
18
  } from '../helpers/proxy';
9
- import {emitValue, equalArrays, getValue} from '../helpers/value';
10
- import type {ReactiveOptions, ReactiveState, Unsubscribe} from '../models';
11
- import {noop, subscribe} from '../subscription';
12
- import {type Computed, computed} from './computed';
13
- import {Reactive} from './reactive';
14
- import {type Signal, signal} from './signal';
15
-
16
- export class ReactiveArray<Item> extends Reactive<Item[], Item> {
17
- readonly #indiced = new Map<number, Computed<unknown>>();
18
-
19
- readonly #size = signal(0);
20
-
21
- /**
22
- * The length of the array
23
- */
24
- get length(): number {
25
- return this.#size.get();
26
- }
27
-
28
- /**
29
- * Set the length of the array
30
- */
31
- set length(value: number) {
32
- if (typeof value === 'number' && value >= 0 && value !== this.state.value.length) {
33
- this.get().length = value;
34
- }
35
- }
36
-
37
- constructor(value: Item[], options?: ReactiveOptions<Item>) {
38
- super(
39
- NAME_ARRAY,
40
- new Proxy(value, {
41
- get: (target: Item[], property: PropertyKey) =>
42
- METHODS_UPDATE.has(property as string)
43
- ? updateArray(property as string, target, this.state, this.#size)
44
- : Reflect.get(target, property),
45
- set: (target: Item[], property: PropertyKey, value: Item) =>
46
- setValueInProxy({
47
- property,
48
- target,
49
- value,
50
- isArray: true,
51
- state: this.state,
52
- length: this.#size,
53
- }),
54
- }),
55
- options,
56
- );
57
-
58
- this.#size.set(value.length);
59
- }
60
-
61
- /**
62
- * Clear the array
63
- */
64
- clear(): void {
65
- this.length = 0;
66
- }
67
-
68
- /**
69
- * Create a computed, filtered array
70
- * @param callback Callback to evaluate each item
71
- * @return Computed array of filtered items
72
- */
73
- filter(callback: (item: Item, index: number, array: Item[]) => boolean): Computed<Item[]> {
74
- return computed(() => this.get().filter(callback));
75
- }
76
-
77
- /**
78
- * @inheritdoc
79
- */
80
- get(): Item[];
81
-
82
- /**
83
- * Get the value at an index
84
- * @param index Index of item to get _(if negative, starts from the end)_
85
- * @returns Item at index, or `undefined` if it doesn't exist
86
- */
87
- get(index: number): Item | undefined;
88
-
89
- /**
90
- * Get the length of the array
91
- * @returns Length of the array
92
- */
93
- get(property: typeof PROPERTY_LENGTH): number;
94
-
95
- get(first?: unknown): unknown {
96
- if (typeof first === 'number') {
97
- return getReactiveValueInProxy(this, this.#indiced, first, true).get();
98
- }
99
-
100
- return first === PROPERTY_LENGTH ? this.length : getValue(this.state);
101
- }
102
-
103
- /**
104
- * Create a computed, mapped array
105
- * @param callback Callback to transform each item
106
- * @return Computed array of mapped items
107
- */
108
- map<Mapped>(callback: (item: Item, index: number, array: Item[]) => Mapped): Computed<Mapped[]> {
109
- return computed(() => this.get().map(callback));
110
- }
111
-
112
- /**
113
- * Notify dependents of changes
114
- *
115
- * _This bypasses equality checks and will immediately notify dependents.
116
- * Use this only if you're modifying nested data that would be ignored by equality checks._
117
- */
118
- notify(): void {
119
- emityProxyValues(this.state, this.#indiced);
120
- }
121
-
122
- /**
123
- * @inheritdoc
124
- */
125
- override peek(): Item[];
126
-
127
- /**
128
- * Get the value at an index _(without reactivity)_
129
- * @param index Index of item to get _(if negative, starts from the end)_
130
- * @returns Item at index, or `undefined` if it doesn't exist
131
- */
132
- override peek(index: number): Item | undefined;
133
-
134
- /**
135
- * Get the length of the array _(without reactivity)_
136
- * @returns Length of the array
137
- */
138
- override peek(property: typeof PROPERTY_LENGTH): number;
139
-
140
- override peek(value?: unknown): unknown {
141
- if (value === PROPERTY_LENGTH) {
142
- return this.#size.peek();
143
- }
144
-
145
- return typeof value === 'number' ? this.state.value.at(value) : this.state.value.slice();
146
- }
147
-
148
- /**
149
- * Remove and return the last item of the array
150
- * @returns Removed item, or `undefined` if the array is empty
151
- */
152
- pop(): Item | undefined {
153
- return this.state.value.pop();
154
- }
155
-
156
- /**
157
- * Add items to the end of the array
158
- * @param items Items to add
159
- * @returns New array length
160
- */
161
- push(...items: Item[]): number {
162
- return this.state.value.push(...items);
163
- }
164
-
165
- /**
166
- * Set the value
167
- * @param value New array of items _(defaults to an empty array)_
168
- */
169
- set(
170
- value?:
171
- | Item[]
172
- | (() => null | undefined | Item[] | Promise<null | undefined | Item[]>)
173
- | Promise<null | undefined | Item[]>,
174
- ): void;
175
-
176
- /**
177
- * Set the value at an index
178
- * @param index Index of item to set __(if negative, starts from the end)_
179
- * @param value New item
180
- */
181
- set(index: number, value: Item | (() => Item | Promise<Item>) | Promise<Item>): void;
182
-
183
- /**
184
- * Set the length of the array
185
- * @param value New array length
186
- */
187
- set(property: typeof PROPERTY_LENGTH, value: number): void;
188
-
189
- set(first?: unknown, second?: unknown): void {
190
- setProxyValue<Item[], Item>(
191
- true,
192
- this.state,
193
- isArrayValue,
194
- isArrayIndex,
195
- setArray,
196
- setAtIndex,
197
- first,
198
- second,
199
- );
200
- }
201
-
202
- /**
203
- * Remove and return the first item of the array
204
- * @returns Removed item, or `undefined` if the array is empty
205
- */
206
- shift(): Item | undefined {
207
- return this.state.value.shift();
208
- }
209
-
210
- /**
211
- * Remove and return items from the array _(and optionally add new items)_
212
- * @param from Index to start removing items from
213
- * @param to Index to stop removing items at _(defaults to the end of the array)_
214
- * @param items Optional items to add
215
- * @returns Removed items
216
- */
217
- splice(from: number, to?: number, ...items: Item[]): Item[] {
218
- return this.state.value.splice(from, to ?? this.state.value.length, ...items);
219
- }
220
-
221
- /**
222
- * @inheritdoc
223
- */
224
- override subscribe(callback: (value: Item[]) => void): Unsubscribe;
225
-
226
- /**
227
- * Subscribe to changes at a specific index
228
- * @param index Index of item to subscribe to
229
- * @param callback Callback for changes
230
- * @returns Unsubscribe callback
231
- */
232
- override subscribe(index: number, callback: (value: Item | undefined) => void): Unsubscribe;
233
-
234
- override subscribe(first: number | GenericCallback, second?: GenericCallback): Unsubscribe {
235
- if (typeof first === 'number' && typeof second === 'function') {
236
- return getReactiveValueInProxy(this, this.#indiced, first, true).subscribe(second);
237
- }
238
-
239
- return typeof first === 'function' ? subscribe(this.state, first) : noop;
240
- }
241
-
242
- /**
243
- * Add items to the beginning of the array
244
- * @param items Items to add
245
- * @returns New array length
246
- */
247
- unshift(...items: Item[]): number {
248
- return this.state.value.unshift(...items);
249
- }
250
-
251
- /**
252
- * Update the value _(based on the current value)_
253
- * @param callback Callback to update the value
254
- */
255
- update(callback: (value: Item[]) => Item[]): void {
256
- const updated = callback(this.state.value);
257
-
258
- if (updated == null || Array.isArray(updated)) {
259
- this.set(updated);
260
- }
261
- }
262
- }
19
+ import {emitValue, equalArrays, getSimpleValue} from '../helpers/value';
20
+ import type {
21
+ Computed,
22
+ ComputedEffect,
23
+ ReactiveArray,
24
+ ReactiveOptions,
25
+ ReactiveState,
26
+ ReadonlyInstances,
27
+ Signal,
28
+ } from '../models';
29
+ import {subscribe, unsubscribe} from '../subscription';
30
+ import {computed} from './computed';
31
+ import {reactive} from './reactive';
32
+ import {getReadonlyInstance} from './readonly';
33
+ import {signal} from './signal';
263
34
 
264
35
  /**
265
36
  * Create a reactive array from a function result
37
+ *
266
38
  * @param value Initial array of items
267
39
  * @param options Reactivity options
268
40
  * @returns Reactive array
@@ -274,6 +46,7 @@ export function array<Item>(
274
46
 
275
47
  /**
276
48
  * Create a reactive array from a promise
49
+ *
277
50
  * @param value Initial array of items
278
51
  * @param options Reactivity options
279
52
  * @returns Reactive array
@@ -285,6 +58,7 @@ export function array<Item>(
285
58
 
286
59
  /**
287
60
  * Create a reactive array
61
+ *
288
62
  * @param value Initial array of items
289
63
  * @param options Reactivity options
290
64
  * @returns Reactive array
@@ -295,11 +69,140 @@ export function array<Item>(
295
69
  value: Item[] | (() => Item[] | Promise<Item[]>) | Promise<Item[]>,
296
70
  options?: ReactiveOptions<Item>,
297
71
  ): ReactiveArray<Item> {
298
- const instance = new ReactiveArray([], options);
72
+ const [rx, state] = reactive<Item[], Item>([], options);
73
+
74
+ const indiced = new Map<number, [Computed<unknown>, ComputedEffect]>();
75
+ const length = signal(0);
76
+ const readonlies: ReadonlyInstances<Item[]> = {};
77
+
78
+ let instance: Record<string, unknown> = {};
79
+
80
+ state.value = new Proxy([], {
81
+ get: (target: Item[], property: PropertyKey) =>
82
+ METHODS_UPDATE.has(property as string)
83
+ ? updateArray(property as string, target, state, length)
84
+ : Reflect.get(target, property),
85
+ set: (target: Item[], property: PropertyKey, value: Item) =>
86
+ setValueInProxy({
87
+ length,
88
+ property,
89
+ state,
90
+ target,
91
+ value,
92
+ isArray: true,
93
+ }),
94
+ });
299
95
 
300
- instance.set(value);
96
+ function get(): Item[];
97
+ function get(index: number): Item | undefined;
98
+ function get(property: typeof PROPERTY_LENGTH): number;
99
+ function get(value?: unknown): number | Item | Item[] | undefined;
301
100
 
302
- return instance;
101
+ function get(value?: unknown): number | Item | Item[] | undefined {
102
+ return getArrayValue(instance as ReactiveArray<Item>, indiced, state, length, value);
103
+ }
104
+
105
+ function set(first?: unknown, second?: unknown): void {
106
+ setProxyValue<Item[], Item>(
107
+ true,
108
+ state,
109
+ isArrayValue,
110
+ isArrayIndex,
111
+ setArray,
112
+ setAtIndex,
113
+ first,
114
+ second,
115
+ );
116
+ }
117
+
118
+ const handlers = {
119
+ ...rx,
120
+ get: (value?: number | typeof PROPERTY_LENGTH) => get(value),
121
+ peek: (first?: unknown, second?: boolean) => peekArrayValue(state, length, first, second),
122
+ subscribe: (first: number | GenericCallback, second?: GenericCallback) => {
123
+ if (typeof first === 'number' && typeof second === 'function') {
124
+ return getReactiveValueInProxy(
125
+ instance as ReactiveArray<Item>,
126
+ indiced,
127
+ first,
128
+ true,
129
+ ).subscribe(second);
130
+ }
131
+
132
+ return typeof first === 'function' ? subscribe(state, first) : noop;
133
+ },
134
+ unsubscribe: (first: number | GenericCallback, second?: GenericCallback) => {
135
+ if (typeof first === 'number' && typeof second === 'function') {
136
+ getReactiveValueInProxy(instance as ReactiveArray<Item>, indiced, first, true)?.unsubscribe(
137
+ second,
138
+ );
139
+ } else if (typeof first === 'function') {
140
+ unsubscribe(state, first);
141
+ }
142
+ },
143
+ };
144
+
145
+ instance = {
146
+ ...handlers,
147
+ asReadonly: (frozen?: unknown) =>
148
+ getReadonlyInstance(state, readonlies, handlers, frozen === true),
149
+ at: (index: number): Item | undefined => get(index),
150
+ clear: () => {
151
+ state.value.length = 0;
152
+ },
153
+ filter: (callback: (item: Item, index: number, array: Item[]) => boolean) =>
154
+ computed(() => filter(get(), callback)),
155
+ map: <Mapped>(callback: (item: Item, index: number, array: Item[]) => Mapped) =>
156
+ computed(() => get().map(callback)),
157
+ notify: () => {
158
+ emityProxyValues(state, indiced);
159
+ },
160
+ pop: () => state.value.pop(),
161
+ push: (...items: Item[]) => state.value.push(...items),
162
+ select: <Mapped>(
163
+ filter: (item: Item, index: number, array: Item[]) => boolean,
164
+ map: (item: Item, index: number, array: Item[]) => Mapped,
165
+ ) => computed(() => select(get() as Item[], filter, map)),
166
+ set: (first?: unknown, second?: unknown) => {
167
+ set(first, second);
168
+ },
169
+ shift: () => state.value.shift(),
170
+ splice: (from: number, to?: number, ...items: Item[]) =>
171
+ state.value.splice(from, to ?? state.value.length, ...items),
172
+ unshift: (...items: Item[]) => state.value.unshift(...items),
173
+ update: (callback: (value: Item[]) => Item[]) =>
174
+ updateArrayValue(instance as ReactiveArray<Item>, state, callback),
175
+ };
176
+
177
+ Object.defineProperties(instance, {
178
+ [NAME_MORA]: {
179
+ enumerable: false,
180
+ value: NAME_ARRAY,
181
+ },
182
+ length: {
183
+ enumerable: true,
184
+ get: () => length.get(),
185
+ set: (value: number) => setArrayLength(state, value),
186
+ },
187
+ });
188
+
189
+ set(value);
190
+
191
+ return Object.freeze(instance) as ReactiveArray<Item>;
192
+ }
193
+
194
+ function getArrayValue<Item>(
195
+ instance: ReactiveArray<Item>,
196
+ indiced: Map<number, [Computed<unknown>, ComputedEffect]>,
197
+ state: ReactiveState<Item[], Item>,
198
+ length: Signal<number>,
199
+ first?: unknown,
200
+ ): number | Item | Item[] | undefined {
201
+ if (typeof first === 'number') {
202
+ return getReactiveValueInProxy(instance, indiced, first, true).get();
203
+ }
204
+
205
+ return first === PROPERTY_LENGTH ? length.get() : getSimpleValue(state);
303
206
  }
304
207
 
305
208
  function isArrayIndex(value: unknown): boolean {
@@ -310,6 +213,57 @@ function isArrayValue<Item>(value: unknown): value is Item[] | undefined {
310
213
  return value == null || Array.isArray(value);
311
214
  }
312
215
 
216
+ function peekArrayValue<Item>(
217
+ state: ReactiveState<Item[], Item>,
218
+ length: Signal<number>,
219
+ first?: unknown,
220
+ second?: boolean,
221
+ ): number | Item | Item[] | undefined {
222
+ if (first === PROPERTY_LENGTH) {
223
+ return length.peek();
224
+ }
225
+
226
+ let value: Item | Item[] | undefined;
227
+
228
+ if (typeof first === 'number') {
229
+ value = state.value.at(first);
230
+ } else {
231
+ value = state.value;
232
+ }
233
+
234
+ if (!(first === true || second === true)) {
235
+ return value;
236
+ }
237
+
238
+ if (Array.isArray(value)) {
239
+ return value.slice();
240
+ }
241
+
242
+ if (isPlainObject(value)) {
243
+ return {...value};
244
+ }
245
+
246
+ return value;
247
+ }
248
+
249
+ function setArray<Item>(state: ReactiveState<Item[], Item>, value: Item[] | undefined): void {
250
+ state.value.splice(0, state.value.length, ...(value ?? []));
251
+ }
252
+
253
+ function setArrayLength<Item>(state: ReactiveState<Item[], Item>, value: number): void {
254
+ if (typeof value === 'number' && value >= 0 && value !== state.value.length) {
255
+ state.value.length = value;
256
+ }
257
+ }
258
+
259
+ function setAtIndex<Item>(state: ReactiveState<Item[], Item>, index: unknown, value: Item): void {
260
+ const actual = (index as number) < 0 ? state.value.length + (index as number) : (index as number);
261
+
262
+ if (actual > -1) {
263
+ state.value[actual] = value;
264
+ }
265
+ }
266
+
313
267
  function updateArray<Item>(
314
268
  type: string,
315
269
  array: Item[],
@@ -335,14 +289,14 @@ function updateArray<Item>(
335
289
  };
336
290
  }
337
291
 
338
- function setArray<Item>(state: ReactiveState<Item[], Item>, value: Item[] | undefined): void {
339
- state.value.splice(0, state.value.length, ...(value ?? []));
340
- }
341
-
342
- function setAtIndex<Item>(state: ReactiveState<Item[], Item>, index: unknown, value: Item): void {
343
- const actual = (index as number) < 0 ? state.value.length + (index as number) : (index as number);
292
+ function updateArrayValue<Item>(
293
+ instance: ReactiveArray<Item>,
294
+ state: ReactiveState<Item[], Item>,
295
+ callback: (value: Item[]) => Item[],
296
+ ): void {
297
+ const updated = callback(state.value);
344
298
 
345
- if (actual > -1) {
346
- state.value[actual] = value;
299
+ if (updated == null || Array.isArray(updated)) {
300
+ instance.set(updated);
347
301
  }
348
302
  }