@siggn/react 0.1.0 → 0.1.2
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/{src/hooks.ts → dist/hooks.d.ts} +12 -56
- 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 +281 -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 +4 -1
- package/.prettierrc +0 -16
- package/CHANGELOG.md +0 -28
- package/src/index.ts +0 -2
- package/src/types.ts +0 -8
- package/tests/hooks.test.tsx +0 -270
- 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
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
1
|
+
import { Msg, Siggn } from '../../core/src/index.ts';
|
|
2
|
+
import { SubscriptionOptions } from 'packages/react/src/types';
|
|
3
|
+
import { DependencyList } from 'react';
|
|
5
4
|
/**
|
|
6
5
|
* Creates and returns a `Siggn` instance that persists for the lifetime of the component.
|
|
7
6
|
* This is useful for creating a message bus scoped to a component and its children.
|
|
@@ -11,7 +10,7 @@ import { useEffect, useMemo, useRef, type DependencyList } from 'react';
|
|
|
11
10
|
* @category Lifecycle
|
|
12
11
|
* @since 0.0.1
|
|
13
12
|
* @example
|
|
14
|
-
*
|
|
13
|
+
*
|
|
15
14
|
```tsx
|
|
16
15
|
* function MyComponent() {
|
|
17
16
|
* const localSiggn = useSiggn<{ type: 'local-event' }>();
|
|
@@ -19,11 +18,7 @@ import { useEffect, useMemo, useRef, type DependencyList } from 'react';
|
|
|
19
18
|
* }
|
|
20
19
|
* ```
|
|
21
20
|
*/
|
|
22
|
-
export function useSiggn<T extends Msg>(): Siggn<T
|
|
23
|
-
const siggn = useRef(new Siggn<T>());
|
|
24
|
-
return siggn.current;
|
|
25
|
-
}
|
|
26
|
-
|
|
21
|
+
export declare function useSiggn<T extends Msg>(): Siggn<T>;
|
|
27
22
|
/**
|
|
28
23
|
* Subscribes to messages and automatically unsubscribes when the component unmounts.
|
|
29
24
|
*
|
|
@@ -34,7 +29,7 @@ export function useSiggn<T extends Msg>(): Siggn<T> {
|
|
|
34
29
|
* @category Subscription
|
|
35
30
|
* @since 0.0.1
|
|
36
31
|
* @example
|
|
37
|
-
*
|
|
32
|
+
*
|
|
38
33
|
```tsx
|
|
39
34
|
* import { siggn } from './siggn'; // Your shared instance
|
|
40
35
|
*
|
|
@@ -46,31 +41,9 @@ export function useSiggn<T extends Msg>(): Siggn<T> {
|
|
|
46
41
|
* }
|
|
47
42
|
* ```
|
|
48
43
|
*/
|
|
49
|
-
export function useSubscribe<T extends Msg>(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
subscribe: <K extends T['type']>(
|
|
53
|
-
type: K,
|
|
54
|
-
callback: (msg: Extract<T, { type: K }>) => void,
|
|
55
|
-
) => void,
|
|
56
|
-
) => void,
|
|
57
|
-
deps: DependencyList = [],
|
|
58
|
-
) {
|
|
59
|
-
const instance = useMemo(
|
|
60
|
-
() => (options instanceof Siggn ? options : options.instance),
|
|
61
|
-
[options],
|
|
62
|
-
);
|
|
63
|
-
const id = useMemo(() => instance.makeId('id' in options ? options.id : undefined), [instance]);
|
|
64
|
-
|
|
65
|
-
useEffect(() => {
|
|
66
|
-
instance.subscribeMany(id, setup);
|
|
67
|
-
|
|
68
|
-
return () => {
|
|
69
|
-
instance.unsubscribe(id);
|
|
70
|
-
};
|
|
71
|
-
}, [instance, id, ...deps]);
|
|
72
|
-
}
|
|
73
|
-
|
|
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;
|
|
74
47
|
/**
|
|
75
48
|
* Subscribes to all messages on a `Siggn` instance and automatically unsubscribes
|
|
76
49
|
* when the component unmounts.
|
|
@@ -82,7 +55,7 @@ export function useSubscribe<T extends Msg>(
|
|
|
82
55
|
* @category Subscription
|
|
83
56
|
* @since 0.0.1
|
|
84
57
|
* @example
|
|
85
|
-
*
|
|
58
|
+
*
|
|
86
59
|
```tsx
|
|
87
60
|
* import { siggn } from './siggn';
|
|
88
61
|
*
|
|
@@ -94,22 +67,5 @@ export function useSubscribe<T extends Msg>(
|
|
|
94
67
|
* }
|
|
95
68
|
* ```
|
|
96
69
|
*/
|
|
97
|
-
export function useSubscribeAll<T extends Msg>(
|
|
98
|
-
|
|
99
|
-
callback: (msg: T) => void,
|
|
100
|
-
deps: DependencyList = [],
|
|
101
|
-
) {
|
|
102
|
-
const instance = useMemo(
|
|
103
|
-
() => (options instanceof Siggn ? options : options.instance),
|
|
104
|
-
[options],
|
|
105
|
-
);
|
|
106
|
-
const id = useMemo(() => instance.makeId('id' in options ? options.id : undefined), [instance]);
|
|
107
|
-
|
|
108
|
-
useEffect(() => {
|
|
109
|
-
instance.subscribeAll(id, callback);
|
|
110
|
-
|
|
111
|
-
return () => {
|
|
112
|
-
instance.unsubscribeGlobal(id);
|
|
113
|
-
};
|
|
114
|
-
}, [instance, id, ...deps]);
|
|
115
|
-
}
|
|
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 n=require("react");class u{nextId=0;subscriptions;globalSubscriptions=[];registry=new FinalizationRegistry(s=>{this.unsubscribe(s)});registryGlobal=new FinalizationRegistry(s=>{this.unsubscribeGlobal(s)});constructor(){this.subscriptions=new Map}createClone(){return new u}makeId(s){return s??`sub_${(this.nextId++).toString(36)}`}make(s){return{subscribe:(i,e)=>{this.subscribe(s,i,e)},unsubscribe:()=>{this.unsubscribe(s)},subscribeMany:i=>{this.subscribeMany(s,i)},subscribeAll:i=>{this.subscribeAll(s,i)}}}subscribeMany(s,i){i((e,t)=>this.subscribe(s,e,t))}subscribe(s,i,e){this.subscriptions.has(i)||this.subscriptions.set(i,[]),this.registry.register(e,s),this.subscriptions.get(i)?.push({id:s,ref:new WeakRef(e)})}subscribeAll(s,i){this.registryGlobal.register(i,s),this.globalSubscriptions.push({id:s,ref:new WeakRef(i)})}publish(s){this.globalSubscriptions.forEach(i=>{const e=i.ref.deref();if(!e){this.unsubscribeGlobal(i.id);return}e(s)}),this.subscriptions.has(s.type)&&this.subscriptions.get(s.type)?.forEach(i=>{const e=i.ref.deref();if(!e){this.unsubscribe(i.id);return}e(s)})}unsubscribe(s){this.unsubscribeGlobal(s);for(const[i,e]of this.subscriptions)this.subscriptions.set(i,e.filter(t=>t.id!==s))}unsubscribeGlobal(s){this.globalSubscriptions=this.globalSubscriptions.filter(i=>i.id!==s)}get subscriptionsCount(){let s=0;return this.subscriptions.forEach(i=>{s+=i.length}),s+=this.globalSubscriptions.length,s}}function b(){return n.useRef(new u).current}function c(r,s,i=[]){const e=n.useMemo(()=>r instanceof u?r:r.instance,[r]),t=n.useMemo(()=>e.makeId("id"in r?r.id:void 0),[e]);n.useEffect(()=>(e.subscribeMany(t,s),()=>{e.unsubscribe(t)}),[e,t,...i])}function o(r,s,i=[]){const e=n.useMemo(()=>r instanceof u?r:r.instance,[r]),t=n.useMemo(()=>e.makeId("id"in r?r.id:void 0),[e]);n.useEffect(()=>(e.subscribeAll(t,s),()=>{e.unsubscribeGlobal(t)}),[e,t,...i])}exports.Siggn=u;exports.useSiggn=b;exports.useSubscribe=c;exports.useSubscribeAll=o;
|
|
2
|
+
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../../core/src/siggn.ts","../src/hooks.ts"],"sourcesContent":["import type { Msg, Subscription, SiggnId } from './types.js';\n\n/**\n * A type-safe message bus for dispatching and subscribing to events.\n * @template T A union of all possible message types.\n * @since 0.0.5\n */\nexport class Siggn<T extends Msg> {\n private nextId = 0;\n private subscriptions: Map<T['type'], Array<Subscription<T, any>>>;\n private globalSubscriptions: Array<Subscription<T, any>> = [];\n\n /**\n * A FinalizationRegistry to automatically unregister specific subscriptions\n * when the subscribed callback function is garbage collected.\n */\n private readonly registry = new FinalizationRegistry<SiggnId>((id) => {\n this.unsubscribe(id);\n });\n\n /**\n * A FinalizationRegistry to automatically unregister global subscriptions\n * when the subscribed callback function is garbage collected.\n */\n private readonly registryGlobal = new FinalizationRegistry<SiggnId>((id) => {\n this.unsubscribeGlobal(id);\n });\n\n /**\n * Creates a new Siggn instance.\n * @category Lifecycle\n * @since 0.0.5\n */\n constructor() {\n this.subscriptions = new Map();\n }\n\n /**\n * Creates a new, independent `Siggn` instance that inherits the message\n * types of its parent and adds new ones.\n *\n * @template C The new message types to add.\n * @returns A new `Siggn` instance with combined message types.\n * @category Lifecycle\n * @since 0.0.5\n * @example\n * \n```typescript\n * const baseSiggn = new Siggn<{ type: 'A' }>();\n * const childSiggn = baseSiggn.createClone<{ type: 'B' }>();\n * // childSiggn can now publish and subscribe to types 'A' and 'B'.\n * ```\n */\n createClone<C extends Msg>() {\n return new Siggn<C | T>();\n }\n\n /**\n * Generates a unique ID for a subscriber.\n * If an ID is provided, it will be used; otherwise, a new one is generated.\n *\n * @param id An optional ID to use.\n * @returns A unique subscriber ID.\n * @category Utilities\n * @since 0.0.5\n * @example\n * \n```typescript\n * const siggn = new Siggn();\n * const id1 = siggn.makeId(); // e.g., \"sub_0\"\n * const id2 = siggn.makeId('custom-id'); // \"custom-id\"\n * ```\n */\n makeId(id?: string): SiggnId {\n return id ?? `sub_${(this.nextId++).toString(36)}`;\n }\n\n /**\n * Creates a subscription helper object that is pre-configured with a\n * specific subscriber ID. This simplifies managing multiple subscriptions\n * for a single component or service.\n *\n * @param id The subscriber ID to use for all subscriptions.\n * @returns An object with `subscribe`, `unsubscribe`, `subscribeMany`, and `subscribeAll` methods.\n * @category Subscription\n * @since 0.0.5\n * @example\n * \n```typescript\n * const siggn = new Siggn<{ type: 'event' }>();\n * const component = siggn.make('my-component');\n * component.subscribe('event', () => console.log('event received!'));\n * component.unsubscribe();\n * ```\n */\n make(id: SiggnId): {\n subscribe: <K extends T['type']>(\n type: K,\n callback: (msg: Extract<T, { type: K }>) => void,\n ) => void;\n unsubscribe: () => void;\n subscribeMany: (\n setup: (\n subscribe: <K extends T['type']>(\n type: K,\n callback: (msg: Extract<T, { type: K }>) => void,\n ) => void,\n ) => void,\n ) => void;\n subscribeAll: (callback: (msg: T) => void) => void;\n } {\n return {\n subscribe: (type, callback) => {\n this.subscribe(id, type, callback);\n },\n unsubscribe: () => {\n this.unsubscribe(id);\n },\n subscribeMany: (setup) => {\n this.subscribeMany(id, setup);\n },\n subscribeAll: (callback) => {\n this.subscribeAll(id, callback);\n },\n };\n }\n\n /**\n * Subscribes to multiple message types using a single subscriber ID.\n *\n * @param id The subscriber ID.\n * @param setup A function that receives a `subscribe` helper to register callbacks.\n * @category Subscription\n * @since 0.0.5\n * @example\n * \n```typescript\n * const siggn = new Siggn<{ type: 'A' } | { type: 'B' }>();\n * siggn.subscribeMany('subscriber-1', (subscribe) => {\n * subscribe('A', () => console.log('A received'));\n * subscribe('B', () => console.log('B received'));\n * });\n * ```\n */\n subscribeMany(\n id: SiggnId,\n setup: (\n subscribe: <K extends T['type']>(\n type: K,\n callback: (msg: Extract<T, { type: K }>) => void,\n ) => void,\n ) => void,\n ) {\n setup((type, callback) => this.subscribe(id, type, callback));\n }\n\n /**\n * Subscribes to a specific message type.\n *\n * @param id The subscriber ID.\n * @param type The message type to subscribe to.\n * @param callback The function to call when a message of the specified type is published.\n * @category Subscription\n * @since 0.0.5\n * @example\n * \n```typescript\n * const siggn = new Siggn<{ type: 'my-event', payload: string }>();\n * siggn.subscribe('subscriber-1', 'my-event', (msg) => {\n * console.log(msg.payload);\n * });\n * ```\n */\n subscribe<K extends T['type']>(\n id: SiggnId,\n type: K,\n callback: (msg: Extract<T, { type: K }>) => void,\n ) {\n if (!this.subscriptions.has(type)) {\n this.subscriptions.set(type, []);\n }\n\n this.registry.register(callback, id);\n this.subscriptions.get(type)?.push({ id, ref: new WeakRef(callback) });\n }\n\n /**\n * Subscribes to all message types. The callback will be invoked for every\n * message published on the bus.\n *\n * @param id The subscriber ID.\n * @param callback The function to call for any message.\n * @category Subscription\n * @since 0.0.5\n * @example\n * \n```typescript\n * const siggn = new Siggn<{ type: 'A' } | { type: 'B' }>();\n * siggn.subscribeAll('logger', (msg) => {\n * console.log(`Received message of type: ${msg.type}`);\n * });\n * ```\n */\n subscribeAll(id: SiggnId, callback: (msg: T) => void) {\n this.registryGlobal.register(callback, id);\n this.globalSubscriptions.push({ id, ref: new WeakRef(callback) });\n }\n\n /**\n * Publishes a message to all relevant subscribers.\n *\n * @param msg The message to publish.\n * @category Publishing\n * @since 0.0.5\n * @example\n * \n```typescript\n * const siggn = new Siggn<{ type: 'my-event' }>();\n * siggn.subscribe('sub-1', 'my-event', () => console.log('received'));\n * siggn.publish({ type: 'my-event' }); // \"received\"\n * ```\n */\n publish(msg: T) {\n this.globalSubscriptions.forEach((sub) => {\n const fn = sub.ref.deref();\n\n if (!fn) {\n this.unsubscribeGlobal(sub.id);\n return;\n }\n\n fn(msg as Extract<T, { type: any }>);\n });\n\n if (!this.subscriptions.has(msg.type)) {\n return;\n }\n\n this.subscriptions.get(msg.type)?.forEach((sub) => {\n const fn = sub.ref.deref();\n\n if (!fn) {\n this.unsubscribe(sub.id);\n return;\n }\n\n fn(msg as Extract<T, { type: any }>);\n });\n }\n\n /**\n * Removes all subscriptions (both specific and global) for a given\n * subscriber ID.\n *\n * @param id The subscriber ID to unsubscribe.\n * @category Subscription\n * @since 0.0.5\n * @example\n * \n```typescript\n * const siggn = new Siggn<{ type: 'my-event' }>();\n * siggn.subscribe('sub-1', 'my-event', () => console.log('received'));\n * siggn.unsubscribe('sub-1');\n * siggn.publish({ type: 'my-event' }); // (nothing is logged)\n * ```\n */\n unsubscribe(id: SiggnId) {\n this.unsubscribeGlobal(id);\n\n for (const [type, subscriptions] of this.subscriptions) {\n this.subscriptions.set(\n type,\n subscriptions.filter((sub) => sub.id !== id),\n );\n }\n }\n\n /**\n * Removes a global subscription for a given subscriber ID.\n *\n * @param id The subscriber ID to unsubscribe.\n * @category Subscription\n * @since 0.0.5\n * @example\n * \n```typescript\n * const siggn = new Siggn<{ type: 'my-event' }>();\n * siggn.subscribeAll('logger', console.log);\n * siggn.unsubscribeGlobal('logger');\n * siggn.publish({ type: 'my-event' }); // (nothing is logged)\n * ```\n */\n unsubscribeGlobal(id: SiggnId) {\n this.globalSubscriptions = this.globalSubscriptions.filter((s) => s.id !== id);\n }\n\n /**\n * Returns the total number of subscriptions (both specific and global).\n *\n * @category Subscription\n * @since 0.1.0\n * @example\n * \n```typescript\n * const siggn = new Siggn<{ type: 'my-event' }>();\n * siggn.subscribe('sub-1', 'my-event', () => {});\n * siggn.subscribeAll('logger', () => {});\n * console.log(siggn.subscriptionsCount); // 2\n * ```\n */\n get subscriptionsCount() {\n let count = 0;\n\n this.subscriptions.forEach((subs) => {\n count += subs.length;\n });\n\n count += this.globalSubscriptions.length;\n\n return count;\n }\n}\n","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":["Siggn","id","type","callback","setup","msg","sub","fn","subscriptions","s","count","subs","useSiggn","useRef","useSubscribe","options","deps","instance","useMemo","useEffect","useSubscribeAll"],"mappings":"yGAOO,MAAMA,CAAqB,CACxB,OAAS,EACT,cACA,oBAAmD,CAAA,EAM1C,SAAW,IAAI,qBAA+BC,GAAO,CACpE,KAAK,YAAYA,CAAE,CACrB,CAAC,EAMgB,eAAiB,IAAI,qBAA+BA,GAAO,CAC1E,KAAK,kBAAkBA,CAAE,CAC3B,CAAC,EAOD,aAAc,CACZ,KAAK,kBAAoB,GAC3B,CAkBA,aAA6B,CAC3B,OAAO,IAAID,CACb,CAkBA,OAAOC,EAAsB,CAC3B,OAAOA,GAAM,QAAQ,KAAK,UAAU,SAAS,EAAE,CAAC,EAClD,CAoBA,KAAKA,EAeH,CACA,MAAO,CACL,UAAW,CAACC,EAAMC,IAAa,CAC7B,KAAK,UAAUF,EAAIC,EAAMC,CAAQ,CACnC,EACA,YAAa,IAAM,CACjB,KAAK,YAAYF,CAAE,CACrB,EACA,cAAgBG,GAAU,CACxB,KAAK,cAAcH,EAAIG,CAAK,CAC9B,EACA,aAAeD,GAAa,CAC1B,KAAK,aAAaF,EAAIE,CAAQ,CAChC,CAAA,CAEJ,CAmBA,cACEF,EACAG,EAMA,CACAA,EAAM,CAACF,EAAMC,IAAa,KAAK,UAAUF,EAAIC,EAAMC,CAAQ,CAAC,CAC9D,CAmBA,UACEF,EACAC,EACAC,EACA,CACK,KAAK,cAAc,IAAID,CAAI,GAC9B,KAAK,cAAc,IAAIA,EAAM,CAAA,CAAE,EAGjC,KAAK,SAAS,SAASC,EAAUF,CAAE,EACnC,KAAK,cAAc,IAAIC,CAAI,GAAG,KAAK,CAAE,GAAAD,EAAI,IAAK,IAAI,QAAQE,CAAQ,CAAA,CAAG,CACvE,CAmBA,aAAaF,EAAaE,EAA4B,CACpD,KAAK,eAAe,SAASA,EAAUF,CAAE,EACzC,KAAK,oBAAoB,KAAK,CAAE,GAAAA,EAAI,IAAK,IAAI,QAAQE,CAAQ,EAAG,CAClE,CAgBA,QAAQE,EAAQ,CACd,KAAK,oBAAoB,QAASC,GAAQ,CACxC,MAAMC,EAAKD,EAAI,IAAI,MAAA,EAEnB,GAAI,CAACC,EAAI,CACP,KAAK,kBAAkBD,EAAI,EAAE,EAC7B,MACF,CAEAC,EAAGF,CAAgC,CACrC,CAAC,EAEI,KAAK,cAAc,IAAIA,EAAI,IAAI,GAIpC,KAAK,cAAc,IAAIA,EAAI,IAAI,GAAG,QAASC,GAAQ,CACjD,MAAMC,EAAKD,EAAI,IAAI,MAAA,EAEnB,GAAI,CAACC,EAAI,CACP,KAAK,YAAYD,EAAI,EAAE,EACvB,MACF,CAEAC,EAAGF,CAAgC,CACrC,CAAC,CACH,CAkBA,YAAYJ,EAAa,CACvB,KAAK,kBAAkBA,CAAE,EAEzB,SAAW,CAACC,EAAMM,CAAa,IAAK,KAAK,cACvC,KAAK,cAAc,IACjBN,EACAM,EAAc,OAAQF,GAAQA,EAAI,KAAOL,CAAE,CAAA,CAGjD,CAiBA,kBAAkBA,EAAa,CAC7B,KAAK,oBAAsB,KAAK,oBAAoB,OAAQQ,GAAMA,EAAE,KAAOR,CAAE,CAC/E,CAgBA,IAAI,oBAAqB,CACvB,IAAIS,EAAQ,EAEZ,YAAK,cAAc,QAASC,GAAS,CACnCD,GAASC,EAAK,MAChB,CAAC,EAEDD,GAAS,KAAK,oBAAoB,OAE3BA,CACT,CACF,CC5SO,SAASE,GAAoC,CAElD,OADcC,EAAAA,OAAO,IAAIb,CAAU,EACtB,OACf,CAwBO,SAASc,EACdC,EACAX,EAMAY,EAAuB,CAAA,EACvB,CACA,MAAMC,EAAWC,EAAAA,QACf,IAAOH,aAAmBf,EAAQe,EAAUA,EAAQ,SACpD,CAACA,CAAO,CAAA,EAEJd,EAAKiB,EAAAA,QAAQ,IAAMD,EAAS,OAAO,OAAQF,EAAUA,EAAQ,GAAK,MAAS,EAAG,CAACE,CAAQ,CAAC,EAE9FE,EAAAA,UAAU,KACRF,EAAS,cAAchB,EAAIG,CAAK,EAEzB,IAAM,CACXa,EAAS,YAAYhB,CAAE,CACzB,GACC,CAACgB,EAAUhB,EAAI,GAAGe,CAAI,CAAC,CAC5B,CAyBO,SAASI,EACdL,EACAZ,EACAa,EAAuB,CAAA,EACvB,CACA,MAAMC,EAAWC,EAAAA,QACf,IAAOH,aAAmBf,EAAQe,EAAUA,EAAQ,SACpD,CAACA,CAAO,CAAA,EAEJd,EAAKiB,EAAAA,QAAQ,IAAMD,EAAS,OAAO,OAAQF,EAAUA,EAAQ,GAAK,MAAS,EAAG,CAACE,CAAQ,CAAC,EAE9FE,EAAAA,UAAU,KACRF,EAAS,aAAahB,EAAIE,CAAQ,EAE3B,IAAM,CACXc,EAAS,kBAAkBhB,CAAE,CAC/B,GACC,CAACgB,EAAUhB,EAAI,GAAGe,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,281 @@
|
|
|
1
|
+
import { useRef as c, useMemo as b, useEffect as u } from "react";
|
|
2
|
+
class n {
|
|
3
|
+
nextId = 0;
|
|
4
|
+
subscriptions;
|
|
5
|
+
globalSubscriptions = [];
|
|
6
|
+
/**
|
|
7
|
+
* A FinalizationRegistry to automatically unregister specific subscriptions
|
|
8
|
+
* when the subscribed callback function is garbage collected.
|
|
9
|
+
*/
|
|
10
|
+
registry = new FinalizationRegistry((s) => {
|
|
11
|
+
this.unsubscribe(s);
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* A FinalizationRegistry to automatically unregister global subscriptions
|
|
15
|
+
* when the subscribed callback function is garbage collected.
|
|
16
|
+
*/
|
|
17
|
+
registryGlobal = new FinalizationRegistry((s) => {
|
|
18
|
+
this.unsubscribeGlobal(s);
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* Creates a new Siggn instance.
|
|
22
|
+
* @category Lifecycle
|
|
23
|
+
* @since 0.0.5
|
|
24
|
+
*/
|
|
25
|
+
constructor() {
|
|
26
|
+
this.subscriptions = /* @__PURE__ */ new Map();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Creates a new, independent `Siggn` instance that inherits the message
|
|
30
|
+
* types of its parent and adds new ones.
|
|
31
|
+
*
|
|
32
|
+
* @template C The new message types to add.
|
|
33
|
+
* @returns A new `Siggn` instance with combined message types.
|
|
34
|
+
* @category Lifecycle
|
|
35
|
+
* @since 0.0.5
|
|
36
|
+
* @example
|
|
37
|
+
*
|
|
38
|
+
```typescript
|
|
39
|
+
* const baseSiggn = new Siggn<{ type: 'A' }>();
|
|
40
|
+
* const childSiggn = baseSiggn.createClone<{ type: 'B' }>();
|
|
41
|
+
* // childSiggn can now publish and subscribe to types 'A' and 'B'.
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
createClone() {
|
|
45
|
+
return new n();
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Generates a unique ID for a subscriber.
|
|
49
|
+
* If an ID is provided, it will be used; otherwise, a new one is generated.
|
|
50
|
+
*
|
|
51
|
+
* @param id An optional ID to use.
|
|
52
|
+
* @returns A unique subscriber ID.
|
|
53
|
+
* @category Utilities
|
|
54
|
+
* @since 0.0.5
|
|
55
|
+
* @example
|
|
56
|
+
*
|
|
57
|
+
```typescript
|
|
58
|
+
* const siggn = new Siggn();
|
|
59
|
+
* const id1 = siggn.makeId(); // e.g., "sub_0"
|
|
60
|
+
* const id2 = siggn.makeId('custom-id'); // "custom-id"
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
makeId(s) {
|
|
64
|
+
return s ?? `sub_${(this.nextId++).toString(36)}`;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Creates a subscription helper object that is pre-configured with a
|
|
68
|
+
* specific subscriber ID. This simplifies managing multiple subscriptions
|
|
69
|
+
* for a single component or service.
|
|
70
|
+
*
|
|
71
|
+
* @param id The subscriber ID to use for all subscriptions.
|
|
72
|
+
* @returns An object with `subscribe`, `unsubscribe`, `subscribeMany`, and `subscribeAll` methods.
|
|
73
|
+
* @category Subscription
|
|
74
|
+
* @since 0.0.5
|
|
75
|
+
* @example
|
|
76
|
+
*
|
|
77
|
+
```typescript
|
|
78
|
+
* const siggn = new Siggn<{ type: 'event' }>();
|
|
79
|
+
* const component = siggn.make('my-component');
|
|
80
|
+
* component.subscribe('event', () => console.log('event received!'));
|
|
81
|
+
* component.unsubscribe();
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
make(s) {
|
|
85
|
+
return {
|
|
86
|
+
subscribe: (i, e) => {
|
|
87
|
+
this.subscribe(s, i, e);
|
|
88
|
+
},
|
|
89
|
+
unsubscribe: () => {
|
|
90
|
+
this.unsubscribe(s);
|
|
91
|
+
},
|
|
92
|
+
subscribeMany: (i) => {
|
|
93
|
+
this.subscribeMany(s, i);
|
|
94
|
+
},
|
|
95
|
+
subscribeAll: (i) => {
|
|
96
|
+
this.subscribeAll(s, i);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Subscribes to multiple message types using a single subscriber ID.
|
|
102
|
+
*
|
|
103
|
+
* @param id The subscriber ID.
|
|
104
|
+
* @param setup A function that receives a `subscribe` helper to register callbacks.
|
|
105
|
+
* @category Subscription
|
|
106
|
+
* @since 0.0.5
|
|
107
|
+
* @example
|
|
108
|
+
*
|
|
109
|
+
```typescript
|
|
110
|
+
* const siggn = new Siggn<{ type: 'A' } | { type: 'B' }>();
|
|
111
|
+
* siggn.subscribeMany('subscriber-1', (subscribe) => {
|
|
112
|
+
* subscribe('A', () => console.log('A received'));
|
|
113
|
+
* subscribe('B', () => console.log('B received'));
|
|
114
|
+
* });
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
subscribeMany(s, i) {
|
|
118
|
+
i((e, t) => this.subscribe(s, e, t));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Subscribes to a specific message type.
|
|
122
|
+
*
|
|
123
|
+
* @param id The subscriber ID.
|
|
124
|
+
* @param type The message type to subscribe to.
|
|
125
|
+
* @param callback The function to call when a message of the specified type is published.
|
|
126
|
+
* @category Subscription
|
|
127
|
+
* @since 0.0.5
|
|
128
|
+
* @example
|
|
129
|
+
*
|
|
130
|
+
```typescript
|
|
131
|
+
* const siggn = new Siggn<{ type: 'my-event', payload: string }>();
|
|
132
|
+
* siggn.subscribe('subscriber-1', 'my-event', (msg) => {
|
|
133
|
+
* console.log(msg.payload);
|
|
134
|
+
* });
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
subscribe(s, i, e) {
|
|
138
|
+
this.subscriptions.has(i) || this.subscriptions.set(i, []), this.registry.register(e, s), this.subscriptions.get(i)?.push({ id: s, ref: new WeakRef(e) });
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Subscribes to all message types. The callback will be invoked for every
|
|
142
|
+
* message published on the bus.
|
|
143
|
+
*
|
|
144
|
+
* @param id The subscriber ID.
|
|
145
|
+
* @param callback The function to call for any message.
|
|
146
|
+
* @category Subscription
|
|
147
|
+
* @since 0.0.5
|
|
148
|
+
* @example
|
|
149
|
+
*
|
|
150
|
+
```typescript
|
|
151
|
+
* const siggn = new Siggn<{ type: 'A' } | { type: 'B' }>();
|
|
152
|
+
* siggn.subscribeAll('logger', (msg) => {
|
|
153
|
+
* console.log(`Received message of type: ${msg.type}`);
|
|
154
|
+
* });
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
subscribeAll(s, i) {
|
|
158
|
+
this.registryGlobal.register(i, s), this.globalSubscriptions.push({ id: s, ref: new WeakRef(i) });
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Publishes a message to all relevant subscribers.
|
|
162
|
+
*
|
|
163
|
+
* @param msg The message to publish.
|
|
164
|
+
* @category Publishing
|
|
165
|
+
* @since 0.0.5
|
|
166
|
+
* @example
|
|
167
|
+
*
|
|
168
|
+
```typescript
|
|
169
|
+
* const siggn = new Siggn<{ type: 'my-event' }>();
|
|
170
|
+
* siggn.subscribe('sub-1', 'my-event', () => console.log('received'));
|
|
171
|
+
* siggn.publish({ type: 'my-event' }); // "received"
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
publish(s) {
|
|
175
|
+
this.globalSubscriptions.forEach((i) => {
|
|
176
|
+
const e = i.ref.deref();
|
|
177
|
+
if (!e) {
|
|
178
|
+
this.unsubscribeGlobal(i.id);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
e(s);
|
|
182
|
+
}), this.subscriptions.has(s.type) && this.subscriptions.get(s.type)?.forEach((i) => {
|
|
183
|
+
const e = i.ref.deref();
|
|
184
|
+
if (!e) {
|
|
185
|
+
this.unsubscribe(i.id);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
e(s);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Removes all subscriptions (both specific and global) for a given
|
|
193
|
+
* subscriber ID.
|
|
194
|
+
*
|
|
195
|
+
* @param id The subscriber ID to unsubscribe.
|
|
196
|
+
* @category Subscription
|
|
197
|
+
* @since 0.0.5
|
|
198
|
+
* @example
|
|
199
|
+
*
|
|
200
|
+
```typescript
|
|
201
|
+
* const siggn = new Siggn<{ type: 'my-event' }>();
|
|
202
|
+
* siggn.subscribe('sub-1', 'my-event', () => console.log('received'));
|
|
203
|
+
* siggn.unsubscribe('sub-1');
|
|
204
|
+
* siggn.publish({ type: 'my-event' }); // (nothing is logged)
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
unsubscribe(s) {
|
|
208
|
+
this.unsubscribeGlobal(s);
|
|
209
|
+
for (const [i, e] of this.subscriptions)
|
|
210
|
+
this.subscriptions.set(
|
|
211
|
+
i,
|
|
212
|
+
e.filter((t) => t.id !== s)
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Removes a global subscription for a given subscriber ID.
|
|
217
|
+
*
|
|
218
|
+
* @param id The subscriber ID to unsubscribe.
|
|
219
|
+
* @category Subscription
|
|
220
|
+
* @since 0.0.5
|
|
221
|
+
* @example
|
|
222
|
+
*
|
|
223
|
+
```typescript
|
|
224
|
+
* const siggn = new Siggn<{ type: 'my-event' }>();
|
|
225
|
+
* siggn.subscribeAll('logger', console.log);
|
|
226
|
+
* siggn.unsubscribeGlobal('logger');
|
|
227
|
+
* siggn.publish({ type: 'my-event' }); // (nothing is logged)
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
unsubscribeGlobal(s) {
|
|
231
|
+
this.globalSubscriptions = this.globalSubscriptions.filter((i) => i.id !== s);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Returns the total number of subscriptions (both specific and global).
|
|
235
|
+
*
|
|
236
|
+
* @category Subscription
|
|
237
|
+
* @since 0.1.0
|
|
238
|
+
* @example
|
|
239
|
+
*
|
|
240
|
+
```typescript
|
|
241
|
+
* const siggn = new Siggn<{ type: 'my-event' }>();
|
|
242
|
+
* siggn.subscribe('sub-1', 'my-event', () => {});
|
|
243
|
+
* siggn.subscribeAll('logger', () => {});
|
|
244
|
+
* console.log(siggn.subscriptionsCount); // 2
|
|
245
|
+
* ```
|
|
246
|
+
*/
|
|
247
|
+
get subscriptionsCount() {
|
|
248
|
+
let s = 0;
|
|
249
|
+
return this.subscriptions.forEach((i) => {
|
|
250
|
+
s += i.length;
|
|
251
|
+
}), s += this.globalSubscriptions.length, s;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function l() {
|
|
255
|
+
return c(new n()).current;
|
|
256
|
+
}
|
|
257
|
+
function a(r, s, i = []) {
|
|
258
|
+
const e = b(
|
|
259
|
+
() => r instanceof n ? r : r.instance,
|
|
260
|
+
[r]
|
|
261
|
+
), t = b(() => e.makeId("id" in r ? r.id : void 0), [e]);
|
|
262
|
+
u(() => (e.subscribeMany(t, s), () => {
|
|
263
|
+
e.unsubscribe(t);
|
|
264
|
+
}), [e, t, ...i]);
|
|
265
|
+
}
|
|
266
|
+
function h(r, s, i = []) {
|
|
267
|
+
const e = b(
|
|
268
|
+
() => r instanceof n ? r : r.instance,
|
|
269
|
+
[r]
|
|
270
|
+
), t = b(() => e.makeId("id" in r ? r.id : void 0), [e]);
|
|
271
|
+
u(() => (e.subscribeAll(t, s), () => {
|
|
272
|
+
e.unsubscribeGlobal(t);
|
|
273
|
+
}), [e, t, ...i]);
|
|
274
|
+
}
|
|
275
|
+
export {
|
|
276
|
+
n as Siggn,
|
|
277
|
+
l as useSiggn,
|
|
278
|
+
a as useSubscribe,
|
|
279
|
+
h as useSubscribeAll
|
|
280
|
+
};
|
|
281
|
+
//# sourceMappingURL=index.es.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.es.js","sources":["../../core/src/siggn.ts","../src/hooks.ts"],"sourcesContent":["import type { Msg, Subscription, SiggnId } from './types.js';\n\n/**\n * A type-safe message bus for dispatching and subscribing to events.\n * @template T A union of all possible message types.\n * @since 0.0.5\n */\nexport class Siggn<T extends Msg> {\n private nextId = 0;\n private subscriptions: Map<T['type'], Array<Subscription<T, any>>>;\n private globalSubscriptions: Array<Subscription<T, any>> = [];\n\n /**\n * A FinalizationRegistry to automatically unregister specific subscriptions\n * when the subscribed callback function is garbage collected.\n */\n private readonly registry = new FinalizationRegistry<SiggnId>((id) => {\n this.unsubscribe(id);\n });\n\n /**\n * A FinalizationRegistry to automatically unregister global subscriptions\n * when the subscribed callback function is garbage collected.\n */\n private readonly registryGlobal = new FinalizationRegistry<SiggnId>((id) => {\n this.unsubscribeGlobal(id);\n });\n\n /**\n * Creates a new Siggn instance.\n * @category Lifecycle\n * @since 0.0.5\n */\n constructor() {\n this.subscriptions = new Map();\n }\n\n /**\n * Creates a new, independent `Siggn` instance that inherits the message\n * types of its parent and adds new ones.\n *\n * @template C The new message types to add.\n * @returns A new `Siggn` instance with combined message types.\n * @category Lifecycle\n * @since 0.0.5\n * @example\n * \n```typescript\n * const baseSiggn = new Siggn<{ type: 'A' }>();\n * const childSiggn = baseSiggn.createClone<{ type: 'B' }>();\n * // childSiggn can now publish and subscribe to types 'A' and 'B'.\n * ```\n */\n createClone<C extends Msg>() {\n return new Siggn<C | T>();\n }\n\n /**\n * Generates a unique ID for a subscriber.\n * If an ID is provided, it will be used; otherwise, a new one is generated.\n *\n * @param id An optional ID to use.\n * @returns A unique subscriber ID.\n * @category Utilities\n * @since 0.0.5\n * @example\n * \n```typescript\n * const siggn = new Siggn();\n * const id1 = siggn.makeId(); // e.g., \"sub_0\"\n * const id2 = siggn.makeId('custom-id'); // \"custom-id\"\n * ```\n */\n makeId(id?: string): SiggnId {\n return id ?? `sub_${(this.nextId++).toString(36)}`;\n }\n\n /**\n * Creates a subscription helper object that is pre-configured with a\n * specific subscriber ID. This simplifies managing multiple subscriptions\n * for a single component or service.\n *\n * @param id The subscriber ID to use for all subscriptions.\n * @returns An object with `subscribe`, `unsubscribe`, `subscribeMany`, and `subscribeAll` methods.\n * @category Subscription\n * @since 0.0.5\n * @example\n * \n```typescript\n * const siggn = new Siggn<{ type: 'event' }>();\n * const component = siggn.make('my-component');\n * component.subscribe('event', () => console.log('event received!'));\n * component.unsubscribe();\n * ```\n */\n make(id: SiggnId): {\n subscribe: <K extends T['type']>(\n type: K,\n callback: (msg: Extract<T, { type: K }>) => void,\n ) => void;\n unsubscribe: () => void;\n subscribeMany: (\n setup: (\n subscribe: <K extends T['type']>(\n type: K,\n callback: (msg: Extract<T, { type: K }>) => void,\n ) => void,\n ) => void,\n ) => void;\n subscribeAll: (callback: (msg: T) => void) => void;\n } {\n return {\n subscribe: (type, callback) => {\n this.subscribe(id, type, callback);\n },\n unsubscribe: () => {\n this.unsubscribe(id);\n },\n subscribeMany: (setup) => {\n this.subscribeMany(id, setup);\n },\n subscribeAll: (callback) => {\n this.subscribeAll(id, callback);\n },\n };\n }\n\n /**\n * Subscribes to multiple message types using a single subscriber ID.\n *\n * @param id The subscriber ID.\n * @param setup A function that receives a `subscribe` helper to register callbacks.\n * @category Subscription\n * @since 0.0.5\n * @example\n * \n```typescript\n * const siggn = new Siggn<{ type: 'A' } | { type: 'B' }>();\n * siggn.subscribeMany('subscriber-1', (subscribe) => {\n * subscribe('A', () => console.log('A received'));\n * subscribe('B', () => console.log('B received'));\n * });\n * ```\n */\n subscribeMany(\n id: SiggnId,\n setup: (\n subscribe: <K extends T['type']>(\n type: K,\n callback: (msg: Extract<T, { type: K }>) => void,\n ) => void,\n ) => void,\n ) {\n setup((type, callback) => this.subscribe(id, type, callback));\n }\n\n /**\n * Subscribes to a specific message type.\n *\n * @param id The subscriber ID.\n * @param type The message type to subscribe to.\n * @param callback The function to call when a message of the specified type is published.\n * @category Subscription\n * @since 0.0.5\n * @example\n * \n```typescript\n * const siggn = new Siggn<{ type: 'my-event', payload: string }>();\n * siggn.subscribe('subscriber-1', 'my-event', (msg) => {\n * console.log(msg.payload);\n * });\n * ```\n */\n subscribe<K extends T['type']>(\n id: SiggnId,\n type: K,\n callback: (msg: Extract<T, { type: K }>) => void,\n ) {\n if (!this.subscriptions.has(type)) {\n this.subscriptions.set(type, []);\n }\n\n this.registry.register(callback, id);\n this.subscriptions.get(type)?.push({ id, ref: new WeakRef(callback) });\n }\n\n /**\n * Subscribes to all message types. The callback will be invoked for every\n * message published on the bus.\n *\n * @param id The subscriber ID.\n * @param callback The function to call for any message.\n * @category Subscription\n * @since 0.0.5\n * @example\n * \n```typescript\n * const siggn = new Siggn<{ type: 'A' } | { type: 'B' }>();\n * siggn.subscribeAll('logger', (msg) => {\n * console.log(`Received message of type: ${msg.type}`);\n * });\n * ```\n */\n subscribeAll(id: SiggnId, callback: (msg: T) => void) {\n this.registryGlobal.register(callback, id);\n this.globalSubscriptions.push({ id, ref: new WeakRef(callback) });\n }\n\n /**\n * Publishes a message to all relevant subscribers.\n *\n * @param msg The message to publish.\n * @category Publishing\n * @since 0.0.5\n * @example\n * \n```typescript\n * const siggn = new Siggn<{ type: 'my-event' }>();\n * siggn.subscribe('sub-1', 'my-event', () => console.log('received'));\n * siggn.publish({ type: 'my-event' }); // \"received\"\n * ```\n */\n publish(msg: T) {\n this.globalSubscriptions.forEach((sub) => {\n const fn = sub.ref.deref();\n\n if (!fn) {\n this.unsubscribeGlobal(sub.id);\n return;\n }\n\n fn(msg as Extract<T, { type: any }>);\n });\n\n if (!this.subscriptions.has(msg.type)) {\n return;\n }\n\n this.subscriptions.get(msg.type)?.forEach((sub) => {\n const fn = sub.ref.deref();\n\n if (!fn) {\n this.unsubscribe(sub.id);\n return;\n }\n\n fn(msg as Extract<T, { type: any }>);\n });\n }\n\n /**\n * Removes all subscriptions (both specific and global) for a given\n * subscriber ID.\n *\n * @param id The subscriber ID to unsubscribe.\n * @category Subscription\n * @since 0.0.5\n * @example\n * \n```typescript\n * const siggn = new Siggn<{ type: 'my-event' }>();\n * siggn.subscribe('sub-1', 'my-event', () => console.log('received'));\n * siggn.unsubscribe('sub-1');\n * siggn.publish({ type: 'my-event' }); // (nothing is logged)\n * ```\n */\n unsubscribe(id: SiggnId) {\n this.unsubscribeGlobal(id);\n\n for (const [type, subscriptions] of this.subscriptions) {\n this.subscriptions.set(\n type,\n subscriptions.filter((sub) => sub.id !== id),\n );\n }\n }\n\n /**\n * Removes a global subscription for a given subscriber ID.\n *\n * @param id The subscriber ID to unsubscribe.\n * @category Subscription\n * @since 0.0.5\n * @example\n * \n```typescript\n * const siggn = new Siggn<{ type: 'my-event' }>();\n * siggn.subscribeAll('logger', console.log);\n * siggn.unsubscribeGlobal('logger');\n * siggn.publish({ type: 'my-event' }); // (nothing is logged)\n * ```\n */\n unsubscribeGlobal(id: SiggnId) {\n this.globalSubscriptions = this.globalSubscriptions.filter((s) => s.id !== id);\n }\n\n /**\n * Returns the total number of subscriptions (both specific and global).\n *\n * @category Subscription\n * @since 0.1.0\n * @example\n * \n```typescript\n * const siggn = new Siggn<{ type: 'my-event' }>();\n * siggn.subscribe('sub-1', 'my-event', () => {});\n * siggn.subscribeAll('logger', () => {});\n * console.log(siggn.subscriptionsCount); // 2\n * ```\n */\n get subscriptionsCount() {\n let count = 0;\n\n this.subscriptions.forEach((subs) => {\n count += subs.length;\n });\n\n count += this.globalSubscriptions.length;\n\n return count;\n }\n}\n","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":["Siggn","id","type","callback","setup","msg","sub","fn","subscriptions","s","count","subs","useSiggn","useRef","useSubscribe","options","deps","instance","useMemo","useEffect","useSubscribeAll"],"mappings":"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"}
|
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.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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",
|
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,28 +0,0 @@
|
|
|
1
|
-
# @siggn/react
|
|
2
|
-
|
|
3
|
-
## 0.1.0
|
|
4
|
-
|
|
5
|
-
### Minor Changes
|
|
6
|
-
|
|
7
|
-
- [`54e3afd`](https://github.com/Guiguerreiro39/siggn/commit/54e3afd430bdb73ef0549ebdfbf9fc6f76bc62b1)
|
|
8
|
-
Thanks [@Guiguerreiro39](https://github.com/Guiguerreiro39)! - - Introduced automatic garbage
|
|
9
|
-
collection to prevent memory leaks.
|
|
10
|
-
- Added code documentation
|
|
11
|
-
- Added `subscriptionsCount` property to `Siggn`
|
|
12
|
-
|
|
13
|
-
### Patch Changes
|
|
14
|
-
|
|
15
|
-
- Updated dependencies
|
|
16
|
-
[[`54e3afd`](https://github.com/Guiguerreiro39/siggn/commit/54e3afd430bdb73ef0549ebdfbf9fc6f76bc62b1)]:
|
|
17
|
-
- @siggn/core@0.1.0
|
|
18
|
-
|
|
19
|
-
## 0.0.1
|
|
20
|
-
|
|
21
|
-
### Patch Changes
|
|
22
|
-
|
|
23
|
-
- [`d2a88af`](https://github.com/Guiguerreiro39/siggn/commit/d2a88af8e484f28e436c594cd3238379295941a2)
|
|
24
|
-
Thanks [@Guiguerreiro39](https://github.com/Guiguerreiro39)! - Added react hooks support
|
|
25
|
-
|
|
26
|
-
- Updated dependencies
|
|
27
|
-
[[`d2a88af`](https://github.com/Guiguerreiro39/siggn/commit/d2a88af8e484f28e436c594cd3238379295941a2)]:
|
|
28
|
-
- @siggn/core@0.0.5
|
package/src/index.ts
DELETED
package/src/types.ts
DELETED
package/tests/hooks.test.tsx
DELETED
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
import { test, expect, describe, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { useSiggn, useSubscribe, useSubscribeAll } from '../src/hooks.js';
|
|
3
|
-
import { Siggn } from '@siggn/core';
|
|
4
|
-
import { act, render, renderHook, screen, cleanup } 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
|
-
| { type: 'custom_event'; data: string }
|
|
14
|
-
| { type: 'dependency_changed'; newValue: number };
|
|
15
|
-
|
|
16
|
-
describe('@siggn/react', () => {
|
|
17
|
-
let siggn: Siggn<Msg>;
|
|
18
|
-
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
const { result } = renderHook(() => useSiggn<Msg>());
|
|
21
|
-
siggn = result.current;
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
afterEach(() => {
|
|
25
|
-
cleanup();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test('user should be able to create a siggn instance and subscribe using hooks', () => {
|
|
29
|
-
function TestComponent() {
|
|
30
|
-
const [count, setCount] = useState(0);
|
|
31
|
-
|
|
32
|
-
useSubscribe(siggn, (subscribe) => {
|
|
33
|
-
subscribe('increment_count', (msg) => {
|
|
34
|
-
setCount((prev) => prev + msg.value);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
subscribe('decrement_count', (msg) => {
|
|
38
|
-
setCount((prev) => prev - msg.value);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
return <div data-testid='value'>{count}</div>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
render(<TestComponent />);
|
|
46
|
-
|
|
47
|
-
expect(screen.getByTestId('value')).toHaveTextContent('0');
|
|
48
|
-
|
|
49
|
-
act(() => {
|
|
50
|
-
siggn.publish({ type: 'increment_count', value: 4 });
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
expect(screen.getByTestId('value')).toHaveTextContent('4');
|
|
54
|
-
|
|
55
|
-
act(() => {
|
|
56
|
-
siggn.publish({ type: 'decrement_count', value: 2 });
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
expect(screen.getByTestId('value')).toHaveTextContent('2');
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('useSiggn should return a stable instance across re-renders', () => {
|
|
63
|
-
const { result, rerender } = renderHook(() => useSiggn<Msg>());
|
|
64
|
-
const firstInstance = result.current;
|
|
65
|
-
|
|
66
|
-
rerender(); // Re-render the hook
|
|
67
|
-
|
|
68
|
-
const secondInstance = result.current;
|
|
69
|
-
expect(firstInstance).toBe(secondInstance); // Should be the same instance
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test('useSubscribe should work with explicit id in options', () => {
|
|
73
|
-
let receivedData: string | null = null;
|
|
74
|
-
const customId = 'my-custom-subscriber';
|
|
75
|
-
|
|
76
|
-
function TestComponent() {
|
|
77
|
-
useSubscribe({ instance: siggn, id: customId }, (subscribe) => {
|
|
78
|
-
subscribe('custom_event', (msg) => {
|
|
79
|
-
receivedData = msg.data;
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
render(<TestComponent />);
|
|
86
|
-
|
|
87
|
-
act(() => {
|
|
88
|
-
siggn.publish({ type: 'custom_event', data: 'hello' });
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
expect(receivedData).toBe('hello');
|
|
92
|
-
|
|
93
|
-
// Manually unsubscribe using the custom ID
|
|
94
|
-
act(() => {
|
|
95
|
-
siggn.unsubscribe(customId);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
act(() => {
|
|
99
|
-
siggn.publish({ type: 'custom_event', data: 'world' });
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// Should not receive 'world' as it's unsubscribed
|
|
103
|
-
expect(receivedData).toBe('hello');
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test('useSubscribe should re-subscribe when dependencies change', () => {
|
|
107
|
-
let callCount = 0;
|
|
108
|
-
let lastReceivedValue = 0;
|
|
109
|
-
|
|
110
|
-
function TestComponent({ depValue }: { depValue: number }) {
|
|
111
|
-
useSubscribe(
|
|
112
|
-
siggn,
|
|
113
|
-
(subscribe) => {
|
|
114
|
-
callCount++; // This should increment when subscription is re-established
|
|
115
|
-
subscribe('dependency_changed', (msg) => {
|
|
116
|
-
lastReceivedValue = msg.newValue + depValue; // Use depValue in callback
|
|
117
|
-
});
|
|
118
|
-
},
|
|
119
|
-
[depValue], // Dependency array includes depValue
|
|
120
|
-
);
|
|
121
|
-
return <div data-testid='last-value'>{lastReceivedValue}</div>;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const { rerender } = render(<TestComponent depValue={10} />);
|
|
125
|
-
|
|
126
|
-
act(() => {
|
|
127
|
-
siggn.publish({ type: 'dependency_changed', newValue: 5 });
|
|
128
|
-
});
|
|
129
|
-
expect(lastReceivedValue).toBe(15); // 5 + 10
|
|
130
|
-
expect(callCount).toBe(1);
|
|
131
|
-
|
|
132
|
-
rerender(<TestComponent depValue={20} />); // Change dependency
|
|
133
|
-
|
|
134
|
-
act(() => {
|
|
135
|
-
siggn.publish({ type: 'dependency_changed', newValue: 5 });
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
expect(lastReceivedValue).toBe(25); // 5 + 20
|
|
139
|
-
expect(callCount).toBe(2); // Subscription should have been re-established
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test('useSubscribe should automatically unsubscribe on component unmount', () => {
|
|
143
|
-
let receivedMessage: string | null = null;
|
|
144
|
-
|
|
145
|
-
function TestComponent() {
|
|
146
|
-
useSubscribe(siggn, (subscribe) => {
|
|
147
|
-
subscribe('custom_event', (msg) => {
|
|
148
|
-
receivedMessage = msg.data;
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const { unmount } = render(<TestComponent />);
|
|
155
|
-
|
|
156
|
-
act(() => {
|
|
157
|
-
siggn.publish({ type: 'custom_event', data: 'first message' });
|
|
158
|
-
});
|
|
159
|
-
expect(receivedMessage).toBe('first message');
|
|
160
|
-
|
|
161
|
-
receivedMessage = null; // Reset for next check
|
|
162
|
-
|
|
163
|
-
unmount(); // Unmount the component
|
|
164
|
-
|
|
165
|
-
act(() => {
|
|
166
|
-
siggn.publish({ type: 'custom_event', data: 'second message' });
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
// After unmount, the subscription should be gone
|
|
170
|
-
expect(receivedMessage).toBeNull();
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
test('useSubscribeAll should receive all messages', () => {
|
|
174
|
-
const receivedMessages: Msg[] = [];
|
|
175
|
-
const callback = vi.fn((msg: Msg) => {
|
|
176
|
-
receivedMessages.push(msg);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
function TestComponent() {
|
|
180
|
-
useSubscribeAll(siggn, callback);
|
|
181
|
-
return null;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
render(<TestComponent />);
|
|
185
|
-
|
|
186
|
-
act(() => {
|
|
187
|
-
siggn.publish({ type: 'increment_count', value: 1 });
|
|
188
|
-
siggn.publish({ type: 'custom_event', data: 'test' });
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
expect(callback).toHaveBeenCalledTimes(2);
|
|
192
|
-
expect(receivedMessages).toEqual([
|
|
193
|
-
{ type: 'increment_count', value: 1 },
|
|
194
|
-
{ type: 'custom_event', data: 'test' },
|
|
195
|
-
]);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
test('useSubscribeAll should automatically unsubscribe on component unmount', () => {
|
|
199
|
-
const callback = vi.fn();
|
|
200
|
-
|
|
201
|
-
function TestComponent() {
|
|
202
|
-
useSubscribeAll(siggn, callback);
|
|
203
|
-
return null;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const { unmount } = render(<TestComponent />);
|
|
207
|
-
|
|
208
|
-
act(() => {
|
|
209
|
-
siggn.publish({ type: 'increment_count', value: 1 });
|
|
210
|
-
});
|
|
211
|
-
expect(callback).toHaveBeenCalledTimes(1);
|
|
212
|
-
|
|
213
|
-
unmount();
|
|
214
|
-
|
|
215
|
-
act(() => {
|
|
216
|
-
siggn.publish({ type: 'custom_event', data: 'after unmount' });
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
expect(callback).toHaveBeenCalledTimes(1); // Should not be called again
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
test('useSubscribeAll should re-subscribe when dependencies change', () => {
|
|
223
|
-
const callback = vi.fn();
|
|
224
|
-
|
|
225
|
-
function TestComponent({ dep }: { dep: number }) {
|
|
226
|
-
useSubscribeAll(siggn, callback, [dep]);
|
|
227
|
-
return null;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const { rerender } = render(<TestComponent dep={1} />);
|
|
231
|
-
|
|
232
|
-
act(() => {
|
|
233
|
-
siggn.publish({ type: 'increment_count', value: 1 });
|
|
234
|
-
});
|
|
235
|
-
expect(callback).toHaveBeenCalledTimes(1);
|
|
236
|
-
|
|
237
|
-
rerender(<TestComponent dep={2} />);
|
|
238
|
-
|
|
239
|
-
act(() => {
|
|
240
|
-
siggn.publish({ type: 'increment_count', value: 2 });
|
|
241
|
-
});
|
|
242
|
-
expect(callback).toHaveBeenCalledTimes(2);
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
test('useSubscribeAll should work with explicit id in options', () => {
|
|
246
|
-
const callback = vi.fn();
|
|
247
|
-
const customId = 'my-global-subscriber';
|
|
248
|
-
|
|
249
|
-
function TestComponent() {
|
|
250
|
-
useSubscribeAll({ instance: siggn, id: customId }, callback);
|
|
251
|
-
return null;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
render(<TestComponent />);
|
|
255
|
-
|
|
256
|
-
act(() => {
|
|
257
|
-
siggn.publish({ type: 'increment_count', value: 1 });
|
|
258
|
-
});
|
|
259
|
-
expect(callback).toHaveBeenCalledTimes(1);
|
|
260
|
-
|
|
261
|
-
act(() => {
|
|
262
|
-
siggn.unsubscribeGlobal(customId);
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
act(() => {
|
|
266
|
-
siggn.publish({ type: 'increment_count', value: 2 });
|
|
267
|
-
});
|
|
268
|
-
expect(callback).toHaveBeenCalledTimes(1); // Should not be called again
|
|
269
|
-
});
|
|
270
|
-
});
|
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": ["tests"],
|
|
4
|
-
"references": [
|
|
5
|
-
{
|
|
6
|
-
"path": "tsconfig.src.json"
|
|
7
|
-
}
|
|
8
|
-
],
|
|
9
|
-
"compilerOptions": {
|
|
10
|
-
"tsBuildInfoFile": ".tsbuildinfo/test.tsbuildinfo",
|
|
11
|
-
"rootDir": "tests",
|
|
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
|
-
});
|