@sigrea/vue 0.3.1 → 0.5.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.
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  - **Computed subscriptions.** `useComputed` subscribes to computed values, mirroring Vue's `computed` while tracking through Sigrea scopes.
7
7
  - **Deep signal subscriptions.** `useDeepSignal` subscribes to deep signal objects and exposes them as mutable refs with automatic cleanup.
8
8
  - **Two-way bindings.** `useMutableSignal` wraps primitive signals as `WritableComputedRef` for two-way bindings like `v-model`.
9
- - **Molecule lifecycles.** `useMolcule` mounts molecule factories and binds their lifecycles to Vue components.
9
+ - **Molecule lifecycles.** `useMolecule` mounts molecule factories and binds their lifecycles to Vue components.
10
10
 
11
11
  ## Table of Contents
12
12
 
@@ -21,7 +21,7 @@
21
21
  - [useComputed](#usecomputed)
22
22
  - [useDeepSignal](#usedeepsignal)
23
23
  - [useMutableSignal](#usemutablesignal)
24
- - [useMolcule](#usemolcule)
24
+ - [useMolecule](#usemolecule)
25
25
  - [Testing](#testing)
26
26
  - [Handling Scope Cleanup Errors](#handling-scope-cleanup-errors)
27
27
  - [Development](#development)
@@ -57,39 +57,62 @@ const value = useSignal(count);
57
57
 
58
58
  ```ts
59
59
  // CounterMolecule.ts
60
- import { molecule, signal } from "@sigrea/core";
60
+ import { molecule, readonly, signal } from "@sigrea/core";
61
61
 
62
- export const CounterMolecule = molecule((props: { initialCount: number }) => {
62
+ type CounterProps = {
63
+ initialCount: number;
64
+ initialStep: number;
65
+ };
66
+
67
+ export const CounterMolecule = molecule((props: CounterProps) => {
63
68
  const count = signal(props.initialCount);
69
+ const step = signal(props.initialStep);
64
70
 
65
- const increment = () => {
66
- count.value += 1;
67
- };
71
+ function setStep(next: number) {
72
+ step.value = next;
73
+ }
74
+
75
+ function increment() {
76
+ count.value += step.value;
77
+ }
68
78
 
69
- const reset = () => {
79
+ function reset() {
70
80
  count.value = props.initialCount;
71
- };
81
+ }
72
82
 
73
- return { count, increment, reset };
83
+ return {
84
+ count: readonly(count),
85
+ step: readonly(step),
86
+ setStep,
87
+ increment,
88
+ reset,
89
+ };
74
90
  });
75
91
  ```
76
92
 
77
93
  ```vue
78
94
  <!-- Counter.vue -->
79
95
  <script setup lang="ts">
80
- import { useMolcule, useSignal } from "@sigrea/vue";
96
+ import { useMolecule, useSignal } from "@sigrea/vue";
81
97
  import { CounterMolecule } from "./CounterMolecule";
82
98
 
83
- const props = defineProps<{ initialCount: number }>();
84
- const counter = useMolcule(CounterMolecule, props);
85
- const value = useSignal(counter.count);
99
+ const props = defineProps<{ initialCount: number; initialStep: number }>();
100
+
101
+ const counter = useMolecule(CounterMolecule, {
102
+ initialCount: props.initialCount,
103
+ initialStep: props.initialStep,
104
+ });
105
+
106
+ const count = useSignal(counter.count);
107
+ const step = useSignal(counter.step);
86
108
  </script>
87
109
 
88
110
  <template>
89
111
  <div>
90
- <span>{{ value }}</span>
112
+ <span>{{ count }}</span>
91
113
  <button @click="counter.increment">Increment</button>
92
114
  <button @click="counter.reset">Reset</button>
115
+ <button @click="counter.setStep(step + 1)">Step +</button>
93
116
  </div>
94
117
  </template>
95
118
  ```
@@ -122,7 +145,7 @@ const model = useMutableSignal(count);
122
145
  import { deepSignal } from "@sigrea/core";
123
146
  import { useDeepSignal } from "@sigrea/vue";
124
147
 
125
- const profile = deepSignal({ name: "Sigrea" });
148
+ const profile = deepSignal({ name: "Mendako" });
126
149
  const model = useDeepSignal(profile);
127
150
  </script>
128
151
 
@@ -170,10 +193,10 @@ function useMutableSignal<T>(signal: Signal<T>): WritableComputedRef<T>
170
193
 
171
194
  Wraps a Sigrea signal as a Vue `WritableComputedRef` for two-way bindings like `v-model`. Expects a writable signal created by `signal()`. Passing a readonly signal throws at runtime.
172
195
 
173
- ### useMolcule
196
+ ### useMolecule
174
197
 
175
198
  ```ts
176
- function useMolcule<TReturn extends object, TProps = void>(
199
+ function useMolecule<TReturn extends object, TProps extends object | void = void>(
177
200
  molecule: MoleculeFactory<TReturn, TProps>,
178
201
  ...args: MoleculeArgs<TProps>
179
202
  ): MoleculeInstance<TReturn>
@@ -181,6 +204,20 @@ function useMolcule<TReturn extends object, TProps = void>(
181
204
 
182
205
  Mounts a molecule factory and returns its MoleculeInstance. Sigrea augments the molecule with lifecycle metadata: `onMount` callbacks run after the component mounts, and `onUnmount` callbacks run before it unmounts.
183
206
 
207
+ **KeepAlive Support**
208
+
209
+ When used inside Vue's `<KeepAlive>`, molecule side effects are automatically managed for optimal resource efficiency:
210
+
211
+ - **On deactivation** (`onDeactivated`): `watch` effects and ongoing work are paused via `unmountMolecule`. The molecule instance itself remains alive, preserving its internal state.
212
+ - **On reactivation** (`onActivated`): Side effects resume via `mountMolecule`, allowing watches and subscriptions to pick up where they left off.
213
+ - **On final unmount**: The molecule is fully disposed via `disposeMolecule`, releasing all resources.
214
+
215
+ This design prevents unnecessary computation and subscriptions while components are cached but invisible, reducing CPU and memory usage without losing state.
216
+
217
+ **Props Handling**
218
+
219
+ Props are treated as an initial snapshot. Updating component props does not recreate the molecule instance or update the snapshot; model dynamic values via signals or explicit molecule methods (for example, `setStep`).
220
+
184
221
  ## Testing
185
222
 
186
223
  ```ts
package/dist/index.cjs CHANGED
@@ -3,13 +3,31 @@
3
3
  const vue = require('vue');
4
4
  const core = require('@sigrea/core');
5
5
 
6
- function useMolcule(molecule, ...args) {
6
+ function useMolecule(molecule, ...args) {
7
7
  if (vue.getCurrentInstance() === null) {
8
8
  throw new Error(
9
- "useMolcule can only be used within a Vue component setup()."
9
+ "useMolecule can only be used within a Vue component setup()."
10
10
  );
11
11
  }
12
- const instance = molecule(...args);
12
+ const props = args.length === 0 ? void 0 : args[0];
13
+ if (props !== void 0 && (typeof props !== "object" || props === null)) {
14
+ throw new TypeError("useMolecule props must be an object.");
15
+ }
16
+ const snapshot = props === void 0 ? void 0 : { ...vue.toRaw(props) };
17
+ const moleculeArgs = snapshot === void 0 ? [] : [snapshot];
18
+ const instance = molecule(...moleculeArgs);
19
+ vue.onMounted(() => {
20
+ core.mountMolecule(instance);
21
+ });
22
+ vue.onActivated(() => {
23
+ core.mountMolecule(instance);
24
+ });
25
+ vue.onDeactivated(() => {
26
+ core.unmountMolecule(instance);
27
+ });
28
+ vue.onBeforeUnmount(() => {
29
+ core.unmountMolecule(instance);
30
+ });
13
31
  vue.onScopeDispose(() => {
14
32
  core.disposeMolecule(instance);
15
33
  });
@@ -85,7 +103,7 @@ function useMutableSignal(source) {
85
103
 
86
104
  exports.useComputed = useComputed;
87
105
  exports.useDeepSignal = useDeepSignal;
88
- exports.useMolcule = useMolcule;
106
+ exports.useMolecule = useMolecule;
89
107
  exports.useMutableSignal = useMutableSignal;
90
108
  exports.useSignal = useSignal;
91
109
  exports.useSnapshot = useSnapshot;
package/dist/index.d.cts CHANGED
@@ -2,7 +2,7 @@ import { MoleculeFactory, MoleculeArgs, MoleculeInstance, Signal, ReadonlySignal
2
2
  import * as vue from 'vue';
3
3
  import { DeepReadonly, ShallowRef } from 'vue';
4
4
 
5
- declare function useMolcule<TReturn extends object, TProps = void>(molecule: MoleculeFactory<TReturn, TProps>, ...args: MoleculeArgs<TProps>): MoleculeInstance<TReturn>;
5
+ declare function useMolecule<TReturn extends object, TProps extends object | void = void>(molecule: MoleculeFactory<TReturn, TProps>, ...args: MoleculeArgs<TProps>): MoleculeInstance<TReturn>;
6
6
 
7
7
  type ReadableSignal<T> = Signal<T> | ReadonlySignal<T>;
8
8
  declare function useSignal<T>(source: ReadableSignal<T>): Readonly<vue.Ref<vue.DeepReadonly<T>, vue.DeepReadonly<T>>>;
@@ -21,4 +21,4 @@ declare function useSnapshot<T>(handler: SnapshotHandler<T>, options: UseSnapsho
21
21
  mode: "mutable";
22
22
  }): ShallowRef<T>;
23
23
 
24
- export { useComputed, useDeepSignal, useMolcule, useMutableSignal, useSignal, useSnapshot };
24
+ export { useComputed, useDeepSignal, useMolecule, useMutableSignal, useSignal, useSnapshot };
package/dist/index.d.mts CHANGED
@@ -2,7 +2,7 @@ import { MoleculeFactory, MoleculeArgs, MoleculeInstance, Signal, ReadonlySignal
2
2
  import * as vue from 'vue';
3
3
  import { DeepReadonly, ShallowRef } from 'vue';
4
4
 
5
- declare function useMolcule<TReturn extends object, TProps = void>(molecule: MoleculeFactory<TReturn, TProps>, ...args: MoleculeArgs<TProps>): MoleculeInstance<TReturn>;
5
+ declare function useMolecule<TReturn extends object, TProps extends object | void = void>(molecule: MoleculeFactory<TReturn, TProps>, ...args: MoleculeArgs<TProps>): MoleculeInstance<TReturn>;
6
6
 
7
7
  type ReadableSignal<T> = Signal<T> | ReadonlySignal<T>;
8
8
  declare function useSignal<T>(source: ReadableSignal<T>): Readonly<vue.Ref<vue.DeepReadonly<T>, vue.DeepReadonly<T>>>;
@@ -21,4 +21,4 @@ declare function useSnapshot<T>(handler: SnapshotHandler<T>, options: UseSnapsho
21
21
  mode: "mutable";
22
22
  }): ShallowRef<T>;
23
23
 
24
- export { useComputed, useDeepSignal, useMolcule, useMutableSignal, useSignal, useSnapshot };
24
+ export { useComputed, useDeepSignal, useMolecule, useMutableSignal, useSignal, useSnapshot };
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import { MoleculeFactory, MoleculeArgs, MoleculeInstance, Signal, ReadonlySignal
2
2
  import * as vue from 'vue';
3
3
  import { DeepReadonly, ShallowRef } from 'vue';
4
4
 
5
- declare function useMolcule<TReturn extends object, TProps = void>(molecule: MoleculeFactory<TReturn, TProps>, ...args: MoleculeArgs<TProps>): MoleculeInstance<TReturn>;
5
+ declare function useMolecule<TReturn extends object, TProps extends object | void = void>(molecule: MoleculeFactory<TReturn, TProps>, ...args: MoleculeArgs<TProps>): MoleculeInstance<TReturn>;
6
6
 
7
7
  type ReadableSignal<T> = Signal<T> | ReadonlySignal<T>;
8
8
  declare function useSignal<T>(source: ReadableSignal<T>): Readonly<vue.Ref<vue.DeepReadonly<T>, vue.DeepReadonly<T>>>;
@@ -21,4 +21,4 @@ declare function useSnapshot<T>(handler: SnapshotHandler<T>, options: UseSnapsho
21
21
  mode: "mutable";
22
22
  }): ShallowRef<T>;
23
23
 
24
- export { useComputed, useDeepSignal, useMolcule, useMutableSignal, useSignal, useSnapshot };
24
+ export { useComputed, useDeepSignal, useMolecule, useMutableSignal, useSignal, useSnapshot };
package/dist/index.mjs CHANGED
@@ -1,13 +1,31 @@
1
- import { getCurrentInstance, onScopeDispose, shallowRef, readonly, triggerRef, computed } from 'vue';
2
- import { disposeMolecule, createSignalHandler, createComputedHandler, createDeepSignalHandler } from '@sigrea/core';
1
+ import { getCurrentInstance, toRaw, onMounted, onActivated, onDeactivated, onBeforeUnmount, onScopeDispose, shallowRef, readonly, triggerRef, computed } from 'vue';
2
+ import { mountMolecule, unmountMolecule, disposeMolecule, createSignalHandler, createComputedHandler, createDeepSignalHandler } from '@sigrea/core';
3
3
 
4
- function useMolcule(molecule, ...args) {
4
+ function useMolecule(molecule, ...args) {
5
5
  if (getCurrentInstance() === null) {
6
6
  throw new Error(
7
- "useMolcule can only be used within a Vue component setup()."
7
+ "useMolecule can only be used within a Vue component setup()."
8
8
  );
9
9
  }
10
- const instance = molecule(...args);
10
+ const props = args.length === 0 ? void 0 : args[0];
11
+ if (props !== void 0 && (typeof props !== "object" || props === null)) {
12
+ throw new TypeError("useMolecule props must be an object.");
13
+ }
14
+ const snapshot = props === void 0 ? void 0 : { ...toRaw(props) };
15
+ const moleculeArgs = snapshot === void 0 ? [] : [snapshot];
16
+ const instance = molecule(...moleculeArgs);
17
+ onMounted(() => {
18
+ mountMolecule(instance);
19
+ });
20
+ onActivated(() => {
21
+ mountMolecule(instance);
22
+ });
23
+ onDeactivated(() => {
24
+ unmountMolecule(instance);
25
+ });
26
+ onBeforeUnmount(() => {
27
+ unmountMolecule(instance);
28
+ });
11
29
  onScopeDispose(() => {
12
30
  disposeMolecule(instance);
13
31
  });
@@ -81,4 +99,4 @@ function useMutableSignal(source) {
81
99
  });
82
100
  }
83
101
 
84
- export { useComputed, useDeepSignal, useMolcule, useMutableSignal, useSignal, useSnapshot };
102
+ export { useComputed, useDeepSignal, useMolecule, useMutableSignal, useSignal, useSnapshot };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sigrea/vue",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "Vue adapter bindings for Sigrea molecule modules.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -53,23 +53,25 @@
53
53
  "cicheck": "pnpm test && pnpm typecheck && pnpm format:fix"
54
54
  },
55
55
  "peerDependencies": {
56
- "@sigrea/core": "^0.4.3",
56
+ "@sigrea/core": "^0.5.0",
57
57
  "vue": "^3.4.0"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@biomejs/biome": "1.9.4",
61
+ "@sigrea/core": "^0.5.0",
61
62
  "@vitejs/plugin-vue": "^5.1.4",
62
- "@vue/test-utils": "^2.4.0",
63
63
  "@vitest/coverage-v8": "^3.2.4",
64
+ "@vue/test-utils": "^2.4.0",
65
+ "baseline-browser-mapping": "^2.9.13",
64
66
  "changelogen": "^0.6.2",
67
+ "jsdom": "^24.1.3",
65
68
  "lefthook": "1.13.6",
66
69
  "tsx": "^4.20.5",
67
70
  "typescript": "5.9.3",
68
71
  "unbuild": "3.6.1",
69
72
  "vite": "^5.4.6",
70
73
  "vitest": "^3.2.4",
71
- "vue": "^3.4.0",
72
- "jsdom": "^24.1.3"
74
+ "vue": "^3.4.0"
73
75
  },
74
76
  "pnpm": {
75
77
  "onlyBuiltDependencies": [