@siggn/react 0.0.1 → 0.1.1
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 +104 -133
- package/dist/hooks.d.ts +71 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/index.cjs.js +2 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.es.js +30 -0
- package/dist/index.es.js.map +1 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +6 -2
- package/.prettierrc +0 -16
- package/CHANGELOG.md +0 -12
- package/src/hooks.ts +0 -39
- package/src/index.ts +0 -2
- package/tests/hooks.test.tsx +0 -55
- package/tsconfig.build.json +0 -10
- package/tsconfig.examples.json +0 -14
- package/tsconfig.json +0 -14
- package/tsconfig.src.json +0 -10
- package/tsconfig.test.json +0 -16
- package/vite.config.ts +0 -49
- package/vitest.setup.ts +0 -7
package/README.md
CHANGED
|
@@ -1,202 +1,173 @@
|
|
|
1
|
-
|
|
1
|
+
[](https://www.npmjs.com/package/%40siggn%2Freact)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# @siggn/react
|
|
4
|
+
|
|
5
|
+
React package for `@siggn/core`, providing a simple and idiomatic way to integrate the message bus with your React components.
|
|
4
6
|
|
|
5
7
|
## Features
|
|
6
8
|
|
|
7
|
-
- **
|
|
8
|
-
|
|
9
|
-
- **
|
|
10
|
-
- **Simple API**: Easy to learn and use, with a clear and concise API.
|
|
9
|
+
- **Seamless Integration**: Hooks-based API that feels natural in React.
|
|
10
|
+
- **Automatic Cleanup**: Subscriptions are automatically managed throughout the component lifecycle.
|
|
11
|
+
- **Type-Safe**: Full TypeScript support, inheriting the type safety of `@siggn/core`.
|
|
11
12
|
|
|
12
13
|
## Installation
|
|
13
14
|
|
|
14
|
-
You can install the package using your favorite package manager:
|
|
15
|
-
|
|
16
15
|
```bash
|
|
17
|
-
npm install @siggn/
|
|
16
|
+
npm install @siggn/react
|
|
18
17
|
```
|
|
19
18
|
|
|
20
19
|
```bash
|
|
21
|
-
yarn add @siggn/
|
|
20
|
+
yarn add @siggn/react
|
|
22
21
|
```
|
|
23
22
|
|
|
24
23
|
```bash
|
|
25
|
-
pnpm add @siggn/
|
|
24
|
+
pnpm add @siggn/react
|
|
26
25
|
```
|
|
27
26
|
|
|
28
27
|
## Usage
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
The primary way to use `@siggn/react` is by creating a `Siggn` instance and sharing it across your application. You can do this using React Context or by exporting a singleton instance.
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
import { Siggn } from '@siggn/core';
|
|
31
|
+
### 1. Create a Siggn Instance
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
type Message =
|
|
37
|
-
| { type: 'user_created'; userId: string; name: string }
|
|
38
|
-
| { type: 'user_deleted'; userId: string };
|
|
33
|
+
It's recommended to create a single `Siggn` instance and share it throughout your app.
|
|
39
34
|
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
```typescript
|
|
36
|
+
// src/siggn.ts
|
|
37
|
+
import { Siggn } from '@siggn/react';
|
|
42
38
|
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
// Define your message types
|
|
40
|
+
export type Message =
|
|
41
|
+
| { type: 'user_login'; name: string }
|
|
42
|
+
| { type: 'user_logout' };
|
|
46
43
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
// Create and export the instance
|
|
45
|
+
export const siggn = new Siggn<Message>();
|
|
46
|
+
```
|
|
50
47
|
|
|
51
|
-
|
|
52
|
-
siggn.publish({ type: 'user_created', userId: '123', name: 'John Doe' });
|
|
53
|
-
// Output: [Analytics] New user created: John Doe (ID: 123)
|
|
48
|
+
Alternatively, you can use the `useSiggn` hook to create a `Siggn` instance that is scoped to a component and its children.
|
|
54
49
|
|
|
55
|
-
|
|
56
|
-
siggn.unsubscribe(subscriberId);
|
|
50
|
+
### 2. Subscribe to Events in a Component
|
|
57
51
|
|
|
58
|
-
|
|
59
|
-
// No output, because the subscriber was removed.
|
|
60
|
-
```
|
|
52
|
+
Use the `useSubscribe` hook to listen for messages. It automatically handles subscribing and unsubscribing.
|
|
61
53
|
|
|
62
|
-
|
|
54
|
+
```tsx
|
|
55
|
+
// src/components/Notification.tsx
|
|
56
|
+
import { useState } from 'react';
|
|
57
|
+
import { useSubscribe } from '@siggn/react';
|
|
58
|
+
import { siggn, type Message } from '../siggn';
|
|
63
59
|
|
|
64
|
-
|
|
60
|
+
function Notification() {
|
|
61
|
+
const [notification, setNotification] = useState<string | null>(null);
|
|
65
62
|
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
useSubscribe(siggn, (subscribe) => {
|
|
64
|
+
subscribe('user_login', (msg) => {
|
|
65
|
+
setNotification(`Welcome, ${msg.name}!`);
|
|
66
|
+
});
|
|
68
67
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
});
|
|
68
|
+
subscribe('user_logout', () => {
|
|
69
|
+
setNotification('You have been logged out.');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
if (!notification) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
75
76
|
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
return <div className='notification'>{notification}</div>;
|
|
78
|
+
}
|
|
78
79
|
```
|
|
79
80
|
|
|
80
|
-
###
|
|
81
|
-
|
|
82
|
-
You can use `subscribeMany` to group subscriptions for a single subscriber.
|
|
81
|
+
### 3. Publish Events
|
|
83
82
|
|
|
84
|
-
|
|
85
|
-
const auditService = siggn.make('audit-service');
|
|
83
|
+
You can publish events from anywhere in your application.
|
|
86
84
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
});
|
|
91
|
-
subscribe('user_deleted', (msg) => {
|
|
92
|
-
console.log(`[Audit] User deleted: ${msg.userId}`);
|
|
93
|
-
});
|
|
94
|
-
});
|
|
85
|
+
```tsx
|
|
86
|
+
// src/components/AuthButton.tsx
|
|
87
|
+
import { siggn } from '../siggn';
|
|
95
88
|
|
|
96
|
-
|
|
97
|
-
|
|
89
|
+
function AuthButton({ isLoggedIn }: { isLoggedIn: boolean }) {
|
|
90
|
+
const handleClick = () => {
|
|
91
|
+
if (isLoggedIn) {
|
|
92
|
+
siggn.publish({ type: 'user_logout' });
|
|
93
|
+
} else {
|
|
94
|
+
siggn.publish({ type: 'user_login', name: 'Jane Doe' });
|
|
95
|
+
}
|
|
96
|
+
};
|
|
98
97
|
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
return <button onClick={handleClick}>{isLoggedIn ? 'Log Out' : 'Log In'}</button>;
|
|
99
|
+
}
|
|
101
100
|
```
|
|
102
101
|
|
|
103
|
-
### Subscribing to
|
|
104
|
-
|
|
105
|
-
If you need to listen to every message that passes through the bus, regardless of its type, you can
|
|
106
|
-
use `subscribeAll`. This is useful for cross-cutting concerns like logging or debugging.
|
|
102
|
+
### Subscribing to All Events
|
|
107
103
|
|
|
108
|
-
|
|
109
|
-
const logger = siggn.make('logger-service');
|
|
110
|
-
|
|
111
|
-
logger.subscribeAll((msg) => {
|
|
112
|
-
console.log(`[Logger] Received event of type: ${msg.type}`);
|
|
113
|
-
});
|
|
104
|
+
If you need to listen to all messages, you can use `useSubscribeAll`. This is useful for cross-cutting concerns like logging or analytics.
|
|
114
105
|
|
|
115
|
-
|
|
116
|
-
//
|
|
106
|
+
```tsx
|
|
107
|
+
// src/components/Logger.tsx
|
|
108
|
+
import { useSubscribeAll } from '@siggn/react';
|
|
109
|
+
import { siggn } from '../siggn';
|
|
117
110
|
|
|
118
|
-
|
|
119
|
-
|
|
111
|
+
function Logger() {
|
|
112
|
+
useSubscribeAll(siggn, (msg) => {
|
|
113
|
+
console.log(`[Logger] Event of type ${msg.type} was triggered`);
|
|
114
|
+
});
|
|
120
115
|
|
|
121
|
-
//
|
|
122
|
-
|
|
116
|
+
return null; // This component does not render anything
|
|
117
|
+
}
|
|
123
118
|
```
|
|
124
119
|
|
|
125
|
-
###
|
|
120
|
+
### Using `useSiggn`
|
|
126
121
|
|
|
127
|
-
The `
|
|
128
|
-
message types of its parent. This is useful for creating specialized message buses that extend a
|
|
129
|
-
base set of events without affecting the parent bus.
|
|
122
|
+
The `useSiggn` hook creates a `Siggn` instance that is tied to the component's lifecycle. This can be useful for local, component-specific event buses.
|
|
130
123
|
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
const baseSiggn = new Siggn<Message>();
|
|
124
|
+
```tsx
|
|
125
|
+
import { useSiggn, useSubscribe } from '@siggn/react';
|
|
134
126
|
|
|
135
|
-
|
|
136
|
-
type AdminMessage = { type: 'admin_login'; adminId: string };
|
|
127
|
+
type LocalMessage = { type: 'local_event' };
|
|
137
128
|
|
|
138
|
-
|
|
139
|
-
const
|
|
129
|
+
function LocalComponent() {
|
|
130
|
+
const localSiggn = useSiggn<LocalMessage>();
|
|
140
131
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
adminSiggn.subscribe('auth-service', 'admin_login', (msg) => {
|
|
147
|
-
console.log(`[Admin Auth] Admin logged in: ${msg.adminId}`);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
// 4. Publish events on the child bus
|
|
151
|
-
adminSiggn.publish({ type: 'user_created', userId: 'abc', name: 'Alice' });
|
|
152
|
-
// Output: [Admin Audit] User created: Alice
|
|
132
|
+
useSubscribe(localSiggn, (subscribe) => {
|
|
133
|
+
subscribe('local_event', () => {
|
|
134
|
+
console.log('Local event received!');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
153
137
|
|
|
154
|
-
|
|
155
|
-
|
|
138
|
+
const triggerEvent = () => {
|
|
139
|
+
localSiggn.publish({ type: 'local_event' });
|
|
140
|
+
};
|
|
156
141
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
baseSiggn.publish({ type: 'user_created', userId: 'def', name: 'Bob' });
|
|
160
|
-
// No output, because the subscription is on `adminSiggn`.
|
|
142
|
+
return <button onClick={triggerEvent}>Trigger Local Event</button>;
|
|
143
|
+
}
|
|
161
144
|
```
|
|
162
145
|
|
|
163
146
|
## API
|
|
164
147
|
|
|
165
|
-
### `
|
|
166
|
-
|
|
167
|
-
Creates a new message bus instance. `T` is a union type of all possible messages.
|
|
168
|
-
|
|
169
|
-
### `publish(msg)`
|
|
170
|
-
|
|
171
|
-
Publishes a message to all relevant subscribers.
|
|
172
|
-
|
|
173
|
-
### `subscribe(id, type, callback)`
|
|
174
|
-
|
|
175
|
-
Subscribes a callback to a specific message type with a unique subscriber ID.
|
|
176
|
-
|
|
177
|
-
### `subscribeAll(id, callback)`
|
|
148
|
+
### `useSiggn<T>()`
|
|
178
149
|
|
|
179
|
-
|
|
180
|
-
every message published on the bus.
|
|
150
|
+
Creates and returns a `Siggn` instance that persists for the lifetime of the component.
|
|
181
151
|
|
|
182
|
-
|
|
152
|
+
- `T`: A union type of all possible messages.
|
|
183
153
|
|
|
184
|
-
|
|
154
|
+
Returns a `Siggn<T>` instance.
|
|
185
155
|
|
|
186
|
-
### `
|
|
156
|
+
### `useSubscribe(options, setup, deps)`
|
|
187
157
|
|
|
188
|
-
|
|
189
|
-
pre-bound to the provided ID. This is useful for encapsulating subscription logic within a component
|
|
190
|
-
or service.
|
|
158
|
+
Subscribes to messages and automatically unsubscribes when the component unmounts.
|
|
191
159
|
|
|
192
|
-
|
|
160
|
+
- `options`: A `Siggn` instance or an object `{ instance: Siggn<T>; id?: string; }`.
|
|
161
|
+
- `setup`: A function that receives a `subscribe` helper to define subscriptions, similar to `subscribeMany` in `@siggn/core`.
|
|
162
|
+
- `deps` (optional): A dependency array to control when the subscriptions are re-created.
|
|
193
163
|
|
|
194
|
-
|
|
164
|
+
### `useSubscribeAll(options, callback, deps)`
|
|
195
165
|
|
|
196
|
-
|
|
166
|
+
Subscribes to all messages and automatically unsubscribes when the component unmounts.
|
|
197
167
|
|
|
198
|
-
|
|
199
|
-
|
|
168
|
+
- `options`: A `Siggn` instance or an object `{ instance: Siggn<T>; id?: string; }`.
|
|
169
|
+
- `callback`: A function that will be called with every message.
|
|
170
|
+
- `deps` (optional): A dependency array to control when the subscriptions are re-created.
|
|
200
171
|
|
|
201
172
|
## License
|
|
202
173
|
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Msg, Siggn } from '../../core/src/index.ts';
|
|
2
|
+
import { SubscriptionOptions } from 'packages/react/src/types';
|
|
3
|
+
import { DependencyList } from 'react';
|
|
4
|
+
/**
|
|
5
|
+
* Creates and returns a `Siggn` instance that persists for the lifetime of the component.
|
|
6
|
+
* This is useful for creating a message bus scoped to a component and its children.
|
|
7
|
+
*
|
|
8
|
+
* @template T A union of all possible message types for the new instance.
|
|
9
|
+
* @returns A `Siggn<T>` instance.
|
|
10
|
+
* @category Lifecycle
|
|
11
|
+
* @since 0.0.1
|
|
12
|
+
* @example
|
|
13
|
+
*
|
|
14
|
+
```tsx
|
|
15
|
+
* function MyComponent() {
|
|
16
|
+
* const localSiggn = useSiggn<{ type: 'local-event' }>();
|
|
17
|
+
* // ...
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function useSiggn<T extends Msg>(): Siggn<T>;
|
|
22
|
+
/**
|
|
23
|
+
* Subscribes to messages and automatically unsubscribes when the component unmounts.
|
|
24
|
+
*
|
|
25
|
+
* @template T A union of all possible message types.
|
|
26
|
+
* @param options A `Siggn` instance or an object with the instance and an optional subscriber ID.
|
|
27
|
+
* @param setup A function that receives a `subscribe` helper to define subscriptions.
|
|
28
|
+
* @param deps An optional dependency array to control when the subscriptions are re-created.
|
|
29
|
+
* @category Subscription
|
|
30
|
+
* @since 0.0.1
|
|
31
|
+
* @example
|
|
32
|
+
*
|
|
33
|
+
```tsx
|
|
34
|
+
* import { siggn } from './siggn'; // Your shared instance
|
|
35
|
+
*
|
|
36
|
+
* function MyComponent() {
|
|
37
|
+
* useSubscribe(siggn, (subscribe) => {
|
|
38
|
+
* subscribe('user-created', (msg) => console.log(msg.name));
|
|
39
|
+
* });
|
|
40
|
+
* // ...
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare function useSubscribe<T extends Msg>(options: SubscriptionOptions<T>, setup: (subscribe: <K extends T['type']>(type: K, callback: (msg: Extract<T, {
|
|
45
|
+
type: K;
|
|
46
|
+
}>) => void) => void) => void, deps?: DependencyList): void;
|
|
47
|
+
/**
|
|
48
|
+
* Subscribes to all messages on a `Siggn` instance and automatically unsubscribes
|
|
49
|
+
* when the component unmounts.
|
|
50
|
+
*
|
|
51
|
+
* @template T A union of all possible message types.
|
|
52
|
+
* @param options A `Siggn` instance or an object with the instance and an optional subscriber ID.
|
|
53
|
+
* @param callback The function to call for any message.
|
|
54
|
+
* @param deps An optional dependency array to control when the subscription is re-created.
|
|
55
|
+
* @category Subscription
|
|
56
|
+
* @since 0.0.1
|
|
57
|
+
* @example
|
|
58
|
+
*
|
|
59
|
+
```tsx
|
|
60
|
+
* import { siggn } from './siggn';
|
|
61
|
+
*
|
|
62
|
+
* function LoggerComponent() {
|
|
63
|
+
* useSubscribeAll(siggn, (msg) => {
|
|
64
|
+
* console.log(`[LOG]: ${msg.type}`);
|
|
65
|
+
* }, []);
|
|
66
|
+
* // ...
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function useSubscribeAll<T extends Msg>(options: SubscriptionOptions<T>, callback: (msg: T) => void, deps?: DependencyList): void;
|
|
71
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAA8B,KAAK,cAAc,EAAE,MAAM,OAAO,CAAC;AAExE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAGlD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,GAAG,EACxC,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,EAC/B,KAAK,EAAE,CACL,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EAC7B,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,CAAC,KAAK,IAAI,KAC7C,IAAI,KACN,IAAI,EACT,IAAI,GAAE,cAAmB,QAe1B;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,GAAG,EAC3C,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,EAC/B,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EAC1B,IAAI,GAAE,cAAmB,QAe1B"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("@siggn/core"),c=require("react");function t(){return c.useRef(new r.Siggn).current}function b(e,s,i=[]){const u=c.useMemo(()=>e instanceof r.Siggn?e:e.instance,[e]),n=c.useMemo(()=>u.makeId("id"in e?e.id:void 0),[u]);c.useEffect(()=>(u.subscribeMany(n,s),()=>{u.unsubscribe(n)}),[u,n,...i])}function a(e,s,i=[]){const u=c.useMemo(()=>e instanceof r.Siggn?e:e.instance,[e]),n=c.useMemo(()=>u.makeId("id"in e?e.id:void 0),[u]);c.useEffect(()=>(u.subscribeAll(n,s),()=>{u.unsubscribeGlobal(n)}),[u,n,...i])}exports.useSiggn=t;exports.useSubscribe=b;exports.useSubscribeAll=a;Object.keys(r).forEach(e=>{e!=="default"&&!Object.prototype.hasOwnProperty.call(exports,e)&&Object.defineProperty(exports,e,{enumerable:!0,get:()=>r[e]})});
|
|
2
|
+
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../src/hooks.ts"],"sourcesContent":["import { type Msg, Siggn } from '@siggn/core';\nimport type { SubscriptionOptions } from 'packages/react/src/types';\nimport { useEffect, useMemo, useRef, type DependencyList } from 'react';\n\n/**\n * Creates and returns a `Siggn` instance that persists for the lifetime of the component.\n * This is useful for creating a message bus scoped to a component and its children.\n *\n * @template T A union of all possible message types for the new instance.\n * @returns A `Siggn<T>` instance.\n * @category Lifecycle\n * @since 0.0.1\n * @example\n * \n```tsx\n * function MyComponent() {\n * const localSiggn = useSiggn<{ type: 'local-event' }>();\n * // ...\n * }\n * ```\n */\nexport function useSiggn<T extends Msg>(): Siggn<T> {\n const siggn = useRef(new Siggn<T>());\n return siggn.current;\n}\n\n/**\n * Subscribes to messages and automatically unsubscribes when the component unmounts.\n *\n * @template T A union of all possible message types.\n * @param options A `Siggn` instance or an object with the instance and an optional subscriber ID.\n * @param setup A function that receives a `subscribe` helper to define subscriptions.\n * @param deps An optional dependency array to control when the subscriptions are re-created.\n * @category Subscription\n * @since 0.0.1\n * @example\n * \n```tsx\n * import { siggn } from './siggn'; // Your shared instance\n *\n * function MyComponent() {\n * useSubscribe(siggn, (subscribe) => {\n * subscribe('user-created', (msg) => console.log(msg.name));\n * });\n * // ...\n * }\n * ```\n */\nexport function useSubscribe<T extends Msg>(\n options: SubscriptionOptions<T>,\n setup: (\n subscribe: <K extends T['type']>(\n type: K,\n callback: (msg: Extract<T, { type: K }>) => void,\n ) => void,\n ) => void,\n deps: DependencyList = [],\n) {\n const instance = useMemo(\n () => (options instanceof Siggn ? options : options.instance),\n [options],\n );\n const id = useMemo(() => instance.makeId('id' in options ? options.id : undefined), [instance]);\n\n useEffect(() => {\n instance.subscribeMany(id, setup);\n\n return () => {\n instance.unsubscribe(id);\n };\n }, [instance, id, ...deps]);\n}\n\n/**\n * Subscribes to all messages on a `Siggn` instance and automatically unsubscribes\n * when the component unmounts.\n *\n * @template T A union of all possible message types.\n * @param options A `Siggn` instance or an object with the instance and an optional subscriber ID.\n * @param callback The function to call for any message.\n * @param deps An optional dependency array to control when the subscription is re-created.\n * @category Subscription\n * @since 0.0.1\n * @example\n * \n```tsx\n * import { siggn } from './siggn';\n *\n * function LoggerComponent() {\n * useSubscribeAll(siggn, (msg) => {\n * console.log(`[LOG]: ${msg.type}`);\n * }, []);\n * // ...\n * }\n * ```\n */\nexport function useSubscribeAll<T extends Msg>(\n options: SubscriptionOptions<T>,\n callback: (msg: T) => void,\n deps: DependencyList = [],\n) {\n const instance = useMemo(\n () => (options instanceof Siggn ? options : options.instance),\n [options],\n );\n const id = useMemo(() => instance.makeId('id' in options ? options.id : undefined), [instance]);\n\n useEffect(() => {\n instance.subscribeAll(id, callback);\n\n return () => {\n instance.unsubscribeGlobal(id);\n };\n }, [instance, id, ...deps]);\n}\n"],"names":["useSiggn","useRef","Siggn","useSubscribe","options","setup","deps","instance","useMemo","id","useEffect","useSubscribeAll","callback"],"mappings":"kIAqBO,SAASA,GAAoC,CAElD,OADcC,EAAAA,OAAO,IAAIC,EAAAA,KAAU,EACtB,OACf,CAwBO,SAASC,EACdC,EACAC,EAMAC,EAAuB,CAAA,EACvB,CACA,MAAMC,EAAWC,EAAAA,QACf,IAAOJ,aAAmBF,EAAAA,MAAQE,EAAUA,EAAQ,SACpD,CAACA,CAAO,CAAA,EAEJK,EAAKD,EAAAA,QAAQ,IAAMD,EAAS,OAAO,OAAQH,EAAUA,EAAQ,GAAK,MAAS,EAAG,CAACG,CAAQ,CAAC,EAE9FG,EAAAA,UAAU,KACRH,EAAS,cAAcE,EAAIJ,CAAK,EAEzB,IAAM,CACXE,EAAS,YAAYE,CAAE,CACzB,GACC,CAACF,EAAUE,EAAI,GAAGH,CAAI,CAAC,CAC5B,CAyBO,SAASK,EACdP,EACAQ,EACAN,EAAuB,CAAA,EACvB,CACA,MAAMC,EAAWC,EAAAA,QACf,IAAOJ,aAAmBF,EAAAA,MAAQE,EAAUA,EAAQ,SACpD,CAACA,CAAO,CAAA,EAEJK,EAAKD,EAAAA,QAAQ,IAAMD,EAAS,OAAO,OAAQH,EAAUA,EAAQ,GAAK,MAAS,EAAG,CAACG,CAAQ,CAAC,EAE9FG,EAAAA,UAAU,KACRH,EAAS,aAAaE,EAAIG,CAAQ,EAE3B,IAAM,CACXL,EAAS,kBAAkBE,CAAE,CAC/B,GACC,CAACF,EAAUE,EAAI,GAAGH,CAAI,CAAC,CAC5B"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC"}
|
package/dist/index.es.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Siggn as s } from "@siggn/core";
|
|
2
|
+
export * from "@siggn/core";
|
|
3
|
+
import { useRef as t, useMemo as i, useEffect as b } from "react";
|
|
4
|
+
function d() {
|
|
5
|
+
return t(new s()).current;
|
|
6
|
+
}
|
|
7
|
+
function g(e, r, u = []) {
|
|
8
|
+
const n = i(
|
|
9
|
+
() => e instanceof s ? e : e.instance,
|
|
10
|
+
[e]
|
|
11
|
+
), c = i(() => n.makeId("id" in e ? e.id : void 0), [n]);
|
|
12
|
+
b(() => (n.subscribeMany(c, r), () => {
|
|
13
|
+
n.unsubscribe(c);
|
|
14
|
+
}), [n, c, ...u]);
|
|
15
|
+
}
|
|
16
|
+
function m(e, r, u = []) {
|
|
17
|
+
const n = i(
|
|
18
|
+
() => e instanceof s ? e : e.instance,
|
|
19
|
+
[e]
|
|
20
|
+
), c = i(() => n.makeId("id" in e ? e.id : void 0), [n]);
|
|
21
|
+
b(() => (n.subscribeAll(c, r), () => {
|
|
22
|
+
n.unsubscribeGlobal(c);
|
|
23
|
+
}), [n, c, ...u]);
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
d as useSiggn,
|
|
27
|
+
g as useSubscribe,
|
|
28
|
+
m as useSubscribeAll
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=index.es.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.es.js","sources":["../src/hooks.ts"],"sourcesContent":["import { type Msg, Siggn } from '@siggn/core';\nimport type { SubscriptionOptions } from 'packages/react/src/types';\nimport { useEffect, useMemo, useRef, type DependencyList } from 'react';\n\n/**\n * Creates and returns a `Siggn` instance that persists for the lifetime of the component.\n * This is useful for creating a message bus scoped to a component and its children.\n *\n * @template T A union of all possible message types for the new instance.\n * @returns A `Siggn<T>` instance.\n * @category Lifecycle\n * @since 0.0.1\n * @example\n * \n```tsx\n * function MyComponent() {\n * const localSiggn = useSiggn<{ type: 'local-event' }>();\n * // ...\n * }\n * ```\n */\nexport function useSiggn<T extends Msg>(): Siggn<T> {\n const siggn = useRef(new Siggn<T>());\n return siggn.current;\n}\n\n/**\n * Subscribes to messages and automatically unsubscribes when the component unmounts.\n *\n * @template T A union of all possible message types.\n * @param options A `Siggn` instance or an object with the instance and an optional subscriber ID.\n * @param setup A function that receives a `subscribe` helper to define subscriptions.\n * @param deps An optional dependency array to control when the subscriptions are re-created.\n * @category Subscription\n * @since 0.0.1\n * @example\n * \n```tsx\n * import { siggn } from './siggn'; // Your shared instance\n *\n * function MyComponent() {\n * useSubscribe(siggn, (subscribe) => {\n * subscribe('user-created', (msg) => console.log(msg.name));\n * });\n * // ...\n * }\n * ```\n */\nexport function useSubscribe<T extends Msg>(\n options: SubscriptionOptions<T>,\n setup: (\n subscribe: <K extends T['type']>(\n type: K,\n callback: (msg: Extract<T, { type: K }>) => void,\n ) => void,\n ) => void,\n deps: DependencyList = [],\n) {\n const instance = useMemo(\n () => (options instanceof Siggn ? options : options.instance),\n [options],\n );\n const id = useMemo(() => instance.makeId('id' in options ? options.id : undefined), [instance]);\n\n useEffect(() => {\n instance.subscribeMany(id, setup);\n\n return () => {\n instance.unsubscribe(id);\n };\n }, [instance, id, ...deps]);\n}\n\n/**\n * Subscribes to all messages on a `Siggn` instance and automatically unsubscribes\n * when the component unmounts.\n *\n * @template T A union of all possible message types.\n * @param options A `Siggn` instance or an object with the instance and an optional subscriber ID.\n * @param callback The function to call for any message.\n * @param deps An optional dependency array to control when the subscription is re-created.\n * @category Subscription\n * @since 0.0.1\n * @example\n * \n```tsx\n * import { siggn } from './siggn';\n *\n * function LoggerComponent() {\n * useSubscribeAll(siggn, (msg) => {\n * console.log(`[LOG]: ${msg.type}`);\n * }, []);\n * // ...\n * }\n * ```\n */\nexport function useSubscribeAll<T extends Msg>(\n options: SubscriptionOptions<T>,\n callback: (msg: T) => void,\n deps: DependencyList = [],\n) {\n const instance = useMemo(\n () => (options instanceof Siggn ? options : options.instance),\n [options],\n );\n const id = useMemo(() => instance.makeId('id' in options ? options.id : undefined), [instance]);\n\n useEffect(() => {\n instance.subscribeAll(id, callback);\n\n return () => {\n instance.unsubscribeGlobal(id);\n };\n }, [instance, id, ...deps]);\n}\n"],"names":["useSiggn","useRef","Siggn","useSubscribe","options","setup","deps","instance","useMemo","id","useEffect","useSubscribeAll","callback"],"mappings":";;;AAqBO,SAASA,IAAoC;AAElD,SADcC,EAAO,IAAIC,GAAU,EACtB;AACf;AAwBO,SAASC,EACdC,GACAC,GAMAC,IAAuB,CAAA,GACvB;AACA,QAAMC,IAAWC;AAAA,IACf,MAAOJ,aAAmBF,IAAQE,IAAUA,EAAQ;AAAA,IACpD,CAACA,CAAO;AAAA,EAAA,GAEJK,IAAKD,EAAQ,MAAMD,EAAS,OAAO,QAAQH,IAAUA,EAAQ,KAAK,MAAS,GAAG,CAACG,CAAQ,CAAC;AAE9F,EAAAG,EAAU,OACRH,EAAS,cAAcE,GAAIJ,CAAK,GAEzB,MAAM;AACX,IAAAE,EAAS,YAAYE,CAAE;AAAA,EACzB,IACC,CAACF,GAAUE,GAAI,GAAGH,CAAI,CAAC;AAC5B;AAyBO,SAASK,EACdP,GACAQ,GACAN,IAAuB,CAAA,GACvB;AACA,QAAMC,IAAWC;AAAA,IACf,MAAOJ,aAAmBF,IAAQE,IAAUA,EAAQ;AAAA,IACpD,CAACA,CAAO;AAAA,EAAA,GAEJK,IAAKD,EAAQ,MAAMD,EAAS,OAAO,QAAQH,IAAUA,EAAQ,KAAK,MAAS,GAAG,CAACG,CAAQ,CAAC;AAE9F,EAAAG,EAAU,OACRH,EAAS,aAAaE,GAAIG,CAAQ,GAE3B,MAAM;AACX,IAAAL,EAAS,kBAAkBE,CAAE;AAAA,EAC/B,IACC,CAACF,GAAUE,GAAI,GAAGH,CAAI,CAAC;AAC5B;"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,GAAG,IACzC,KAAK,CAAC,CAAC,CAAC,GACR;IACE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;CACb,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@siggn/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "A lightweight type safe message bus system for React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pub/sub",
|
|
@@ -36,6 +36,9 @@
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"type": "module",
|
|
39
|
+
"files": [
|
|
40
|
+
"dist"
|
|
41
|
+
],
|
|
39
42
|
"sideEffects": false,
|
|
40
43
|
"devDependencies": {
|
|
41
44
|
"typescript": "^5.9.3",
|
|
@@ -55,13 +58,14 @@
|
|
|
55
58
|
"react": ">=18 <20"
|
|
56
59
|
},
|
|
57
60
|
"dependencies": {
|
|
58
|
-
"@siggn/core": "0.0
|
|
61
|
+
"@siggn/core": "0.1.0"
|
|
59
62
|
},
|
|
60
63
|
"scripts": {
|
|
61
64
|
"dev": "vite build --watch",
|
|
62
65
|
"clean": "rm -rf dist",
|
|
63
66
|
"test:watch": "vitest",
|
|
64
67
|
"test": "vitest run",
|
|
68
|
+
"coverage": "vitest run --coverage",
|
|
65
69
|
"build": "vite build"
|
|
66
70
|
}
|
|
67
71
|
}
|
package/.prettierrc
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"arrowParens": "always",
|
|
3
|
-
"bracketSpacing": true,
|
|
4
|
-
"htmlWhitespaceSensitivity": "css",
|
|
5
|
-
"insertPragma": false,
|
|
6
|
-
"jsxSingleQuote": true,
|
|
7
|
-
"printWidth": 100,
|
|
8
|
-
"proseWrap": "always",
|
|
9
|
-
"quoteProps": "as-needed",
|
|
10
|
-
"requirePragma": false,
|
|
11
|
-
"semi": true,
|
|
12
|
-
"singleQuote": true,
|
|
13
|
-
"tabWidth": 2,
|
|
14
|
-
"trailingComma": "all",
|
|
15
|
-
"useTabs": false
|
|
16
|
-
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# @siggn/react
|
|
2
|
-
|
|
3
|
-
## 0.0.1
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- [`d2a88af`](https://github.com/Guiguerreiro39/siggn/commit/d2a88af8e484f28e436c594cd3238379295941a2)
|
|
8
|
-
Thanks [@Guiguerreiro39](https://github.com/Guiguerreiro39)! - Added react hooks support
|
|
9
|
-
|
|
10
|
-
- Updated dependencies
|
|
11
|
-
[[`d2a88af`](https://github.com/Guiguerreiro39/siggn/commit/d2a88af8e484f28e436c594cd3238379295941a2)]:
|
|
12
|
-
- @siggn/core@0.0.5
|
package/src/hooks.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { type Msg, Siggn } from '@siggn/core';
|
|
2
|
-
import { useEffect, useMemo, useRef, type DependencyList } from 'react';
|
|
3
|
-
|
|
4
|
-
type SubscriptionOptions<T extends Msg> =
|
|
5
|
-
| Siggn<T>
|
|
6
|
-
| {
|
|
7
|
-
instance: Siggn<T>;
|
|
8
|
-
id?: string;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export function useSubscribe<T extends Msg>(
|
|
12
|
-
options: SubscriptionOptions<T>,
|
|
13
|
-
setup: (
|
|
14
|
-
subscribe: <K extends T['type']>(
|
|
15
|
-
type: K,
|
|
16
|
-
callback: (msg: Extract<T, { type: K }>) => void,
|
|
17
|
-
) => void,
|
|
18
|
-
) => void,
|
|
19
|
-
deps: DependencyList = [],
|
|
20
|
-
) {
|
|
21
|
-
const instance = useMemo(
|
|
22
|
-
() => (options instanceof Siggn ? options : options.instance),
|
|
23
|
-
[options],
|
|
24
|
-
);
|
|
25
|
-
const id = useMemo(() => instance.makeId('id' in options ? options.id : undefined), [instance]);
|
|
26
|
-
|
|
27
|
-
useEffect(() => {
|
|
28
|
-
instance.subscribeMany(id, setup);
|
|
29
|
-
|
|
30
|
-
return () => {
|
|
31
|
-
instance.unsubscribe(id);
|
|
32
|
-
};
|
|
33
|
-
}, [instance, id, ...deps]);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function useSiggn<T extends Msg>(): Siggn<T> {
|
|
37
|
-
const siggn = useRef<Siggn<T>>(new Siggn<T>());
|
|
38
|
-
return siggn.current;
|
|
39
|
-
}
|
package/src/index.ts
DELETED
package/tests/hooks.test.tsx
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { test, expect, describe, beforeEach } from 'vitest';
|
|
2
|
-
import { useSiggn, useSubscribe } from '../src/hooks.js';
|
|
3
|
-
import { Siggn } from '@siggn/core';
|
|
4
|
-
import { act, render, renderHook, screen } from '@testing-library/react';
|
|
5
|
-
import { useState } from 'react';
|
|
6
|
-
|
|
7
|
-
type Msg =
|
|
8
|
-
| {
|
|
9
|
-
type: 'increment_count';
|
|
10
|
-
value: number;
|
|
11
|
-
}
|
|
12
|
-
| { type: 'decrement_count'; value: number };
|
|
13
|
-
|
|
14
|
-
describe('@siggn/react', () => {
|
|
15
|
-
let siggn: Siggn<Msg>;
|
|
16
|
-
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
const { result } = renderHook(() => useSiggn<Msg>());
|
|
19
|
-
siggn = result.current;
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test('user should be able to create a siggn instance and subscribe using hooks', () => {
|
|
23
|
-
function TestComponent() {
|
|
24
|
-
const [count, setCount] = useState(0);
|
|
25
|
-
|
|
26
|
-
useSubscribe(siggn, (subscribe) => {
|
|
27
|
-
subscribe('increment_count', (msg) => {
|
|
28
|
-
setCount((prev) => prev + msg.value);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
subscribe('decrement_count', (msg) => {
|
|
32
|
-
setCount((prev) => prev - msg.value);
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
return <div data-testid='value'>{count}</div>;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
render(<TestComponent />);
|
|
40
|
-
|
|
41
|
-
expect(screen.getByTestId('value')).toHaveTextContent('0');
|
|
42
|
-
|
|
43
|
-
act(() => {
|
|
44
|
-
siggn.publish({ type: 'increment_count', value: 4 });
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
expect(screen.getByTestId('value')).toHaveTextContent('4');
|
|
48
|
-
|
|
49
|
-
act(() => {
|
|
50
|
-
siggn.publish({ type: 'decrement_count', value: 2 });
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
expect(screen.getByTestId('value')).toHaveTextContent('2');
|
|
54
|
-
});
|
|
55
|
-
});
|
package/tsconfig.build.json
DELETED
package/tsconfig.examples.json
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.base.json",
|
|
3
|
-
"include": ["examples"],
|
|
4
|
-
"references": [
|
|
5
|
-
{
|
|
6
|
-
"path": "tsconfig.build.json"
|
|
7
|
-
}
|
|
8
|
-
],
|
|
9
|
-
"compilerOptions": {
|
|
10
|
-
"tsBuildInfoFile": ".tsbuildinfo/examples.tsbuildinfo",
|
|
11
|
-
"rootDir": "examples",
|
|
12
|
-
"noEmit": true
|
|
13
|
-
}
|
|
14
|
-
}
|
package/tsconfig.json
DELETED
package/tsconfig.src.json
DELETED
package/tsconfig.test.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.base.json",
|
|
3
|
-
"include": ["test"],
|
|
4
|
-
"references": [
|
|
5
|
-
{
|
|
6
|
-
"path": "tsconfig.src.json"
|
|
7
|
-
}
|
|
8
|
-
],
|
|
9
|
-
"compilerOptions": {
|
|
10
|
-
"tsBuildInfoFile": ".tsbuildinfo/test.tsbuildinfo",
|
|
11
|
-
"rootDir": "test",
|
|
12
|
-
"noEmit": true,
|
|
13
|
-
"jsx": "react-jsx",
|
|
14
|
-
"types": ["@testing-library/jest-dom", "vitest/globals"]
|
|
15
|
-
}
|
|
16
|
-
}
|
package/vite.config.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'vite';
|
|
2
|
-
import react from '@vitejs/plugin-react';
|
|
3
|
-
import dts from 'vite-plugin-dts';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
|
|
6
|
-
export default defineConfig({
|
|
7
|
-
resolve: {
|
|
8
|
-
alias: {
|
|
9
|
-
'@siggn/core': path.resolve(__dirname, '../core/src/index.ts'),
|
|
10
|
-
},
|
|
11
|
-
},
|
|
12
|
-
build: {
|
|
13
|
-
lib: {
|
|
14
|
-
entry: 'src/index.ts',
|
|
15
|
-
name: 'SiggnReact',
|
|
16
|
-
formats: ['es', 'cjs'],
|
|
17
|
-
fileName: (format) => `index.${format}.js`,
|
|
18
|
-
},
|
|
19
|
-
sourcemap: true,
|
|
20
|
-
outDir: 'dist',
|
|
21
|
-
emptyOutDir: true,
|
|
22
|
-
rollupOptions: {
|
|
23
|
-
// Prevent bundling peer dependencies like React, etc.
|
|
24
|
-
external: ['react', 'react-dom', '@siggn/core'],
|
|
25
|
-
output: {
|
|
26
|
-
globals: {
|
|
27
|
-
react: 'React',
|
|
28
|
-
'react-dom': 'ReactDOM',
|
|
29
|
-
'@siggn/core': 'SiggnCore',
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
test: {
|
|
35
|
-
environment: 'jsdom',
|
|
36
|
-
globals: true,
|
|
37
|
-
setupFiles: './vitest.setup.ts',
|
|
38
|
-
},
|
|
39
|
-
plugins: [
|
|
40
|
-
react(),
|
|
41
|
-
dts({
|
|
42
|
-
insertTypesEntry: true,
|
|
43
|
-
tsconfigPath: 'tsconfig.build.json',
|
|
44
|
-
outDir: 'dist',
|
|
45
|
-
include: ['src'],
|
|
46
|
-
exclude: ['src/**/*.test.ts', 'tests/**'],
|
|
47
|
-
}),
|
|
48
|
-
],
|
|
49
|
-
});
|