@sigrea/vue 0.1.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 ADDED
@@ -0,0 +1,131 @@
1
+ # @sigrea/vue
2
+
3
+ `@sigrea/vue` adapts [@sigrea/core](https://www.npmjs.com/package/@sigrea/core) logic modules and signals to Vue 3’s Composition API. It keeps lifecycle scopes aligned with component mounts, forwards deep reactivity, and ships composables that feel first-class inside `<script setup>` or traditional setup functions.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @sigrea/vue @sigrea/core vue
9
+ ```
10
+
11
+ Vue 3.4+ and Node.js 20+ are required. Equivalent npm or yarn commands work as expected.
12
+
13
+ ## What This Adapter Provides
14
+
15
+ - **Signal readers** – `useSignal` consumes shallow signals and computed values inside Vue components.
16
+ - **Deep signal access** – `useDeepSignal` exposes deeply reactive objects with automatic cleanup.
17
+ - **Derived state** – `useComputed` mirrors Vue’s `computed`, but tracks through Sigrea scopes.
18
+ - **Logic lifecycles** – `useLogic` mounts `defineLogic` factories while honoring `onMount` / `onUnmount`.
19
+ - **Snapshots** – `useSnapshot` grants direct access to low-level signal handlers when needed.
20
+ - **Writable bridge** – `useMutableSignal` exposes primitive `signal()` values as `WritableComputedRef`s for two-way bindings.
21
+
22
+ ## Quick Start
23
+
24
+ ### Consume a signal
25
+
26
+ ```ts
27
+ <script setup lang="ts">
28
+ import { signal } from "@sigrea/core";
29
+ import { useSignal } from "@sigrea/vue";
30
+
31
+ const count = signal(0);
32
+ const value = useSignal(count);
33
+ </script>
34
+
35
+ <template>
36
+ <span>{{ value }}</span>
37
+ </template>
38
+ ```
39
+
40
+ ### Bridge framework-agnostic logic
41
+
42
+ ```ts
43
+ // CounterLogic.ts
44
+ import { defineLogic, signal } from "@sigrea/core";
45
+
46
+ export const CounterLogic = defineLogic<{ initialCount: number }>()((props) => {
47
+ const count = signal(props.initialCount);
48
+
49
+ const increment = () => {
50
+ count.value += 1;
51
+ };
52
+
53
+ const reset = () => {
54
+ count.value = props.initialCount;
55
+ };
56
+
57
+ return { count, increment, reset };
58
+ });
59
+ ```
60
+
61
+ ```ts
62
+ // Counter.vue
63
+ <script setup lang="ts">
64
+ import { useLogic, useSignal } from "@sigrea/vue";
65
+
66
+ import { CounterLogic } from "./CounterLogic";
67
+
68
+ const props = defineProps<{ initialCount: number }>();
69
+ const counter = useLogic(CounterLogic, props);
70
+ const value = useSignal(counter.count);
71
+ </script>
72
+
73
+ <template>
74
+ <div>
75
+ <span>{{ value }}</span>
76
+ <button @click="counter.increment">Increment</button>
77
+ <button @click="counter.reset">Reset</button>
78
+ </div>
79
+ </template>
80
+ ```
81
+
82
+ ### Bind writable primitive signals
83
+
84
+ ```ts
85
+ <script setup lang="ts">
86
+ import { signal } from "@sigrea/core";
87
+ import { useMutableSignal } from "@sigrea/vue";
88
+
89
+ const count = signal(0);
90
+ const model = useMutableSignal(count);
91
+ </script>
92
+
93
+ <template>
94
+ <label>
95
+ Count
96
+ <input type="number" v-model.number="model" />
97
+ </label>
98
+ </template>
99
+ ```
100
+
101
+ `useMutableSignal` expects a writable signal produced by `signal()`. Passing a readonly signal throws at runtime so incorrect bindings fail fast.
102
+
103
+ ### Bind deep reactive objects
104
+
105
+ ```ts
106
+ <script setup lang="ts">
107
+ import { deepSignal } from "@sigrea/core";
108
+ import { useDeepSignal } from "@sigrea/vue";
109
+
110
+ const profile = deepSignal({ name: "Sigrea" });
111
+ const model = useDeepSignal(profile);
112
+ </script>
113
+
114
+ <template>
115
+ <label>
116
+ Name
117
+ <input v-model="model.name" />
118
+ </label>
119
+ </template>
120
+ ```
121
+
122
+ ## Development
123
+
124
+ - `pnpm install` – install dependencies
125
+ - `pnpm test` – run the Vitest suite
126
+ - `pnpm build` – emit distributable artifacts
127
+ - `pnpm dev` – launch the playground counter demo
128
+
129
+ ## License
130
+
131
+ MIT — see `LICENSE`.
package/dist/index.cjs ADDED
@@ -0,0 +1,91 @@
1
+ 'use strict';
2
+
3
+ const vue = require('vue');
4
+ const core = require('@sigrea/core');
5
+
6
+ function useLogic(logic, ...args) {
7
+ if (vue.getCurrentInstance() === null) {
8
+ throw new Error(
9
+ "useLogic can only be used within a Vue component setup()."
10
+ );
11
+ }
12
+ const instance = core.mountLogic(logic, ...args);
13
+ vue.onScopeDispose(() => {
14
+ core.cleanupLogic(instance);
15
+ });
16
+ return instance;
17
+ }
18
+
19
+ function useSnapshot(handler, options) {
20
+ if (vue.getCurrentInstance() === null) {
21
+ throw new Error(
22
+ "useSnapshot can only be used within a Vue component setup()."
23
+ );
24
+ }
25
+ const state = vue.shallowRef(handler.getSnapshot().value);
26
+ const update = () => {
27
+ const next = handler.getSnapshot().value;
28
+ if (!Object.is(next, state.value)) {
29
+ state.value = next;
30
+ return;
31
+ }
32
+ vue.triggerRef(state);
33
+ };
34
+ const unsubscribe = handler.subscribe(update);
35
+ vue.onScopeDispose(() => {
36
+ unsubscribe();
37
+ });
38
+ if (options?.mode === "mutable") {
39
+ return state;
40
+ }
41
+ return vue.readonly(state);
42
+ }
43
+
44
+ function useSignal(source) {
45
+ const handler = core.createSignalHandler(source);
46
+ return useSnapshot(handler);
47
+ }
48
+
49
+ function useComputed(source) {
50
+ const handler = core.createComputedHandler(source);
51
+ return useSnapshot(handler);
52
+ }
53
+
54
+ function useDeepSignal(source) {
55
+ const handler = core.createDeepSignalHandler(source);
56
+ return useSnapshot(handler, { mode: "mutable" });
57
+ }
58
+
59
+ function assertWritableSignal(source) {
60
+ let prototype = source;
61
+ let descriptor;
62
+ while (prototype !== null) {
63
+ descriptor = Object.getOwnPropertyDescriptor(prototype, "value");
64
+ if (descriptor !== void 0) {
65
+ break;
66
+ }
67
+ prototype = Object.getPrototypeOf(prototype);
68
+ }
69
+ if (descriptor?.set === void 0) {
70
+ throw new Error(
71
+ "useMutableSignal requires a writable signal instance created by signal()."
72
+ );
73
+ }
74
+ }
75
+ function useMutableSignal(source) {
76
+ assertWritableSignal(source);
77
+ const state = useSignal(source);
78
+ return vue.computed({
79
+ get: () => state.value,
80
+ set: (value) => {
81
+ source.value = value;
82
+ }
83
+ });
84
+ }
85
+
86
+ exports.useComputed = useComputed;
87
+ exports.useDeepSignal = useDeepSignal;
88
+ exports.useLogic = useLogic;
89
+ exports.useMutableSignal = useMutableSignal;
90
+ exports.useSignal = useSignal;
91
+ exports.useSnapshot = useSnapshot;
@@ -0,0 +1,24 @@
1
+ import { LogicFunction, LogicArgs, LogicInstance, Signal, ReadonlySignal, Computed, DeepSignal, SnapshotHandler } from '@sigrea/core';
2
+ import * as vue from 'vue';
3
+ import { DeepReadonly, ShallowRef } from 'vue';
4
+
5
+ declare function useLogic<TReturn extends object, TProps = void>(logic: LogicFunction<TReturn, TProps>, ...args: LogicArgs<TProps>): LogicInstance<TReturn>;
6
+
7
+ type ReadableSignal<T> = Signal<T> | ReadonlySignal<T>;
8
+ declare function useSignal<T>(source: ReadableSignal<T>): Readonly<vue.Ref<vue.DeepReadonly<T>, vue.DeepReadonly<T>>>;
9
+
10
+ declare function useComputed<T>(source: Computed<T>): Readonly<vue.Ref<vue.DeepReadonly<T>, vue.DeepReadonly<T>>>;
11
+
12
+ declare function useDeepSignal<T extends object>(source: DeepSignal<T>): vue.ShallowRef<T>;
13
+
14
+ declare function useMutableSignal<T>(source: Signal<T>): vue.WritableComputedRef<vue.DeepReadonly<T>, T>;
15
+
16
+ interface UseSnapshotOptions {
17
+ mode?: "readonly" | "mutable";
18
+ }
19
+ declare function useSnapshot<T>(handler: SnapshotHandler<T>): DeepReadonly<ShallowRef<T>>;
20
+ declare function useSnapshot<T>(handler: SnapshotHandler<T>, options: UseSnapshotOptions & {
21
+ mode: "mutable";
22
+ }): ShallowRef<T>;
23
+
24
+ export { useComputed, useDeepSignal, useLogic, useMutableSignal, useSignal, useSnapshot };
@@ -0,0 +1,24 @@
1
+ import { LogicFunction, LogicArgs, LogicInstance, Signal, ReadonlySignal, Computed, DeepSignal, SnapshotHandler } from '@sigrea/core';
2
+ import * as vue from 'vue';
3
+ import { DeepReadonly, ShallowRef } from 'vue';
4
+
5
+ declare function useLogic<TReturn extends object, TProps = void>(logic: LogicFunction<TReturn, TProps>, ...args: LogicArgs<TProps>): LogicInstance<TReturn>;
6
+
7
+ type ReadableSignal<T> = Signal<T> | ReadonlySignal<T>;
8
+ declare function useSignal<T>(source: ReadableSignal<T>): Readonly<vue.Ref<vue.DeepReadonly<T>, vue.DeepReadonly<T>>>;
9
+
10
+ declare function useComputed<T>(source: Computed<T>): Readonly<vue.Ref<vue.DeepReadonly<T>, vue.DeepReadonly<T>>>;
11
+
12
+ declare function useDeepSignal<T extends object>(source: DeepSignal<T>): vue.ShallowRef<T>;
13
+
14
+ declare function useMutableSignal<T>(source: Signal<T>): vue.WritableComputedRef<vue.DeepReadonly<T>, T>;
15
+
16
+ interface UseSnapshotOptions {
17
+ mode?: "readonly" | "mutable";
18
+ }
19
+ declare function useSnapshot<T>(handler: SnapshotHandler<T>): DeepReadonly<ShallowRef<T>>;
20
+ declare function useSnapshot<T>(handler: SnapshotHandler<T>, options: UseSnapshotOptions & {
21
+ mode: "mutable";
22
+ }): ShallowRef<T>;
23
+
24
+ export { useComputed, useDeepSignal, useLogic, useMutableSignal, useSignal, useSnapshot };
@@ -0,0 +1,24 @@
1
+ import { LogicFunction, LogicArgs, LogicInstance, Signal, ReadonlySignal, Computed, DeepSignal, SnapshotHandler } from '@sigrea/core';
2
+ import * as vue from 'vue';
3
+ import { DeepReadonly, ShallowRef } from 'vue';
4
+
5
+ declare function useLogic<TReturn extends object, TProps = void>(logic: LogicFunction<TReturn, TProps>, ...args: LogicArgs<TProps>): LogicInstance<TReturn>;
6
+
7
+ type ReadableSignal<T> = Signal<T> | ReadonlySignal<T>;
8
+ declare function useSignal<T>(source: ReadableSignal<T>): Readonly<vue.Ref<vue.DeepReadonly<T>, vue.DeepReadonly<T>>>;
9
+
10
+ declare function useComputed<T>(source: Computed<T>): Readonly<vue.Ref<vue.DeepReadonly<T>, vue.DeepReadonly<T>>>;
11
+
12
+ declare function useDeepSignal<T extends object>(source: DeepSignal<T>): vue.ShallowRef<T>;
13
+
14
+ declare function useMutableSignal<T>(source: Signal<T>): vue.WritableComputedRef<vue.DeepReadonly<T>, T>;
15
+
16
+ interface UseSnapshotOptions {
17
+ mode?: "readonly" | "mutable";
18
+ }
19
+ declare function useSnapshot<T>(handler: SnapshotHandler<T>): DeepReadonly<ShallowRef<T>>;
20
+ declare function useSnapshot<T>(handler: SnapshotHandler<T>, options: UseSnapshotOptions & {
21
+ mode: "mutable";
22
+ }): ShallowRef<T>;
23
+
24
+ export { useComputed, useDeepSignal, useLogic, useMutableSignal, useSignal, useSnapshot };
package/dist/index.mjs ADDED
@@ -0,0 +1,84 @@
1
+ import { getCurrentInstance, onScopeDispose, shallowRef, readonly, triggerRef, computed } from 'vue';
2
+ import { mountLogic, cleanupLogic, createSignalHandler, createComputedHandler, createDeepSignalHandler } from '@sigrea/core';
3
+
4
+ function useLogic(logic, ...args) {
5
+ if (getCurrentInstance() === null) {
6
+ throw new Error(
7
+ "useLogic can only be used within a Vue component setup()."
8
+ );
9
+ }
10
+ const instance = mountLogic(logic, ...args);
11
+ onScopeDispose(() => {
12
+ cleanupLogic(instance);
13
+ });
14
+ return instance;
15
+ }
16
+
17
+ function useSnapshot(handler, options) {
18
+ if (getCurrentInstance() === null) {
19
+ throw new Error(
20
+ "useSnapshot can only be used within a Vue component setup()."
21
+ );
22
+ }
23
+ const state = shallowRef(handler.getSnapshot().value);
24
+ const update = () => {
25
+ const next = handler.getSnapshot().value;
26
+ if (!Object.is(next, state.value)) {
27
+ state.value = next;
28
+ return;
29
+ }
30
+ triggerRef(state);
31
+ };
32
+ const unsubscribe = handler.subscribe(update);
33
+ onScopeDispose(() => {
34
+ unsubscribe();
35
+ });
36
+ if (options?.mode === "mutable") {
37
+ return state;
38
+ }
39
+ return readonly(state);
40
+ }
41
+
42
+ function useSignal(source) {
43
+ const handler = createSignalHandler(source);
44
+ return useSnapshot(handler);
45
+ }
46
+
47
+ function useComputed(source) {
48
+ const handler = createComputedHandler(source);
49
+ return useSnapshot(handler);
50
+ }
51
+
52
+ function useDeepSignal(source) {
53
+ const handler = createDeepSignalHandler(source);
54
+ return useSnapshot(handler, { mode: "mutable" });
55
+ }
56
+
57
+ function assertWritableSignal(source) {
58
+ let prototype = source;
59
+ let descriptor;
60
+ while (prototype !== null) {
61
+ descriptor = Object.getOwnPropertyDescriptor(prototype, "value");
62
+ if (descriptor !== void 0) {
63
+ break;
64
+ }
65
+ prototype = Object.getPrototypeOf(prototype);
66
+ }
67
+ if (descriptor?.set === void 0) {
68
+ throw new Error(
69
+ "useMutableSignal requires a writable signal instance created by signal()."
70
+ );
71
+ }
72
+ }
73
+ function useMutableSignal(source) {
74
+ assertWritableSignal(source);
75
+ const state = useSignal(source);
76
+ return computed({
77
+ get: () => state.value,
78
+ set: (value) => {
79
+ source.value = value;
80
+ }
81
+ });
82
+ }
83
+
84
+ export { useComputed, useDeepSignal, useLogic, useMutableSignal, useSignal, useSnapshot };
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@sigrea/vue",
3
+ "version": "0.1.0",
4
+ "description": "Vue adapter bindings for Sigrea logic modules.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/sigrea/vue.git"
13
+ },
14
+ "homepage": "https://github.com/sigrea/vue#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/sigrea/vue/issues"
17
+ },
18
+ "engines": {
19
+ "node": ">=20"
20
+ },
21
+ "sideEffects": false,
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.mjs",
26
+ "require": "./dist/index.cjs"
27
+ }
28
+ },
29
+ "main": "./dist/index.cjs",
30
+ "types": "./dist/index.d.ts",
31
+ "files": [
32
+ "dist"
33
+ ],
34
+ "keywords": [
35
+ "signals",
36
+ "reactivity",
37
+ "vue",
38
+ "logic",
39
+ "typescript"
40
+ ],
41
+ "peerDependencies": {
42
+ "@sigrea/core": "^0.1.0",
43
+ "vue": "^3.4.0"
44
+ },
45
+ "devDependencies": {
46
+ "@biomejs/biome": "1.9.4",
47
+ "@changesets/cli": "^2.29.6",
48
+ "@vitejs/plugin-vue": "^5.1.4",
49
+ "@vue/test-utils": "^2.4.0",
50
+ "@vitest/coverage-v8": "^3.2.4",
51
+ "lefthook": "1.13.6",
52
+ "tsx": "^4.20.5",
53
+ "typescript": "5.9.3",
54
+ "unbuild": "3.6.1",
55
+ "vite": "^5.4.6",
56
+ "vitest": "^3.2.4",
57
+ "vue": "^3.4.0",
58
+ "jsdom": "^24.1.3"
59
+ },
60
+ "scripts": {
61
+ "dev": "vite --config playground/vite.config.ts",
62
+ "build": "unbuild",
63
+ "test": "vitest run",
64
+ "test:coverage": "vitest --coverage",
65
+ "typecheck": "tsc -p tsconfig.json --noEmit",
66
+ "format": "biome check .",
67
+ "format:fix": "biome check --write ."
68
+ }
69
+ }