@oscarpalmer/mora 0.21.0 → 0.23.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 (70) hide show
  1. package/dist/batch.js +11 -25
  2. package/dist/constants.js +34 -0
  3. package/dist/effect.js +18 -28
  4. package/dist/helpers/is.js +9 -26
  5. package/dist/helpers/proxy.js +36 -48
  6. package/dist/helpers/value.js +21 -48
  7. package/dist/index.js +3 -16
  8. package/dist/models.js +0 -0
  9. package/dist/mora.full.js +218 -130
  10. package/dist/subscription.js +22 -28
  11. package/dist/value/array.js +97 -182
  12. package/dist/value/computed.js +33 -58
  13. package/dist/value/reactive.js +29 -53
  14. package/dist/value/signal.js +21 -33
  15. package/dist/value/store.js +37 -60
  16. package/package.json +17 -24
  17. package/src/batch.ts +11 -13
  18. package/src/constants.ts +49 -0
  19. package/src/effect.ts +11 -16
  20. package/src/helpers/is.ts +41 -19
  21. package/src/helpers/proxy.ts +34 -38
  22. package/src/helpers/value.ts +19 -14
  23. package/src/index.ts +1 -1
  24. package/src/models.ts +66 -0
  25. package/src/subscription.ts +14 -16
  26. package/src/value/array.ts +87 -45
  27. package/src/value/computed.ts +29 -39
  28. package/src/value/reactive.ts +9 -24
  29. package/src/value/signal.ts +9 -3
  30. package/src/value/store.ts +40 -9
  31. package/types/batch.d.ts +3 -5
  32. package/types/constants.d.ts +14 -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 +2 -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 +28 -4
  45. package/dist/batch.cjs +0 -32
  46. package/dist/effect.cjs +0 -30
  47. package/dist/helpers/is.cjs +0 -40
  48. package/dist/helpers/proxy.cjs +0 -56
  49. package/dist/helpers/value.cjs +0 -54
  50. package/dist/index.cjs +0 -21
  51. package/dist/node_modules/@oscarpalmer/atoms/dist/internal/is.cjs +0 -17
  52. package/dist/node_modules/@oscarpalmer/atoms/dist/internal/is.js +0 -17
  53. package/dist/subscription.cjs +0 -32
  54. package/dist/value/array.cjs +0 -187
  55. package/dist/value/computed.cjs +0 -60
  56. package/dist/value/reactive.cjs +0 -55
  57. package/dist/value/signal.cjs +0 -36
  58. package/dist/value/store.cjs +0 -63
  59. package/types/batch.d.cts +0 -79
  60. package/types/effect.d.cts +0 -16
  61. package/types/helpers/is.d.cts +0 -259
  62. package/types/helpers/proxy.d.cts +0 -244
  63. package/types/helpers/value.d.cts +0 -71
  64. package/types/index.d.cts +0 -281
  65. package/types/subscription.d.cts +0 -71
  66. package/types/value/array.d.cts +0 -161
  67. package/types/value/computed.d.cts +0 -81
  68. package/types/value/reactive.d.cts +0 -68
  69. package/types/value/signal.d.cts +0 -87
  70. package/types/value/store.d.cts +0 -136
package/package.json CHANGED
@@ -4,39 +4,33 @@
4
4
  "url": "https://oscarpalmer.se"
5
5
  },
6
6
  "dependencies": {
7
- "@oscarpalmer/atoms": "^0.102"
7
+ "@oscarpalmer/atoms": "^0.114"
8
8
  },
9
9
  "description": "Signals and stuff…",
10
10
  "devDependencies": {
11
- "@biomejs/biome": "^1.9",
11
+ "@biomejs/biome": "^2.3",
12
12
  "@rollup/plugin-node-resolve": "^16",
13
- "@rollup/plugin-typescript": "^12.1",
14
- "@types/node": "^24",
15
- "@vitest/coverage-istanbul": "^3.2",
16
- "dts-bundle-generator": "^9.5",
13
+ "@rollup/plugin-typescript": "^12.3",
14
+ "@types/node": "^24.9",
15
+ "@vitest/coverage-istanbul": "^4",
17
16
  "glob": "^11",
18
- "jsdom": "^26.1",
17
+ "jsdom": "^27.1",
18
+ "rollup": "^4.52",
19
19
  "tslib": "^2.8",
20
- "typescript": "^5.8",
21
- "vite": "^6.3",
22
- "vitest": "^3.2"
20
+ "typescript": "^5.9",
21
+ "vite": "npm:rolldown-vite@latest",
22
+ "vitest": "^4"
23
23
  },
