Skip to content

Commit 8ea9bbb

Browse files
authored
Merge pull request #1 from srav001/0.0.2
0.0.2
2 parents 05097b3 + f0e00f2 commit 8ea9bbb

25 files changed

+3429
-141
lines changed

.eslintignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
* This ignores root files & root directories, then un-ignores the root src directory *
2+
/*
3+
/*/
4+
!/src/

.eslintrc.cjs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
module.exports = {
2+
root: true,
3+
env: {
4+
browser: true,
5+
es6: true
6+
},
7+
plugins: ['@typescript-eslint', 'sonarjs'],
8+
extends: [
9+
'eslint:recommended',
10+
'plugin:sonarjs/recommended',
11+
'plugin:vue/vue3-recommended',
12+
'@vue/eslint-config-typescript/recommended',
13+
'prettier'
14+
],
15+
settings: {
16+
'import/resolver': {
17+
typescript: {}
18+
}
19+
},
20+
parser: 'vue-eslint-parser',
21+
parserOptions: {
22+
parser: '@typescript-eslint/parser',
23+
project: './tsconfig.json',
24+
ecmaVersion: 'latest'
25+
},
26+
rules: {
27+
'no-console': 'warn',
28+
'no-debugger': 'warn',
29+
'@typescript-eslint/consistent-type-imports': 2,
30+
'@typescript-eslint/consistent-type-exports': 2,
31+
'@typescript-eslint/no-explicit-any': ['error', { ignoreRestArgs: true }]
32+
}
33+
};

