@siggn/core 0.0.4 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
+ [![npm version](https://badge.fury.io/js/%40siggn%2Fcore.svg)](https://www.npmjs.com/package/%40siggn%2Fcore)
2
+
1
3
  # @siggn/core
2
4
 
3
- A lightweight and type-safe event-driven pub/sub system for TypeScript projects.
5
+ A lightweight and type-safe event bus system for TypeScript projects.
4
6
 
5
7
  ## Features
6
8
 
@@ -122,9 +124,9 @@ siggn.publish({ type: 'user_deleted', userId: '123' });
122
124
  logger.unsubscribe();
123
125
  ```
124
126
 
125
- ### Extending message types with `createChild`
127
+ ### Extending message types with `createClone`
126
128
 
127
- The `createChild` method allows you to create a new, independent `Siggn` instance that inherits the
129
+ The `createClone` method allows you to create a new, independent `Siggn` instance that inherits the
128
130
  message types of its parent. This is useful for creating specialized message buses that extend a
129
131
  base set of events without affecting the parent bus.
130
132
 
@@ -136,7 +138,7 @@ const baseSiggn = new Siggn<Message>();
136
138
  type AdminMessage = { type: 'admin_login'; adminId: string };
137
139
 
138
140
  // 2. Create a child bus that understands both `Message` and `AdminMessage`
139
- const adminSiggn = baseSiggn.createChild<AdminMessage>();
141
+ const adminSiggn = baseSiggn.createClone<AdminMessage>();
140
142
 
141
143
  // 3. Subscribe to events on the child bus
142
144
  adminSiggn.subscribe('audit-log', 'user_created', (msg) => {
@@ -160,43 +162,75 @@ baseSiggn.publish({ type: 'user_created', userId: 'def', name: 'Bob' });
160
162
  // No output, because the subscription is on `adminSiggn`.
161
163
  ```
162
164
 
165
+ ### Automatic Garbage Collection
166
+
167
+ Siggn is designed to prevent memory leaks even if subscriptions are not explicitly unsubscribed. It achieves this through the use of `WeakRef` and `FinalizationRegistry`:
168
+
169
+ While explicit `unsubscribe` calls are still good practice for immediate cleanup and control, Siggn provides a robust safety net against common memory leak scenarios.
170
+
163
171
  ## API
164
172
 
165
173
  ### `new Siggn<T>()`
166
174
 
167
- Creates a new message bus instance. `T` is a union type of all possible messages.
175
+ Creates a new message bus instance.
176
+
177
+ - `T`: A union type representing all possible messages.
168
178
 
169
179
  ### `publish(msg)`
170
180
 
171
181
  Publishes a message to all relevant subscribers.
172
182
 
183
+ - `msg`: The message object to be published. It must conform to the `Msg` type.
184
+
173
185
  ### `subscribe(id, type, callback)`
174
186
 
175
- Subscribes a callback to a specific message type with a unique subscriber ID.
187
+ Subscribes a callback to a specific message type.
188
+
189
+ - `id`: A unique `SiggnId` (string) to identify the subscriber.
190
+ - `type`: The `type` of the message to subscribe to.
191
+ - `callback`: A function that executes when a message of the specified `type` is published. It receives the message as its only argument.
176
192
 
177
193
  ### `subscribeAll(id, callback)`
178
194
 
179
- Subscribes a callback to all message types with a unique subscriber ID. The callback will receive
180
- every message published on the bus.
195
+ Subscribes a callback to all message types.
196
+
197
+ - `id`: A unique `SiggnId` to identify the subscriber.
198
+ - `callback`: A function that executes for every message published. It receives the message as its only argument.
181
199
 
182
200
  ### `unsubscribe(id)`
183
201
 
184
- Removes all subscriptions associated with a specific subscriber ID.
202
+ Removes all subscriptions (specific and global) associated with a subscriber ID.
203
+
204
+ - `id`: The `SiggnId` of the subscriber to remove.
205
+
206
+ ### `unsubscribeGlobal(id)`
207
+
208
+ Removes a global subscription for a given subscriber ID.
209
+
210
+ - `id`: The `SiggnId` of the global subscriber to remove.
185
211
 
186
212
  ### `make(id)`
187
213
 
188
- Returns a helper object with `subscribe`, `subscribeMany`, `subscribeAll`, and `unsubscribe` methods
189
- pre-bound to the provided ID. This is useful for encapsulating subscription logic within a component
190
- or service.
214
+ Returns a helper object with `subscribe`, `subscribeMany`, `subscribeAll`, and `unsubscribe` methods pre-bound to the provided ID.
215
+
216
+ - `id`: A `SiggnId` to pre-bind to the subscription methods.
191
217
 
192
218
  ### `subscribeMany(id, setup)`
193
219
 
194
- A convenience method to subscribe to multiple message types for a single ID.
220
+ Subscribes to multiple message types for a single ID.
221
+
222
+ - `id`: A `SiggnId` to identify the subscriber.
223
+ - `setup`: A function that receives a `subscribe` helper to register multiple subscriptions under the same `id`.
224
+
225
+ ### `createClone<C>()`
226
+
227
+ Creates a new, independent `Siggn` instance that inherits the parent's message types and adds new ones.
228
+
229
+ - `C`: A union type of additional message types for the new instance.
195
230
 
196
- ### `createChild<C>()`
231
+ ### `subscriptionsCount()`
197
232
 
198
- Creates a new, independent `Siggn` instance whose message types are a union of the parent's types
199
- and the new child-specific types `C`.
233
+ Returns the total number of subscriptions.
200
234
 
201
235
  ## License
202
236
 
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class e{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 e}makeId(s){return s??`sub_${(this.nextId++).toString(36)}`}make(s){return{subscribe:(i,t)=>{this.subscribe(s,i,t)},unsubscribe:()=>{this.unsubscribe(s)},subscribeMany:i=>{this.subscribeMany(s,i)},subscribeAll:i=>{this.subscribeAll(s,i)}}}subscribeMany(s,i){i((t,r)=>this.subscribe(s,t,r))}subscribe(s,i,t){this.subscriptions.has(i)||this.subscriptions.set(i,[]),this.registry.register(t,s),this.subscriptions.get(i)?.push({id:s,ref:new WeakRef(t)})}subscribeAll(s,i){this.registryGlobal.register(i,s),this.globalSubscriptions.push({id:s,ref:new WeakRef(i)})}publish(s){this.globalSubscriptions.forEach(i=>{const t=i.ref.deref();if(!t){this.unsubscribeGlobal(i.id);return}t(s)}),this.subscriptions.has(s.type)&&this.subscriptions.get(s.type)?.forEach(i=>{const t=i.ref.deref();if(!t){this.unsubscribe(i.id);return}t(s)})}unsubscribe(s){this.unsubscribeGlobal(s);for(const[i,t]of this.subscriptions)this.subscriptions.set(i,t.filter(r=>r.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}}exports.Siggn=e;
2
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/siggn.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"],"names":["Siggn","id","type","callback","setup","msg","sub","fn","subscriptions","s","count","subs"],"mappings":"gFAOO,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"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,256 @@
1
+ class e {
2
+ nextId = 0;
3
+ subscriptions;
4
+ globalSubscriptions = [];
5
+ /**
6
+ * A FinalizationRegistry to automatically unregister specific subscriptions
7
+ * when the subscribed callback function is garbage collected.
8
+ */
9
+ registry = new FinalizationRegistry((s) => {
10
+ this.unsubscribe(s);
11
+ });
12
+ /**
13
+ * A FinalizationRegistry to automatically unregister global subscriptions
14
+ * when the subscribed callback function is garbage collected.
15
+ */
16
+ registryGlobal = new FinalizationRegistry((s) => {
17
+ this.unsubscribeGlobal(s);
18
+ });
19
+ /**
20
+ * Creates a new Siggn instance.
21
+ * @category Lifecycle
22
+ * @since 0.0.5
23
+ */
24
+ constructor() {
25
+ this.subscriptions = /* @__PURE__ */ new Map();
26
+ }
27
+ /**
28
+ * Creates a new, independent `Siggn` instance that inherits the message
29
+ * types of its parent and adds new ones.
30
+ *
31
+ * @template C The new message types to add.
32
+ * @returns A new `Siggn` instance with combined message types.
33
+ * @category Lifecycle
34
+ * @since 0.0.5
35
+ * @example
36
+ *
37
+ ```typescript
38
+ * const baseSiggn = new Siggn<{ type: 'A' }>();
39
+ * const childSiggn = baseSiggn.createClone<{ type: 'B' }>();
40
+ * // childSiggn can now publish and subscribe to types 'A' and 'B'.
41
+ * ```
42
+ */
43
+ createClone() {
44
+ return new e();
45
+ }
46
+ /**
47
+ * Generates a unique ID for a subscriber.
48
+ * If an ID is provided, it will be used; otherwise, a new one is generated.
49
+ *
50
+ * @param id An optional ID to use.
51
+ * @returns A unique subscriber ID.
52
+ * @category Utilities
53
+ * @since 0.0.5
54
+ * @example
55
+ *
56
+ ```typescript
57
+ * const siggn = new Siggn();
58
+ * const id1 = siggn.makeId(); // e.g., "sub_0"
59
+ * const id2 = siggn.makeId('custom-id'); // "custom-id"
60
+ * ```
61
+ */
62
+ makeId(s) {
63
+ return s ?? `sub_${(this.nextId++).toString(36)}`;
64
+ }
65
+ /**
66
+ * Creates a subscription helper object that is pre-configured with a
67
+ * specific subscriber ID. This simplifies managing multiple subscriptions
68
+ * for a single component or service.
69
+ *
70
+ * @param id The subscriber ID to use for all subscriptions.
71
+ * @returns An object with `subscribe`, `unsubscribe`, `subscribeMany`, and `subscribeAll` methods.
72
+ * @category Subscription
73
+ * @since 0.0.5
74
+ * @example
75
+ *
76
+ ```typescript
77
+ * const siggn = new Siggn<{ type: 'event' }>();
78
+ * const component = siggn.make('my-component');
79
+ * component.subscribe('event', () => console.log('event received!'));
80
+ * component.unsubscribe();
81
+ * ```
82
+ */
83
+ make(s) {
84
+ return {
85
+ subscribe: (i, t) => {
86
+ this.subscribe(s, i, t);
87
+ },
88
+ unsubscribe: () => {
89
+ this.unsubscribe(s);
90
+ },
91
+ subscribeMany: (i) => {
92
+ this.subscribeMany(s, i);
93
+ },
94
+ subscribeAll: (i) => {
95
+ this.subscribeAll(s, i);
96
+ }
97
+ };
98
+ }
99
+ /**
100
+ * Subscribes to multiple message types using a single subscriber ID.
101
+ *
102
+ * @param id The subscriber ID.
103
+ * @param setup A function that receives a `subscribe` helper to register callbacks.
104
+ * @category Subscription
105
+ * @since 0.0.5
106
+ * @example
107
+ *
108
+ ```typescript
109
+ * const siggn = new Siggn<{ type: 'A' } | { type: 'B' }>();
110
+ * siggn.subscribeMany('subscriber-1', (subscribe) => {
111
+ * subscribe('A', () => console.log('A received'));
112
+ * subscribe('B', () => console.log('B received'));
113
+ * });
114
+ * ```
115
+ */
116
+ subscribeMany(s, i) {
117
+ i((t, r) => this.subscribe(s, t, r));
118
+ }
119
+ /**
120
+ * Subscribes to a specific message type.
121
+ *
122
+ * @param id The subscriber ID.
123
+ * @param type The message type to subscribe to.
124
+ * @param callback The function to call when a message of the specified type is published.
125
+ * @category Subscription
126
+ * @since 0.0.5
127
+ * @example
128
+ *
129
+ ```typescript
130
+ * const siggn = new Siggn<{ type: 'my-event', payload: string }>();
131
+ * siggn.subscribe('subscriber-1', 'my-event', (msg) => {
132
+ * console.log(msg.payload);
133
+ * });
134
+ * ```
135
+ */
136
+ subscribe(s, i, t) {
137
+ this.subscriptions.has(i) || this.subscriptions.set(i, []), this.registry.register(t, s), this.subscriptions.get(i)?.push({ id: s, ref: new WeakRef(t) });
138
+ }
139
+ /**
140
+ * Subscribes to all message types. The callback will be invoked for every
141
+ * message published on the bus.
142
+ *
143
+ * @param id The subscriber ID.
144
+ * @param callback The function to call for any message.
145
+ * @category Subscription
146
+ * @since 0.0.5
147
+ * @example
148
+ *
149
+ ```typescript
150
+ * const siggn = new Siggn<{ type: 'A' } | { type: 'B' }>();
151
+ * siggn.subscribeAll('logger', (msg) => {
152
+ * console.log(`Received message of type: ${msg.type}`);
153
+ * });
154
+ * ```
155
+ */
156
+ subscribeAll(s, i) {
157
+ this.registryGlobal.register(i, s), this.globalSubscriptions.push({ id: s, ref: new WeakRef(i) });
158
+ }
159
+ /**
160
+ * Publishes a message to all relevant subscribers.
161
+ *
162
+ * @param msg The message to publish.
163
+ * @category Publishing
164
+ * @since 0.0.5
165
+ * @example
166
+ *
167
+ ```typescript
168
+ * const siggn = new Siggn<{ type: 'my-event' }>();
169
+ * siggn.subscribe('sub-1', 'my-event', () => console.log('received'));
170
+ * siggn.publish({ type: 'my-event' }); // "received"
171
+ * ```
172
+ */
173
+ publish(s) {
174
+ this.globalSubscriptions.forEach((i) => {
175
+ const t = i.ref.deref();
176
+ if (!t) {
177
+ this.unsubscribeGlobal(i.id);
178
+ return;
179
+ }
180
+ t(s);
181
+ }), this.subscriptions.has(s.type) && this.subscriptions.get(s.type)?.forEach((i) => {
182
+ const t = i.ref.deref();
183
+ if (!t) {
184
+ this.unsubscribe(i.id);
185
+ return;
186
+ }
187
+ t(s);
188
+ });
189
+ }
190
+ /**
191
+ * Removes all subscriptions (both specific and global) for a given
192
+ * subscriber ID.
193
+ *
194
+ * @param id The subscriber ID to unsubscribe.
195
+ * @category Subscription
196
+ * @since 0.0.5
197
+ * @example
198
+ *
199
+ ```typescript
200
+ * const siggn = new Siggn<{ type: 'my-event' }>();
201
+ * siggn.subscribe('sub-1', 'my-event', () => console.log('received'));
202
+ * siggn.unsubscribe('sub-1');
203
+ * siggn.publish({ type: 'my-event' }); // (nothing is logged)
204
+ * ```
205
+ */
206
+ unsubscribe(s) {
207
+ this.unsubscribeGlobal(s);
208
+ for (const [i, t] of this.subscriptions)
209
+ this.subscriptions.set(
210
+ i,
211
+ t.filter((r) => r.id !== s)
212
+ );
213
+ }
214
+ /**
215
+ * Removes a global subscription for a given subscriber ID.
216
+ *
217
+ * @param id The subscriber ID to unsubscribe.
218
+ * @category Subscription
219
+ * @since 0.0.5
220
+ * @example
221
+ *
222
+ ```typescript
223
+ * const siggn = new Siggn<{ type: 'my-event' }>();
224
+ * siggn.subscribeAll('logger', console.log);
225
+ * siggn.unsubscribeGlobal('logger');
226
+ * siggn.publish({ type: 'my-event' }); // (nothing is logged)
227
+ * ```
228
+ */
229
+ unsubscribeGlobal(s) {
230
+ this.globalSubscriptions = this.globalSubscriptions.filter((i) => i.id !== s);
231
+ }
232
+ /**
233
+ * Returns the total number of subscriptions (both specific and global).
234
+ *
235
+ * @category Subscription
236
+ * @since 0.1.0
237
+ * @example
238
+ *
239
+ ```typescript
240
+ * const siggn = new Siggn<{ type: 'my-event' }>();
241
+ * siggn.subscribe('sub-1', 'my-event', () => {});
242
+ * siggn.subscribeAll('logger', () => {});
243
+ * console.log(siggn.subscriptionsCount); // 2
244
+ * ```
245
+ */
246
+ get subscriptionsCount() {
247
+ let s = 0;
248
+ return this.subscriptions.forEach((i) => {
249
+ s += i.length;
250
+ }), s += this.globalSubscriptions.length, s;
251
+ }
252
+ }
253
+ export {
254
+ e as Siggn
255
+ };
256
+ //# sourceMappingURL=index.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.es.js","sources":["../src/siggn.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"],"names":["Siggn","id","type","callback","setup","msg","sub","fn","subscriptions","s","count","subs"],"mappings":"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"}
@@ -0,0 +1,211 @@
1
+ import { Msg, SiggnId } from './types.js';
2
+ /**
3
+ * A type-safe message bus for dispatching and subscribing to events.
4
+ * @template T A union of all possible message types.
5
+ * @since 0.0.5
6
+ */
7
+ export declare class Siggn<T extends Msg> {
8
+ private nextId;
9
+ private subscriptions;
10
+ private globalSubscriptions;
11
+ /**
12
+ * A FinalizationRegistry to automatically unregister specific subscriptions
13
+ * when the subscribed callback function is garbage collected.
14
+ */
15
+ private readonly registry;
16
+ /**
17
+ * A FinalizationRegistry to automatically unregister global subscriptions
18
+ * when the subscribed callback function is garbage collected.
19
+ */
20
+ private readonly registryGlobal;
21
+ /**
22
+ * Creates a new Siggn instance.
23
+ * @category Lifecycle
24
+ * @since 0.0.5
25
+ */
26
+ constructor();
27
+ /**
28
+ * Creates a new, independent `Siggn` instance that inherits the message
29
+ * types of its parent and adds new ones.
30
+ *
31
+ * @template C The new message types to add.
32
+ * @returns A new `Siggn` instance with combined message types.
33
+ * @category Lifecycle
34
+ * @since 0.0.5
35
+ * @example
36
+ *
37
+ ```typescript
38
+ * const baseSiggn = new Siggn<{ type: 'A' }>();
39
+ * const childSiggn = baseSiggn.createClone<{ type: 'B' }>();
40
+ * // childSiggn can now publish and subscribe to types 'A' and 'B'.
41
+ * ```
42
+ */
43
+ createClone<C extends Msg>(): Siggn<T | C>;
44
+ /**
45
+ * Generates a unique ID for a subscriber.
46
+ * If an ID is provided, it will be used; otherwise, a new one is generated.
47
+ *
48
+ * @param id An optional ID to use.
49
+ * @returns A unique subscriber ID.
50
+ * @category Utilities
51
+ * @since 0.0.5
52
+ * @example
53
+ *
54
+ ```typescript
55
+ * const siggn = new Siggn();
56
+ * const id1 = siggn.makeId(); // e.g., "sub_0"
57
+ * const id2 = siggn.makeId('custom-id'); // "custom-id"
58
+ * ```
59
+ */
60
+ makeId(id?: string): SiggnId;
61
+ /**
62
+ * Creates a subscription helper object that is pre-configured with a
63
+ * specific subscriber ID. This simplifies managing multiple subscriptions
64
+ * for a single component or service.
65
+ *
66
+ * @param id The subscriber ID to use for all subscriptions.
67
+ * @returns An object with `subscribe`, `unsubscribe`, `subscribeMany`, and `subscribeAll` methods.
68
+ * @category Subscription
69
+ * @since 0.0.5
70
+ * @example
71
+ *
72
+ ```typescript
73
+ * const siggn = new Siggn<{ type: 'event' }>();
74
+ * const component = siggn.make('my-component');
75
+ * component.subscribe('event', () => console.log('event received!'));
76
+ * component.unsubscribe();
77
+ * ```
78
+ */
79
+ make(id: SiggnId): {
80
+ subscribe: <K extends T['type']>(type: K, callback: (msg: Extract<T, {
81
+ type: K;
82
+ }>) => void) => void;
83
+ unsubscribe: () => void;
84
+ subscribeMany: (setup: (subscribe: <K extends T['type']>(type: K, callback: (msg: Extract<T, {
85
+ type: K;
86
+ }>) => void) => void) => void) => void;
87
+ subscribeAll: (callback: (msg: T) => void) => void;
88
+ };
89
+ /**
90
+ * Subscribes to multiple message types using a single subscriber ID.
91
+ *
92
+ * @param id The subscriber ID.
93
+ * @param setup A function that receives a `subscribe` helper to register callbacks.
94
+ * @category Subscription
95
+ * @since 0.0.5
96
+ * @example
97
+ *
98
+ ```typescript
99
+ * const siggn = new Siggn<{ type: 'A' } | { type: 'B' }>();
100
+ * siggn.subscribeMany('subscriber-1', (subscribe) => {
101
+ * subscribe('A', () => console.log('A received'));
102
+ * subscribe('B', () => console.log('B received'));
103
+ * });
104
+ * ```
105
+ */
106
+ subscribeMany(id: SiggnId, setup: (subscribe: <K extends T['type']>(type: K, callback: (msg: Extract<T, {
107
+ type: K;
108
+ }>) => void) => void) => void): void;
109
+ /**
110
+ * Subscribes to a specific message type.
111
+ *
112
+ * @param id The subscriber ID.
113
+ * @param type The message type to subscribe to.
114
+ * @param callback The function to call when a message of the specified type is published.
115
+ * @category Subscription
116
+ * @since 0.0.5
117
+ * @example
118
+ *
119
+ ```typescript
120
+ * const siggn = new Siggn<{ type: 'my-event', payload: string }>();
121
+ * siggn.subscribe('subscriber-1', 'my-event', (msg) => {
122
+ * console.log(msg.payload);
123
+ * });
124
+ * ```
125
+ */
126
+ subscribe<K extends T['type']>(id: SiggnId, type: K, callback: (msg: Extract<T, {
127
+ type: K;
128
+ }>) => void): void;
129
+ /**
130
+ * Subscribes to all message types. The callback will be invoked for every
131
+ * message published on the bus.
132
+ *
133
+ * @param id The subscriber ID.
134
+ * @param callback The function to call for any message.
135
+ * @category Subscription
136
+ * @since 0.0.5
137
+ * @example
138
+ *
139
+ ```typescript
140
+ * const siggn = new Siggn<{ type: 'A' } | { type: 'B' }>();
141
+ * siggn.subscribeAll('logger', (msg) => {
142
+ * console.log(`Received message of type: ${msg.type}`);
143
+ * });
144
+ * ```
145
+ */
146
+ subscribeAll(id: SiggnId, callback: (msg: T) => void): void;
147
+ /**
148
+ * Publishes a message to all relevant subscribers.
149
+ *
150
+ * @param msg The message to publish.
151
+ * @category Publishing
152
+ * @since 0.0.5
153
+ * @example
154
+ *
155
+ ```typescript
156
+ * const siggn = new Siggn<{ type: 'my-event' }>();
157
+ * siggn.subscribe('sub-1', 'my-event', () => console.log('received'));
158
+ * siggn.publish({ type: 'my-event' }); // "received"
159
+ * ```
160
+ */
161
+ publish(msg: T): void;
162
+ /**
163
+ * Removes all subscriptions (both specific and global) for a given
164
+ * subscriber ID.
165
+ *
166
+ * @param id The subscriber ID to unsubscribe.
167
+ * @category Subscription
168
+ * @since 0.0.5
169
+ * @example
170
+ *
171
+ ```typescript
172
+ * const siggn = new Siggn<{ type: 'my-event' }>();
173
+ * siggn.subscribe('sub-1', 'my-event', () => console.log('received'));
174
+ * siggn.unsubscribe('sub-1');
175
+ * siggn.publish({ type: 'my-event' }); // (nothing is logged)
176
+ * ```
177
+ */
178
+ unsubscribe(id: SiggnId): void;
179
+ /**
180
+ * Removes a global subscription for a given subscriber ID.
181
+ *
182
+ * @param id The subscriber ID to unsubscribe.
183
+ * @category Subscription
184
+ * @since 0.0.5
185
+ * @example
186
+ *
187
+ ```typescript
188
+ * const siggn = new Siggn<{ type: 'my-event' }>();
189
+ * siggn.subscribeAll('logger', console.log);
190
+ * siggn.unsubscribeGlobal('logger');
191
+ * siggn.publish({ type: 'my-event' }); // (nothing is logged)
192
+ * ```
193
+ */
194
+ unsubscribeGlobal(id: SiggnId): void;
195
+ /**
196
+ * Returns the total number of subscriptions (both specific and global).
197
+ *
198
+ * @category Subscription
199
+ * @since 0.1.0
200
+ * @example
201
+ *
202
+ ```typescript
203
+ * const siggn = new Siggn<{ type: 'my-event' }>();
204
+ * siggn.subscribe('sub-1', 'my-event', () => {});
205
+ * siggn.subscribeAll('logger', () => {});
206
+ * console.log(siggn.subscriptionsCount); // 2
207
+ * ```
208
+ */
209
+ get subscriptionsCount(): number;
210
+ }
211
+ //# sourceMappingURL=siggn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"siggn.d.ts","sourceRoot":"","sources":["../src/siggn.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAgB,OAAO,EAAE,MAAM,YAAY,CAAC;AAE7D;;;;GAIG;AACH,qBAAa,KAAK,CAAC,CAAC,SAAS,GAAG;IAC9B,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,aAAa,CAA8C;IACnE,OAAO,CAAC,mBAAmB,CAAmC;IAE9D;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAEtB;IAEH;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,cAAc,CAE5B;IAEH;;;;OAIG;;IAKH;;;;;;;;;;;;;;;OAeG;IACH,WAAW,CAAC,CAAC,SAAS,GAAG;IAIzB;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO;IAI5B;;;;;;;;;;;;;;;;;OAiBG;IACH,IAAI,CAAC,EAAE,EAAE,OAAO,GAAG;QACjB,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EAC7B,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE;YAAE,IAAI,EAAE,CAAC,CAAA;SAAE,CAAC,KAAK,IAAI,KAC7C,IAAI,CAAC;QACV,WAAW,EAAE,MAAM,IAAI,CAAC;QACxB,aAAa,EAAE,CACb,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;YAAE,IAAI,EAAE,CAAC,CAAA;SAAE,CAAC,KAAK,IAAI,KAC7C,IAAI,KACN,IAAI,KACN,IAAI,CAAC;QACV,YAAY,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,KAAK,IAAI,CAAC;KACpD;IAiBD;;;;;;;;;;;;;;;;OAgBG;IACH,aAAa,CACX,EAAE,EAAE,OAAO,EACX,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;QAAE,IAAI,EAAE,CAAC,CAAA;KAAE,CAAC,KAAK,IAAI,KAC7C,IAAI,KACN,IAAI;IAKX;;;;;;;;;;;;;;;;OAgBG;IACH,SAAS,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EAC3B,EAAE,EAAE,OAAO,EACX,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,CAAA;KAAE,CAAC,KAAK,IAAI;IAUlD;;;;;;;;;;;;;;;;OAgBG;IACH,YAAY,CAAC,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI;IAKpD;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,GAAG,EAAE,CAAC;IA4Bd;;;;;;;;;;;;;;;OAeG;IACH,WAAW,CAAC,EAAE,EAAE,OAAO;IAWvB;;;;;;;;;;;;;;OAcG;IACH,iBAAiB,CAAC,EAAE,EAAE,OAAO;IAI7B;;;;;;;;;;;;;OAaG;IACH,IAAI,kBAAkB,WAUrB;CACF"}
@@ -4,11 +4,12 @@ export type Msg = {
4
4
  };
5
5
  export type Subscription<T extends Msg, K extends T['type']> = {
6
6
  id: string;
7
- callback: (msg: Extract<T, {
7
+ ref: WeakRef<(msg: Extract<T, {
8
8
  type: K;
9
- }>) => void;
9
+ }>) => void>;
10
10
  };
11
11
  export type SubscriptionMap<T extends Msg> = {
12
12
  [K in T['type']]: Array<Subscription<T, K>>;
13
13
  };
14
+ export type SiggnId = string;
14
15
  //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,GAAG,GAAG;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,IAAI;IAC7D,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,OAAO,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,CAAA;KAAE,CAAC,KAAK,IAAI,CAAC,CAAC;CACtD,CAAC;AAEF,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,GAAG,IAAI;KAC1C,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC"}
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@siggn/core",
3
- "version": "0.0.4",
3
+ "version": "0.1.0",
4
4
  "description": "A lightweight message bus system for Typescript",
5
5
  "keywords": [
6
6
  "pub/sub",
7
7
  "message",
8
8
  "event",
9
- "bus"
9
+ "bus",
10
+ "typesafe"
10
11
  ],
11
12
  "publishConfig": {
12
13
  "@siggn:registry": "https://registry.npmjs.org/",
@@ -18,18 +19,26 @@
18
19
  },
19
20
  "repository": {
20
21
  "type": "git",
21
- "url": "git+https://github.com/Guiguerreiro39/siggn.git/",
22
+ "url": "https://github.com/Guiguerreiro39/siggn.git/",
22
23
  "directory": "packages/core"
23
24
  },
25
+ "main": "./dist/index.cjs.js",
26
+ "module": "./dist/index.es.js",
27
+ "types": "./dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.es.js",
32
+ "require": "./dist/index.cjs.js"
33
+ }
34
+ },
24
35
  "files": [
25
36
  "dist"
26
37
  ],
27
38
  "license": "MIT",
28
39
  "author": "Guilherme Guerreiro (https://guilhermegr.com)",
29
40
  "type": "module",
30
- "main": "dist/index.cjs.js",
31
- "types": "./dist/index.d.ts",
32
- "module": "./dist/index.es.js",
41
+ "sideEffects": false,
33
42
  "devDependencies": {
34
43
  "typescript": "^5.9.3",
35
44
  "vite": "^7.1.12",
@@ -40,13 +49,11 @@
40
49
  "typescript": "^5"
41
50
  },
42
51
  "scripts": {
43
- "dev": "vitest",
52
+ "dev": "vite build --watch",
53
+ "clean": "rm -rf dist",
44
54
  "test": "vitest run",
45
- "build": "vite build",
46
- "ci": "pnpm run build && pnpm run check-format && pnpm run test",
47
- "format": "prettier --write .",
48
- "check-format": "prettier --check .",
49
- "version": "changeset version",
50
- "release": "changeset publish"
55
+ "coverage": "vitest run --coverage",
56
+ "test:gc": "NODE_OPTIONS=\"--expose-gc\" pnpx vitest run",
57
+ "build": "vite build"
51
58
  }
52
59
  }