24
24
  "exports": {
25
+ "./package.json": "./package.json",
25
26
  ".": {
26
- "import": {
27
- "types": "./types/index.d.ts",
28
- "default": "./dist/index.js"
29
- },
30
- "require": {
31
- "types": "./types/index.d.cts",
32
- "default": "./dist/index.cjs"
33
- }
27
+ "types": "./types/index.d.ts",
28
+ "default": "./dist/index.js"
34
29
  }
35
30
  },
36
31
  "files": ["dist", "src", "types"],
37
32
  "keywords": ["signal", "signals", "reactive"],
38
33
  "license": "MIT",
39
- "main": "./dist/index.cjs",
40
34
  "module": "./dist/index.js",
41
35
  "name": "@oscarpalmer/mora",
42
36
  "repository": {
@@ -44,15 +38,14 @@
44
38
  "url": "git+https://github.com/oscarpalmer/mora.git"
45
39
  },
46
40
  "scripts": {
47
- "build": "npm run clean && npm run build:js && npm run rollup && npm run types",
48
- "build:js": "npx vite build",
41
+ "build": "npm run clean && npx vite build && npm run rollup:build && npx tsc",
49
42
  "clean": "rm -rf ./dist && rm -rf ./types && rm -f ./tsconfig.tsbuildinfo",
50
- "rollup": "npx rollup -c",
43
+ "rollup:build": "npx rollup -c",
44
+ "rollup:watch": "npx rollup -c --watch",
51
45
  "test": "npx vitest --coverage",
52
- "types": "npx tsc && npx dts-bundle-generator --config ./dts.config.cts --silent",
53
46
  "watch": "npx vite build --watch"
54
47
  },
55
48
  "type": "module",
56
49
  "types": "./types/index.d.ts",
57
- "version": "0.21.0"
50
+ "version": "0.23.0"
58
51
  }
package/src/batch.ts CHANGED
@@ -1,12 +1,12 @@
1
- import {type Effect, runEffect} from './effect';
1
+ import {BATCH} from './constants';
2
+ import {runEffect} from './effect';
2
3
  import {isEffect} from './helpers/is';
3
- import type {Subscription} from './subscription';
4
4
 