.gitignore

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
pnpm-debug.log*
6+
7+
node_modules
8+
.DS_Store
9+
dist
10+
dist-ssr
11+
coverage
12+
*.local
13+
14+
/cypress/videos/
15+
/cypress/screenshots/
16+
17+
# Editor directories and files
18+
.vscode/*
19+
!.vscode/extensions.json
20+
.idea
21+
*.suo
22+
*.ntvs*
23+
*.njsproj
24+
*.sln
25+
*.sw?

.prettierrc.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module.exports = {
2+
trailingComma: 'none',
3+
printWidth: 120,
4+
useTabs: true,
5+
tabWidth: 2,
6+
semi: true,
7+
singleQuote: true,
8+
arrowParens: 'avoid',
9+
bracketSpacing: true,
10+
bracketSameLine: true
11+
};

.vscode/extensions.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"recommendations": [
3+
"Vue.volar",
4+
"usernamehw.errorlens",
5+
"dbaeumer.vscode-eslint",
6+
"esbenp.prettier-vscode",
7+
"wayou.vscode-todo-highlight"
8+
]
9+
}

README.md

Lines changed: 240 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,241 @@
11
# vue-subscription
2-
A subscriber to replace the EventBus in Vue.
2+
3+
This Vue package provides a simple way to create reactive subscriptions that can be used to observe changes to a value and execute a list of subscribers when the value changes. It also includes methods to mutate the value and trigger subscribers manually.
4+
5+
The `useSubscription` function takes an initial value and returns an object with a reactive value of the initial value passed in, and a subscriber can be added to be executed when the value is changed.
6+
7+
---
8+
9+
## Installation and Import
10+
11+
To use this package, you can install it via npm:
12+
13+
```sh
14+
// In your console
15+
npm install @vue-subscription
16+
```
17+
18+
```typescript
19+
// In your file
20+
import { useSubscription } from '@vue-subscription';
21+
const $mySubscription = useSubscription('hello'); // Type will be string
22+
```
23+
24+
---
25+
26+
## API
27+
28+
### $value / $get()
29+
30+
This property/method returns the current value of the subscription.
31+
32+
```typescript
33+
const value = $mySubscription.$value;
34+
const value = $mySubscription.$get();
35+
```
36+
37+
### $value = val / $set(val)
38+
39+
This property/method sets the current value of the subscription.
40+
41+
```typescript
42+
$mySubscription.$value = 42;
43+
$mySubscription.$set(42);
44+
```
45+
46+
The $set method can also accept a mutator function that takes the current value as an argument and returns the new value:
47+
48+
```typescript
49+
$mySubscription.$set(value => value + 1);
50+
```
51+
52+
### $read
53+
54+
This is a read-only version of the subscription value. It wraps the subscription in a readonly ref.
55+
56+
```typescript
57+
const readonlySubscription = $mySubscription.$read;
58+
console.log(readonlySubscription.value);
59+
```
60+
61+
### $addSub
62+
63+
This method adds a subscriber to the subscription. A subscriber is a function that takes the new value as an argument and is executed whenever the value changes. The subscriber can be `async`
64+
65+
```typescript
66+
function logValue(value) {
67+
console.log(`New value: ${value}`);
68+
}
69+
70+
$mySubscription.$addSub(logValue);
71+
$mySubscription.$deleteSub(subscriber);
72+
```
73+
74+
### $deleteSub
75+
76+
This method removes a subscriber from the subscription.
77+
78+
```typescript
79+
subscription.$deleteSub(logValue);
80+
```
81+
82+
### $triggerSubs
83+
84+
This method manually triggers all subscribers to the subscription.
85+
86+
```typescript
87+
subscription.$triggerSubs();
88+
```
89+
90+
### $mutate
91+
92+
This method mutates the subscription value with a mutator function. The mutator function takes the current value as an argument and returns the new value.
93+
94+
```typescript
95+
subscription.$mutate(value => {
96+
value.name = 'John';
97+
return value;
98+
});
99+
```
100+
101+
---
102+
103+
## Usage
104+
105+
### Basic Example
106+
107+
```typescript
108+
const $mySubscription = useSubscription('hello'); // Type will be string
109+
110+
// Get the current value
111+
console.log($mySubscription.$value); // 'hello'
112+
113+
// Subscribers can `async`
114+
async function mySubscriber(value: string) {
115+
return new Promise((resolve, reject) => {
116+
setTimeout(() => {
117+
console.log(`The value is now: ${value}`);
118+
}, 1);
119+
});
120+
}
121+
122+
// Add a subscriber
123+
$mySubscription.$addSub(mySubscriber);
124+
// Manually trigger the subscribers if needed(rarely)
125+
$mySubscription.$triggerSubs(); // 'The value is now: hello'
126+
127+
// Set the value
128+
$mySubscription.$value = 'world';
129+
130+
// Subscriber runs here - 'The value is now: world'
131+
132+
// Remove a subscriber (can be used in Unmount, beforeRouteLeave etc)
133+
$mySubscription.$deleteSub(mySubscriber);
134+
135+
// Use the readonly version of the value
136+
const myReadonlyValue = $mySubscription.$read;
137+
console.log(myReadonlyValue.value); // 'world'
138+
```
139+
140+
### Complex state
141+
142+
Example uses a complex objects which won't be tracked deeply by default. Unless the subscriber is used in templates, watch, watchEffect and computed you don't need to add the deep flag.
143+
144+
```typescript
145+
const $mySubscription = useSubscription(
146+
{
147+
user: {
148+
name: 'John',
149+
isActive: false
150+
}
151+
},
152+
// You can pass `true` as the deep flag to make the subscription deeply reactive if used in templates
153+
true
154+
);
155+
// Add a subscriber
156+
$mySubscription.$addSub(data => {
157+
console.log(`The data is now: ${JSON.stringify(data)}`);
158+
});
159+
160+
function myMutator(data: typeof $mySubscription.$value) {
161+
data.user.isActive = true;
162+
return data;
163+
}
164+
165+
// Trigger the subscribers
166+
$mySubscription.$triggerSubs(); // 'The data is now: { user: { name: 'John', isActive: false }}'
167+
168+
function tester() {
169+
// Mutate the value (only works if the value is an object)
170+
$mySubscription.$mutate(myMutator);
171+
// Subscriber runs here - 'The data is now: { user: { name: 'John', isActive: true }}'
172+
}
173+
tester();
174+
```
175+
176+
### Destructured
177+
178+
You can also destructure the properties to have a seperate getter and setter.
179+
180+
```typescript
181+
const { $get, $set, $read, $addSub } = useSubscription('hello');
182+
183+
// Get the current value
184+
console.log($get()); // 'hello'
185+
186+
function mySubscriber(value: string) {
187+
console.log(`The value is now: ${value}`);
188+
}
189+
190+
// Add a subscriber
191+
$addSub(mySubscriber);
192+
193+
// Set the value
194+
$set('world');
195+
196+
// Subscriber runs here - 'The value is now: world'
197+
198+
$set(val => `Hello ${val}`);
199+
// Subscriber runs here - 'The value is now: Hello world'
200+
201+
// Use the readonly version of the value
202+
console.log($read.value); // 'Hello world'
203+
```
204+
205+
---
206+
207+
## Type definition
208+
209+
### Function Signature
210+
211+
```typescript
212+
function useSubscription<T>(
213+
value: T,
214+
deep?: boolean
215+
): {
216+
$value: T;
217+
$get: () => T;
218+
$set: (value: T | ((value: T) => T)) => void;
219+
$read: Readonly<Ref<T>>;
220+
$addSub: (subscriber: (value: T) => Promise<void> | void) => void;
221+
$deleteSub: (subscriber: (value: T) => Promise<void> | void) => void;
222+
$triggerSubs: () => void;
223+
$mutate: (mutator: (value: T) => T) => void;
224+
};
225+
```
226+
227+
### Arguments
228+
229+
value - The initial value of the subscription.
230+
deep (optional) - Whether to create a shallow or deep reactive subscription. Defaults to false.
231+
Return Value
232+
An object with the following properties:
233+
234+
- $value - The current value of the subscription.
235+
- $get() - A function that returns the current value of the subscription.
236+
- $set(value: T | ((value: T) => T)) - A function that sets the value of the subscription. If a function is passed, it will receive the current value of the subscription as its argument and should return the new value.
237+
- $read - A readonly reactive reference to the current value of the subscription.
238+
- $addSub(subscriber: (value: T) => Promise<void> | void)) - A method for adding a subscriber to the subscription. It can be `async`. The subscriber is a function that will be executed whenever the value of the subscription changes. It can take the new value of the subscription as its argument.
239+
- $deleteSub(subscriber: (value: T) => Promise<void> | void)) - A method for removing a subscriber from the subscription.
240+
- $triggerSubs() - A method for manually triggering all subscribers. This should rarely be necessary.
241+
- $mutate(mutator: (value: T) => T) - A method for updating the value of the subscription with a function that takes the current value as its argument and returns the new value. This should only be used for updating complex objects.

commitlint.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
extends: ['@commitlint/config-conventional']
3+
};

demo/App.vue

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script setup lang="ts">
2+
import { ref } from 'vue';
3+
import ChildComp from './childComp.vue';
4+
import { useEventBus } from './composables/eventBus';
5+
6+
const testValue = ref(0);
7+
const eventBus = useEventBus();
8+
eventBus.$on((value) => {
9+
testValue.value = value;
10+
// ^?
11+
});
12+
</script>
13+
14+
<template>
15+
<div style="margin: 10rem">
16+
<h1>{{ testValue }}</h1>
17+
18+
<hr style="margin-top: 5rem; margin-bottom: 5rem" />
19+
<h1>{{ eventBus.$state.value }}</h1>
20+
<ChildComp />
21+
</div>
22+
</template>

demo/ChildComp.vue

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script setup lang="ts">
2+
import { useEventBus } from './composables/eventBus';
3+
4+
const eventBus = useEventBus();
5+
function incrementer() {
6+
eventBus.$emit(eventBus.$state.value + 1);
7+
}
8+
</script>
9+
10+
<template>
11+
<button style="margin: 5rem; padding: 2rem; border-radius: 50%" @click="incrementer()">Increase Count</button>
12+
</template>

demo/composables/eventBus.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { useSubscription } from '../../src/subscription';
2+
3+
const myEventBus = useSubscription(0);
4+
5+
export function useEventBus() {
6+
return {
7+
$on: myEventBus.$addSub,
8+
$off: myEventBus.$deleteSub,
9+
$emit: myEventBus.$set,
10+
$state: myEventBus.$read
11+
};
12+
}

demo/main.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { createApp } from 'vue';
2+
import App from '../demo/App.vue';
3+
4+
const app = createApp(App);
5+
6+
app.mount('#app');

env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/// <reference types="vite/client" />

0 commit comments

Comments
 (0)