package/dist/index.cjs DELETED
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class r{subscriptions;subscribeAllSubscriptions=[];constructor(){this.subscriptions=new Map}createChild(){return new r}make(i){return{subscribe:(s,b)=>{this.subscribe(i,s,b)},unsubscribe:()=>{this.unsubscribe(i)},subscribeMany:s=>{this.subscribeMany(i,s)},subscribeAll:s=>{this.subscribeAll(i,s)}}}subscribeMany(i,s){s((b,t)=>this.subscribe(i,b,t))}subscribe(i,s,b){this.subscriptions.has(s)||this.subscriptions.set(s,[]),this.subscriptions.get(s)?.push({id:i,callback:b})}subscribeAll(i,s){this.subscribeAllSubscriptions.push({id:i,callback:s})}publish(i){this.subscribeAllSubscriptions.forEach(s=>{s.callback(i)}),this.subscriptions.has(i.type)&&this.subscriptions.get(i.type)?.forEach(s=>{s.callback(i)})}unsubscribe(i){this.subscribeAllSubscriptions=this.subscribeAllSubscriptions.filter(s=>s.id!==i);for(const[s,b]of this.subscriptions)this.subscriptions.set(s,b.filter(t=>t.id!==i))}}exports.Siggn=r;
package/dist/index.js DELETED
@@ -1,53 +0,0 @@
1
- class t {
2
- subscriptions;
3
- subscribeAllSubscriptions = [];
4
- constructor() {
5
- this.subscriptions = /* @__PURE__ */ new Map();
6
- }
7
- createChild() {
8
- return new t();
9
- }
10
- make(i) {
11
- return {
12
- subscribe: (s, b) => {
13
- this.subscribe(i, s, b);
14
- },
15
- unsubscribe: () => {
16
- this.unsubscribe(i);
17
- },
18
- subscribeMany: (s) => {
19
- this.subscribeMany(i, s);
20
- },
21
- subscribeAll: (s) => {
22
- this.subscribeAll(i, s);
23
- }
24
- };
25
- }
26
- subscribeMany(i, s) {
27
- s((b, r) => this.subscribe(i, b, r));
28
- }
29
- subscribe(i, s, b) {
30
- this.subscriptions.has(s) || this.subscriptions.set(s, []), this.subscriptions.get(s)?.push({ id: i, callback: b });
31
- }
32
- subscribeAll(i, s) {
33
- this.subscribeAllSubscriptions.push({ id: i, callback: s });
34
- }
35
- publish(i) {
36
- this.subscribeAllSubscriptions.forEach((s) => {
37
- s.callback(i);
38
- }), this.subscriptions.has(i.type) && this.subscriptions.get(i.type)?.forEach((s) => {
39
- s.callback(i);
40
- });
41
- }
42
- unsubscribe(i) {
43
- this.subscribeAllSubscriptions = this.subscribeAllSubscriptions.filter((s) => s.id !== i);
44
- for (const [s, b] of this.subscriptions)
45
- this.subscriptions.set(
46
- s,
47
- b.filter((r) => r.id !== i)
48
- );
49
- }
50
- }
51
- export {
52
- t as Siggn
53
- };
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
@@ -1,27 +0,0 @@
1
- import { Msg } from './types.js';
2
- export declare class Siggn<T extends Msg> {
3
- private subscriptions;
4
- private subscribeAllSubscriptions;
5
- constructor();
6
- createChild<C extends Msg>(): Siggn<T | C>;
7
- make(id: string): {
8
- subscribe: <K extends T['type']>(type: K, callback: (msg: Extract<T, {
9
- type: K;
10
- }>) => void) => void;
11
- unsubscribe: () => void;
12
- subscribeMany: (setup: (subscribe: <K extends T['type']>(type: K, callback: (msg: Extract<T, {
13
- type: K;
14
- }>) => void) => void) => void) => void;
15
- subscribeAll: (callback: (msg: T) => void) => void;
16
- };
17
- subscribeMany(id: string, setup: (subscribe: <K extends T['type']>(type: K, callback: (msg: Extract<T, {
18
- type: K;
19
- }>) => void) => void) => void): void;
20
- subscribe<K extends T['type']>(id: string, type: K, callback: (msg: Extract<T, {
21
- type: K;
22
- }>) => void): void;
23
- subscribeAll(id: string, callback: (msg: T) => void): void;
24
- publish(msg: T): void;
25
- unsubscribe(id: string): void;
26
- }
27
- //# sourceMappingURL=siggn.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"siggn.d.ts","sourceRoot":"","sources":["../../src/siggn.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAgB,MAAM,YAAY,CAAC;AAEpD,qBAAa,KAAK,CAAC,CAAC,SAAS,GAAG;IAC9B,OAAO,CAAC,aAAa,CAA8C;IACnE,OAAO,CAAC,yBAAyB,CAAmC;;IAMpE,WAAW,CAAC,CAAC,SAAS,GAAG;IAIzB,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG;QAChB,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EAC7B,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE;YAAE,IAAI,EAAE,CAAC,CAAA;SAAE,CAAC,KAAK,IAAI,KAC7C,IAAI,CAAC;QACV,WAAW,EAAE,MAAM,IAAI,CAAC;QACxB,aAAa,EAAE,CACb,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;YAAE,IAAI,EAAE,CAAC,CAAA;SAAE,CAAC,KAAK,IAAI,KAC7C,IAAI,KACN,IAAI,KACN,IAAI,CAAC;QACV,YAAY,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,KAAK,IAAI,CAAC;KACpD;IAiBD,aAAa,CACX,EAAE,EAAE,MAAM,EACV,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;QAAE,IAAI,EAAE,CAAC,CAAA;KAAE,CAAC,KAAK,IAAI,KAC7C,IAAI,KACN,IAAI;IAKX,SAAS,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EAC3B,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,CAAA;KAAE,CAAC,KAAK,IAAI;IASlD,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI;IAInD,OAAO,CAAC,GAAG,EAAE,CAAC;IAcd,WAAW,CAAC,EAAE,EAAE,MAAM;CAUvB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,GAAG,GAAG;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,IAAI;IAC7D,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,CAAA;KAAE,CAAC,KAAK,IAAI,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,GAAG,IAAI;KAC1C,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;CAC5C,CAAC"}
File without changes