5
5
  export function flushHandlers(): void {
6
- while (batchDepth === 0 && batchedHandlers.size > 0) {
7
- const handlers = [...batchedHandlers];
6
+ while (BATCH.depth === 0 && BATCH.handlers.size > 0) {
7
+ const handlers = [...BATCH.handlers];
8
8
 
9
- batchedHandlers.clear();
9
+ BATCH.handlers.clear();
10
10
 
11
11
  for (const handler of handlers) {
12
12
  if (isEffect(handler)) {
@@ -19,23 +19,21 @@ export function flushHandlers(): void {
19
19
  }
20
20
 
21
21
  /**
22
- * Start batching effects _(use `stopBatch` to flush and run batched effects)_
22
+ * Start batching effects
23
+ *
24
+ * _(Use {@link stopBatch} to flush and run batched effects)_
23
25
  */
24
26
  export function startBatch(): void {
25
- batchDepth += 1;
27
+ BATCH.depth += 1;
26
28
  }
27
29
 
28
30
  /**
29
31
  * Stop batching effects and flush _(run)_ them
30
32
  */
31
33
  export function stopBatch(): void {
32
- if (batchDepth > 0) {
33
- batchDepth -= 1;
34
+ if (BATCH.depth > 0) {
35
+ BATCH.depth -= 1;
34
36
  }
35
37
 
36
38
  flushHandlers();
37
39
  }
38
-
39
- export const batchedHandlers = new Set<Effect | Subscription>();
40
-
41
- export let batchDepth = 0;
@@ -0,0 +1,49 @@
1
+ import type {Effect} from './effect';
2
+ import type {Active, Batch} from './models';
3
+ import type {Subscription} from './subscription';
4
+
5
+ export const ACTIVE: Active = {};
6
+
7
+ export const ARRAY_THRESHOLD = 100;
8
+
9
+ export const ARRAY_OFFSET = 25;
10
+
11
+ export const ARRAY_PEEK = 10;
12
+
13
+ export const BATCH: Batch = {
14
+ depth: 0,
15
+ handlers: new Set<Effect | Subscription>(),
16
+ };
17
+
18
+ export const METHODS_AFFECTING_LENGTH: Set<string> = new Set<string>([
19
+ 'pop',
20
+ 'push',
21
+ 'shift',
22
+ 'unshift',
23
+ ]);
24
+
25
+ export const METHODS_UPDATE: Set<string> = new Set<string>([
26
+ ...METHODS_AFFECTING_LENGTH,
27
+ 'copyWithin',
28
+ 'fill',
29
+ 'reverse',
30
+ 'sort',
31
+ 'splice',
32
+ ]);
33
+
34
+ export const NAME_ARRAY = 'array';
35
+
36
+ export const NAME_COMPUTED = 'computed';
37
+
38
+ export const NAME_EFFECT = 'effect';
39
+
40
+ export const NAME_SIGNAL = 'signal';
41
+
42
+ export const NAME_STORE = 'store';
43
+
44
+ export const NAMES: Set<string> = new Set([
45
+ NAME_ARRAY,
46
+ NAME_COMPUTED,
47
+ NAME_SIGNAL,
48
+ NAME_STORE,
49
+ ]);
package/src/effect.ts CHANGED
@@ -1,13 +1,6 @@
1
1
  import type {GenericCallback} from '@oscarpalmer/atoms/models';
2
- import {effectName} from './helpers/is';
3
-
4
- type EffectState = {
5
- callback: GenericCallback;
6
- };
7
-
8
- type InternalEffect = {
9
- state: EffectState;
10
- };
2
+ import {ACTIVE, NAME_EFFECT} from './constants';
3
+ import type {EffectState, InternalEffect} from './models';
11
4
 
12
5
  export class Effect {
13
6
  private declare readonly $mora: string;
@@ -16,7 +9,7 @@ export class Effect {
16
9
 
17
10
  constructor(callback: GenericCallback) {
18
11
  Object.defineProperty(this, '$mora', {
19
- value: effectName,
12
+ value: NAME_EFFECT,
20
13
  });
21
14
 
22
15
  this.state = {
@@ -28,22 +21,24 @@ export class Effect {
28
21
  }
29
22
 
30
23
  export function runEffect(effect: Effect): void {
31
- const previousEffect = activeEffect;
24
+ const previousEffect = ACTIVE.effect;
32
25
 
33
- activeEffect = effect;
26
+ ACTIVE.effect = effect;
34
27
 
35
28
  try {
36
29
  (effect as unknown as InternalEffect).state.callback();
37
30
  } finally {
38
- activeEffect = previousEffect;
31
+ ACTIVE.effect = previousEffect;
39
32
  }
40
33
  }
41
34
 
42
35
  /**
43
36
  * Create an effect
37
+ * @param callback Callback for handling signal effects
38
+ * @returns Effect
44
39
  */
45
40
  export function effect(callback: GenericCallback): Effect {
46
- return new Effect(callback);
41
+ return typeof callback === 'function'
42
+ ? new Effect(callback)
43
+ : (undefined as never);
47
44
  }
48
-
49
- export let activeEffect: Effect | undefined;
package/src/helpers/is.ts CHANGED
@@ -1,4 +1,12 @@
1
1
  import type {PlainObject} from '@oscarpalmer/atoms/models';
2
+ import {
3
+ NAME_ARRAY,
4
+ NAME_COMPUTED,
5
+ NAME_EFFECT,
6
+ NAME_SIGNAL,
7
+ NAME_STORE,
8
+ NAMES,
9
+ } from '../constants';
2
10
  import type {Effect} from '../effect';
3
11
  import type {ReactiveArray} from '../value/array';
4
12
  import type {Computed} from '../value/computed';
@@ -8,23 +16,29 @@ import type {Store} from '../value/store';
8
16
 
9
17
  /**
10
18
  * Is the value a reactive array?
19
+ * @param value Value to check
20
+ * @returns True if value is a {@link ReactiveArray}
11
21
  */
12
- export function isArray(value: unknown): value is ReactiveArray<unknown> {
13
- return isMora<ReactiveArray<unknown>>(value, arrayName);
22
+ export function isArray<Item>(value: unknown): value is ReactiveArray<Item> {
23
+ return isMora<ReactiveArray<Item>>(value, NAME_ARRAY);
14
24
  }
15
25
 
16
26
  /**
17
27
  * Is the value a computed signal?
28
+ * @param value Value to check
29
+ * @returns True if value is a {@link Computed}
18
30
  */
19
- export function isComputed(value: unknown): value is Computed<unknown> {
20
- return isMora<Computed<unknown>>(value, computedName);
31
+ export function isComputed<Value>(value: unknown): value is Computed<Value> {
32
+ return isMora<Computed<Value>>(value, NAME_COMPUTED);
21
33
  }
22
34
 
23
35
  /**
24
36
  * Is the value an effect?
37
+ * @param value Value to check
38
+ * @returns True if value is an {@link Effect}
25
39
  */
26
40
  export function isEffect(value: unknown): value is Effect {
27
- return isMora<Effect>(value, effectName);
41
+ return isMora<Effect>(value, NAME_EFFECT);
28
42
  }
29
43
 
30
44
  function isMora<T>(value: unknown, name: string | Set<string>): value is T {
@@ -38,25 +52,33 @@ function isMora<T>(value: unknown, name: string | Set<string>): value is T {
38
52
  );
39
53
  }
40
54
 
41
- export function isReactive(value: unknown): value is Reactive<unknown> {
42
- return isMora<Reactive<unknown>>(value, reactiveNames);
55
+ /**
56
+ * Is the value reactive?
57
+ * @param value Value to check
58
+ * @returns True if value is a {@link Reactive}
59
+ */
60
+ export function isReactive<Value, Equal = Value>(
61
+ value: unknown,
62
+ ): value is Reactive<Value, Equal> {
63
+ return isMora<Reactive<Value, Equal>>(value, NAMES);
43
64
  }
44
65
 
45
66
  /**
46
67
  * Is the value a signal?
68
+ * @param value Value to check
69
+ * @returns True if value is a {@link Signal}
47
70
  */
48
- export function isSignal(value: unknown): value is Signal<unknown> {
49
- return isMora<Signal<unknown>>(value, signalName);
71
+ export function isSignal<Value>(value: unknown): value is Signal<Value> {
72
+ return isMora<Signal<Value>>(value, NAME_SIGNAL);
50
73
  }
51
74
 
52
- export function isStore(value: unknown): value is Store<PlainObject> {
53
- return isMora<Reactive<unknown>>(value, storeName);
75
+ /**
76
+ * Is the value a reactive store?
77
+ * @param value Value to check
78
+ * @returns True if value is a {@link Store}
79
+ */
80
+ export function isStore<Value extends PlainObject>(
81
+ value: unknown,
82
+ ): value is Store<Value> {
83
+ return isMora<Store<Value>>(value, NAME_STORE);
54
84
  }
55
-
56
- export const arrayName = 'array';
57
- export const computedName = 'computed';
58
- export const effectName = 'effect';
59
- export const signalName = 'signal';
60
- export const storeName = 'store';
61
-
62
- const reactiveNames = new Set([arrayName, computedName, signalName, storeName]);
@@ -4,50 +4,49 @@ import type {
4
4
  PlainObject,
5
5
  } from '@oscarpalmer/atoms/models';
6
6
  import {startBatch, stopBatch} from '../batch';
7
+ import type {SetValueInProxyParameters} from '../models';
7
8
  import type {ReactiveArray} from '../value/array';
8
9
  import {type Computed, computed} from '../value/computed';
9
- import type {ReactiveState} from '../value/reactive';
10
- import type {Signal} from '../value/signal';
11
10
  import type {Store} from '../value/store';
12
11
  import {emitValue} from './value';
13
12
 
14
13
  export function getReactiveValueInProxy<Value>(
15
- array: ReactiveArray<Value>,
16
- mapped: Map<Key, Computed<unknown>>,
17
- index: number,
18
- isArray: true,
19
- ): Computed<unknown>;
14
+ array: ReactiveArray<Value>,
15
+ mapped: Map<Key, Computed<unknown>>,
16
+ index: number,
17
+ isArray: true,
18
+ ): Computed<unknown>;
20
19
 
21
20
  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>;
21
+ store: Store<Value>,
22
+ mapped: Map<Key, Computed<unknown>>,
23
+ key: Key,
24
+ isArray: false,
25
+ ): Computed<unknown>;
27
26
 
28
27
  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
- }
28
+ reactive: ReactiveArray<unknown> | Store<PlainObject>,
29
+ mapped: Map<Key, Computed<unknown>>,
30
+ key: Key,
31
+ isArray: boolean,
32
+ ): Computed<unknown> {
33
+ let item = mapped.get(key);
34
+
35
+ if (item == null) {
36
+ item = computed(() => {
37
+ const value = reactive.get();
38
+
39
+ return isArray
40
+ ? (value as unknown[]).at(key as number)
41
+ : (value as PlainObject)[key];
42
+ }) as Computed<unknown>;
47
43
 
48
- return item;
44
+ mapped.set(key, item);
49
45
  }
50
46
 
47
+ return item;
48
+ }
49
+
51
50
  export function setProxyValue(
52
51
  proxy: ArrayOrPlainObject,
53
52
  value: ArrayOrPlainObject,
@@ -83,18 +82,15 @@ export function setProxyValue(
83
82
  }
84
83
 
85
84
  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>,
85
+ parameters: SetValueInProxyParameters<Value, Equal>,
92
86
  ): boolean {
87
+ const {isArray, length, property, state, target, value} = parameters;
88
+
93
89
  if (isArray) {
94
90
  const isIndex = !Number.isNaN(Number(property));
95
91
  const isLength = property === 'length';
96
92
 
97
- if (!isIndex && !isLength) {
93
+ if (!(isIndex || isLength)) {
98
94
  return Reflect.set(target, property, value);
99
95
  }
100
96
  }
@@ -1,7 +1,12 @@
1
- import {batchDepth, batchedHandlers, flushHandlers} from '../batch';
2
- import {activeEffect} from '../effect';
3
- import {type InternalComputed, activeComputed} 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,9 +7,9 @@ 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';
13
14
  export {signal, type Signal} from './value/signal';
14
15
  export {store, type Store} from './value/store';
15
-
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
  }