@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
package/src/models.ts CHANGED
@@ -1,37 +1,276 @@
1
- import type {GenericCallback, Key} 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';
1
+ import type {GenericCallback, Key, PlainObject} from '@oscarpalmer/atoms/models';
2
+ import type {PROPERTY_LENGTH} from './constants';
6
3
 
7
4
  export type Active = {
8
- computed?: Computed<unknown>;
9
- effect?: Effect;
5
+ computed?: ComputedEffect;
6
+ effect?: EffectState;
10
7
  };
11
8
 
12
9
  export type Batch = {
13
10
  depth: number;
14
- handlers: Set<Effect | Subscription>;
11
+ flushing: boolean;
12
+ handlers: Set<EffectState | Subscription>;
15
13
  };
16
14
 
15
+ export type Computed<Value> = Reactive<Value> & SimpleReactive<Value>;
16
+
17
17
  export type ComputedEffect = {
18
18
  dirty: boolean;
19
- instance: Effect;
19
+ instance: EffectState;
20
20
  };
21
21
 
22
+ export type Effect = {};
23
+
22
24
  export type EffectState = {
23
25
  callback: GenericCallback;
24
26
  };
25
27
 
26
- export type InternalComputed = {
27
- readonly effect: ComputedEffect;
28
- readonly state: ReactiveState<unknown, unknown>;
29
- };
28
+ export type Reactive<Value> = {
29
+ /**
30
+ * JSON representation of the value
31
+ *
32
+ * @returns JSON value
33
+ */
34
+ toJSON(): Value;
30
35
 
31
- export type InternalEffect = {
32
- state: EffectState;
36
+ /**
37
+ * String representation of the value
38
+ *
39
+ * @returns Value as string
40
+ */
41
+ toString(): string;
33
42
  };
34
43
 
44
+ export type ReactiveArray<Item> = {
45
+ /**
46
+ * The length of the array
47
+ */
48
+ get length(): number;
49
+
50
+ /**
51
+ * Set the length of the array
52
+ */
53
+ set length(value: number);
54
+
55
+ /**
56
+ * Get a readonly version of the reactive array
57
+ *
58
+ * @param frozen Freeze the array? _(defaults to `false`)_
59
+ * @returns Readonly reactive array
60
+ */
61
+ asReadonly(frozen: true): ReadonlyFrozenSignal<Item[]>;
62
+
63
+ /**
64
+ * Get a readonly version of the reactive array
65
+ *
66
+ * @param frozen Freeze the array? _(defaults to `false`)_
67
+ * @returns Readonly reactive array
68
+ */
69
+ asReadonly(
70
+ frozen?: boolean,
71
+ ): typeof frozen extends true ? ReadonlyFrozenSignal<Item[]> : ReadonlySignal<Item[]>;
72
+
73
+ /**
74
+ * Get the value at an index
75
+ *
76
+ * @param index Index of item to get _(if negative, starts from the end)_
77
+ * @returns Item at index, or `undefined` if it doesn't exist
78
+ */
79
+ at(index: number): Item | undefined;
80
+
81
+ /**
82
+ * Clear the array
83
+ */
84
+ clear(): void;
85
+
86
+ /**
87
+ * Create a computed, filtered array
88
+ *
89
+ * @param callback Callback to evaluate each item
90
+ * @returns Computed array of filtered items
91
+ */
92
+ filter(callback: (item: Item, index: number, array: Item[]) => boolean): Computed<Item[]>;
93
+
94
+ /**
95
+ * Get the array
96
+ *
97
+ * @returns Array of items
98
+ */
99
+ get(): Item[];
100
+
101
+ /**
102
+ * Get the value at an index
103
+ *
104
+ * @param index Index of item to get _(if negative, starts from the end)_
105
+ * @returns Item at index, or `undefined` if it doesn't exist
106
+ */
107
+ get(index: number): Item | undefined;
108
+
109
+ /**
110
+ * Get the length of the array
111
+ *
112
+ * @returns Length of the array
113
+ */
114
+ get(property: typeof PROPERTY_LENGTH): number;
115
+
116
+ /**
117
+ * Create a computed, mapped array
118
+ *
119
+ * @param callback Callback to transform each item
120
+ * @returns Computed array of mapped items
121
+ */
122
+ map<Mapped>(callback: (item: Item, index: number, array: Item[]) => Mapped): Computed<Mapped[]>;
123
+
124
+ /**
125
+ * Notify dependents of changes
126
+ *
127
+ * _This bypasses equality checks and will immediately notify dependents.
128
+ * Use this only if you're modifying nested data that would be ignored by equality checks._
129
+ */
130
+ notify(): void;
131
+
132
+ /**
133
+ * Get the array _(without reactivity)_
134
+ *
135
+ * @param copy Copy the array? _(defaults to `false`)_
136
+ * @returns Array of items
137
+ */
138
+ peek(copy?: boolean): Item[];
139
+
140
+ /**
141
+ * Get the value at an index _(without reactivity)_
142
+ *
143
+ * @param index Index of item to get _(if negative, starts from the end)_
144
+ * @param copy If the item is an array or record, should it be copied? _(defaults to `false`)_
145
+ * @returns Item at index, or `undefined` if it doesn't exist
146
+ */
147
+ peek(index: number, copy?: boolean): Item | undefined;
148
+
149
+ /**
150
+ * Get the length of the array _(without reactivity)_
151
+ *
152
+ * @returns Length of the array
153
+ */
154
+ peek(property: typeof PROPERTY_LENGTH): number;
155
+
156
+ /**
157
+ * Remove and return the last item of the array
158
+ *
159
+ * @returns Removed item, or `undefined` if the array is empty
160
+ */
161
+ pop(): Item | undefined;
162
+
163
+ /**
164
+ * Add items to the end of the array
165
+ *
166
+ * @param items Items to add
167
+ * @returns New array length
168
+ */
169
+ push(...items: Item[]): number;
170
+
171
+ /**
172
+ * Create a computed, filtered, and mapped array
173
+ *
174
+ * @param filter Callback to evaluate each item
175
+ * @param map Callback to transform each item
176
+ * @returns Computed array of mapped items
177
+ */
178
+ select<Mapped>(
179
+ filter: (item: Item, index: number, array: Item[]) => boolean,
180
+ map: (item: Item, index: number, array: Item[]) => Mapped,
181
+ ): Computed<Mapped[]>;
182
+
183
+ /**
184
+ * Set the value
185
+ *
186
+ * @param value New array of items _(defaults to an empty array)_
187
+ */
188
+ set(
189
+ value?:
190
+ | Item[]
191
+ | (() => null | undefined | Item[] | Promise<null | undefined | Item[]>)
192
+ | Promise<null | undefined | Item[]>,
193
+ ): void;
194
+
195
+ /**
196
+ * Set the value at an index
197
+ *
198
+ * @param index Index of item to set _(if negative, starts from the end)_
199
+ * @param value New item
200
+ */
201
+ set(index: number, value: Item | (() => Item | Promise<Item>) | Promise<Item>): void;
202
+
203
+ /**
204
+ * Set the length of the array
205
+ *
206
+ * @param value New array length
207
+ */
208
+ set(property: typeof PROPERTY_LENGTH, value: number): void;
209
+
210
+ /**
211
+ * Remove and return the first item of the array
212
+ *
213
+ * @returns Removed item, or `undefined` if the array is empty
214
+ */
215
+ shift(): Item | undefined;
216
+
217
+ /**
218
+ * Remove and return items from the array _(and optionally add new items)_
219
+ *
220
+ * @param from Index to start removing items from
221
+ * @param to Index to stop removing items at _(defaults to the end of the array)_
222
+ * @param items Optional items to add
223
+ * @returns Removed items
224
+ */
225
+ splice(from: number, to?: number, ...items: Item[]): Item[];
226
+
227
+ /**
228
+ * Subscribe to changes
229
+ *
230
+ * @param callback Callback for changes
231
+ * @returns Unsubscribe callback
232
+ */
233
+ subscribe(callback: (value: Item[]) => void): Unsubscribe;
234
+
235
+ /**
236
+ * Subscribe to changes at a specific index
237
+ *
238
+ * @param index Index of item to subscribe to
239
+ * @param callback Callback for changes
240
+ * @returns Unsubscribe callback
241
+ */
242
+ subscribe(index: number, callback: (value: Item | undefined) => void): Unsubscribe;
243
+
244
+ /**
245
+ * Add items to the beginning of the array
246
+ * @param items Items to add
247
+ * @returns New array length
248
+ */
249
+ unshift(...items: Item[]): number;
250
+
251
+ /**
252
+ * Unsubscribe from changes for a specific index
253
+ *
254
+ * @param index Index of the value to unsubscribe from
255
+ * @param callback Callback to unsubscribe
256
+ */
257
+ unsubscribe(index: number, callback: (item: Item | undefined) => void): void;
258
+
259
+ /**
260
+ * Unsubscribe from changes
261
+ *
262
+ * @param callback Callback to unsubscribe
263
+ */
264
+ unsubscribe(callback: (array: Item[]) => void): void;
265
+
266
+ /**
267
+ * Update the value _(based on the current value)_
268
+ *
269
+ * @param callback Callback to update the value
270
+ */
271
+ update(callback: (value: Item[]) => Item[]): void;
272
+ } & Reactive<Item[]>;
273
+
35
274
  export type ReactiveOptions<Value> = {
36
275
  /**
37
276
  * Method for comparing values for equality
@@ -44,8 +283,8 @@ export type ReactiveOptions<Value> = {
44
283
  };
45
284
 
46
285
  export type ReactiveState<Value, Equal> = {
47
- computeds: Set<Computed<unknown>>;
48
- effects: Set<Effect>;
286
+ computeds: Set<ComputedEffect>;
287
+ effects: Set<EffectState>;
49
288
  equal: (first: Equal, second: Equal) => boolean;
50
289
  promise?: Promise<Value>;
51
290
  promises?: Map<Key, Promise<never>>;
@@ -53,6 +292,197 @@ export type ReactiveState<Value, Equal> = {
53
292
  value: Value;
54
293
  };
55
294
 
295
+ export type ReactiveStore<Store> = {
296
+ /**
297
+ * Get a readonly version of the reactive store
298
+ *
299
+ * @param frozen Freeze the store? _(defaults to `false`)_
300
+ * @returns Readonly reactive store
301
+ */
302
+ asReadonly(frozen: true): ReadonlyFrozenSignal<Store>;
303
+
304
+ /**
305
+ * Get a readonly version of the reactive store
306
+ *
307
+ * @param frozen Freeze the store? _(defaults to `false`)_
308
+ * @returns Readonly reactive store
309
+ */
310
+ asReadonly(
311
+ frozen?: boolean,
312
+ ): typeof frozen extends true ? ReadonlyFrozenSignal<Store> : ReadonlySignal<Store>;
313
+
314
+ /**
315
+ * Get the value
316
+ *
317
+ * @returns Current value
318
+ */
319
+ get(): Store;
320
+
321
+ /**
322
+ * Get a value by key
323
+ *
324
+ * @param key Key of the value to get
325
+ * @returns Value for the specified key, or `undefined` if it doesn't exist
326
+ */
327
+ get<Key extends keyof Store>(key: Key): Store[Key];
328
+
329
+ /**
330
+ * Get a value by key
331
+ *
332
+ * @param key Key of the value to get
333
+ * @returns Value for the specified key, or `undefined` if it doesn't exist
334
+ */
335
+ get(key: Key): unknown;
336
+
337
+ /**
338
+ * Notify dependents of changes
339
+ *
340
+ * _This bypasses equality checks and will immediately notify dependents.
341
+ * Use this only if you're modifying nested data that would be ignored by equality checks._
342
+ */
343
+ notify(): void;
344
+
345
+ /**
346
+ * Get the value _(without reactivity)_
347
+ *
348
+ * @param copy Copy the store? _(defaults to `false`)_
349
+ * @returns Current value
350
+ */
351
+ peek(copy?: boolean): Store;
352
+
353
+ /**
354
+ * Get a value by key _(without reactivity)_
355
+ *
356
+ * @param key Key of the value to get
357
+ * @param copy If the value is an array or record, should it be copied? _(defaults to `false`)_
358
+ * @returns Value for the specified key, or `undefined` if it doesn't exist
359
+ */
360
+ peek<Key extends keyof Store>(key: Key, copy?: boolean): Store[Key];
361
+
362
+ /**
363
+ * Get a value by key _(without reactivity)_
364
+ *
365
+ * @param key Key of the value to get
366
+ * @param copy If the value is an array or record, should it be copied? _(defaults to `false`)_
367
+ * @returns Value for the specified key, or `undefined` if it doesn't exist
368
+ */
369
+ peek(key: Key, copy?: boolean): unknown;
370
+
371
+ /**
372
+ * Set the value
373
+ *
374
+ * @param value New value _(defaults to an empty object)_
375
+ */
376
+ set(
377
+ value?:
378
+ | Store
379
+ | (() => null | undefined | Store | Promise<null | undefined | Store>)
380
+ | Promise<null | undefined | Store>,
381
+ ): void;
382
+
383
+ /**
384
+ * Set a value by key
385
+ *
386
+ * @param key Key of the value to set
387
+ * @param value New value
388
+ */
389
+ set<Key extends keyof Store>(
390
+ key: Key,
391
+ value: Store[Key] | (() => Store[Key] | Promise<Store[Key]>) | Promise<Store[Key]>,
392
+ ): void;
393
+
394
+ /**
395
+ * Set a value by key
396
+ *
397
+ * @param key Key of the value to set
398
+ * @param value New value
399
+ */
400
+ set(key: Key, value: unknown): void;
401
+
402
+ /**
403
+ * Subscribe to changes
404
+ *
405
+ * @param callback Callback for changes
406
+ * @returns Unsubscribe callback
407
+ */
408
+ subscribe(callback: (value: Store) => void): Unsubscribe;
409
+
410
+ /**
411
+ * Subscribe to changes for a specific key
412
+ *
413
+ * @param key Key of the value to subscribe to
414
+ * @param callback Callback for changes
415
+ * @returns Unsubscribe callback
416
+ */
417
+ subscribe<Key extends keyof Store>(
418
+ key: Key,
419
+ callback: (value: Store[Key] | undefined) => void,
420
+ ): Unsubscribe;
421
+
422
+ /**
423
+ * Subscribe to changes for a specific key
424
+ *
425
+ * @param key Key of the value to subscribe to
426
+ * @param callback Callback for changes
427
+ * @returns Unsubscribe callback
428
+ */
429
+ subscribe(key: Key, callback: (value: unknown) => void): Unsubscribe;
430
+
431
+ /**
432
+ * Unsubscribe from changes for a specific key
433
+ *
434
+ * @param key Key of the value to unsubscribe from
435
+ * @param callback Callback to unsubscribe
436
+ */
437
+ unsubscribe<Key extends keyof Store>(
438
+ key: Key,
439
+ callback: (value: Store[Key] | undefined) => void,
440
+ ): void;
441
+
442
+ /**
443
+ * Unsubscribe from changes for a specific key
444
+ *
445
+ * @param key Key of the value to unsubscribe from
446
+ * @param callback Callback to unsubscribe
447
+ */
448
+ unsubscribe(key: Key, callback: (value: unknown | undefined) => void): void;
449
+
450
+ /**
451
+ * Unsubscribe from changes
452
+ *
453
+ * @param callback Callback to unsubscribe
454
+ */
455
+ unsubscribe(callback: (value: Store) => void): void;
456
+
457
+ /**
458
+ * Update the value _(based on the current value)_
459
+ *
460
+ * @param callback Callback to update the value
461
+ */
462
+ update(callback: (value: Store) => Store): void;
463
+ } & Reactive<Store>;
464
+
465
+ export type ReadonlyInstances<Value> = {
466
+ frozen?: ReadonlyFrozenSignal<Value>;
467
+ original?: ReadonlySignal<Value>;
468
+ };
469
+
470
+ export type ReadonlyFrozenSignal<Value> = ReadonlySignal<ReadonlySignalValue<Value>>;
471
+
472
+ export type ReadonlySignal<Value> = {
473
+ /**
474
+ * Is the signal frozen?
475
+ */
476
+ get frozen(): boolean;
477
+ } & Reactive<Value> &
478
+ SimpleReactive<Value>;
479
+
480
+ export type ReadonlySignalValue<Value> = Value extends unknown[]
481
+ ? Readonly<Value>
482
+ : Value extends PlainObject
483
+ ? Readonly<Value>
484
+ : Value;
485
+
56
486
  export type SetValueInProxyParameters<Value, Equal> = {
57
487
  target: Value;
58
488
  property: PropertyKey;
@@ -62,6 +492,79 @@ export type SetValueInProxyParameters<Value, Equal> = {
62
492
  length?: Signal<number>;
63
493
  };
64
494
 
495
+ export type Signal<Value> = {
496
+ /**
497
+ * Get a readonly version of the signal
498
+ *
499
+ * @param frozen Freeze the signal? _(defaults to `false`)_
500
+ * @returns Readonly signal
501
+ */
502
+ asReadonly(frozen: true): ReadonlyFrozenSignal<Value>;
503
+
504
+ /**
505
+ * Get a readonly version of the signal
506
+ *
507
+ * @param frozen Freeze the signal? _(defaults to `false`)_
508
+ * @returns Readonly signal
509
+ */
510
+ asReadonly(
511
+ frozen?: boolean,
512
+ ): typeof frozen extends true ? ReadonlyFrozenSignal<Value> : ReadonlySignal<Value>;
513
+
514
+ /**
515
+ * Set the value
516
+ *
517
+ * @param value New value
518
+ */
519
+ set(value: Value | (() => Value | Promise<Value>) | Promise<Value>): void;
520
+
521
+ /**
522
+ * Update the value _(based on the current value)_
523
+ *
524
+ * @param callback Callback to update the value
525
+ */
526
+ update(callback: (value: Value) => Value): void;
527
+ } & Reactive<Value> &
528
+ SimpleReactive<Value>;
529
+
530
+ type SimpleReactive<Value> = {
531
+ /**
532
+ * Get the value
533
+ *
534
+ * @returns Current value
535
+ */
536
+ get(): Value;
537
+
538
+ /**
539
+ * Get the value _(without reactivity)_
540
+ *
541
+ * @returns Current value
542
+ */
543
+ peek(): Value;
544
+
545
+ /**
546
+ * Subscribe to changes
547
+ *
548
+ * @param callback Callback for changes
549
+ * @returns Unsubscribe callback
550
+ */
551
+ subscribe(callback: (value: Value) => void): Unsubscribe;
552
+
553
+ /**
554
+ * Unsubscribe from changes
555
+ *
556
+ * @param callback Callback to unsubscribe
557
+ */
558
+ unsubscribe(callback: (value: Value) => void): void;
559
+ };
560
+
561
+ export type Subscription = {
562
+ callback: GenericCallback;
563
+ state: ReactiveState<unknown, never>;
564
+
565
+ destroy(): void;
566
+ };
567
+
65
568
  /**
66
569
  * Unsubscribe from changes
67
570
  */
@@ -1,22 +1,4 @@
1
- import type {GenericCallback} from '@oscarpalmer/atoms/models';
2
- import type {ReactiveState, Unsubscribe} from './models';
3
-
4
- export class Subscription {
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
-
12
- callback(state.value);
13
- }
14
-
15
- destroy(): void {
16
- this.callback = noop;
17
- this.state = undefined as never;
18
- }
19
- }
1
+ import type {ReactiveState, Subscription, Unsubscribe} from './models';
20
2
 
21
3
  export function noop(): void {}
22
4
 
@@ -28,13 +10,31 @@ export function subscribe<Value>(
28
10
  return noop;
29
11
  }
30
12
 
31
- state.subscriptions.set(callback, new Subscription(state, callback));
13
+ state.subscriptions.set(callback, subscription(state, callback));
32
14
 
33
15
  return () => {
34
16
  unsubscribe(state, callback);
35
17
  };
36
18
  }
37
19
 
20
+ function subscription<Value>(
21
+ state: ReactiveState<Value, never>,
22
+ callback: (value: Value) => void,
23
+ ): Subscription {
24
+ const instance: Subscription = {
25
+ callback,
26
+ state,
27
+ destroy: () => {
28
+ instance.callback = noop;
29
+ instance.state = undefined as never;
30
+ },
31
+ };
32
+
33
+ callback(state.value);
34
+
35
+ return instance;
36
+ }
37
+
38
38
  export function unsubscribe<Value>(
39
39
  state: ReactiveState<Value, never>,
40
40
  callback: (value: Value) => void,