chem-rx 0.0.18 → 0.0.19

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
@@ -245,6 +245,15 @@ signal$.ping("hello")
245
245
  subscription.unsubscribe();
246
246
  ```
247
247
 
248
+ You can selectively subscribe to Signals, and selectively ping subscribers by ID.
249
+
250
+ ```
251
+ const signal = new Signal<string>();
252
+ signal.subscribe(mockCallbackId123, "123");
253
+ signal.subscribe(mockCallbackId456, "456");
254
+ signal.ping("Message for 123", "123");
255
+ ```
256
+
248
257
  ## Use with React
249
258
 
250
259
  ### useAtom
@@ -287,23 +296,66 @@ function Counter() {
287
296
  <button onClick={() => count$.set('inner', count + 2)}>one up</button> ...
288
297
  ```
289
298
 
290
- ### useHydrateAtoms
299
+ ### hydrateAtoms
291
300
 
292
301
  With SSR, your atoms will likely need to be properly hydrated to prevent
293
302
  server/client mismatches. You can use `useHydrateAtoms` as a simple solution for
294
303
  seeding your client-side Atoms with the correct data.
295
304
 
305
+ NOTE: **`hydrateAtoms` caches values and only runs on the initial load by default**, to prevent re-hydration when client-side only changes are made to the component.
306
+
296
307
  ```
297
308
  import { Atom, useAtom, useHydrateAtoms } from 'chem-rx'
298
309
 
299
310
  const count$ = Atom(0)
300
311
  const CounterPage = ({ countFromServer }) => {
301
- useHydrateAtoms([[count$, countFromServer]])
312
+ hydrateAtoms([[count$, countFromServer]])
302
313
  const count = useAtom(count$)
303
314
  // count would be the value of `countFromServer`, not 0.
315
+
316
+ /*
317
+ * ... other code
318
+ */
319
+
320
+ useEffect(() => {
321
+ // This is safe, because hydrateAtoms runs once by default
322
+ count$.next(10)
323
+ }, [otherDeps])
324
+
304
325
  }
305
326
  ```
306
327
 
328
+ #### `force` hydrateAtoms
329
+
330
+ If you want to force re-hydration, you can optionally pass in a `{force: true}` (for example - you have a top level page wrapper that receives server data )
331
+
332
+ ```
333
+ export default function CasesPage({ cases }: Props) {
334
+ // force it to rehydrate with newest value every time this client page is loaded
335
+ hydrateAtoms([[cases$, cases]], { force: true });
336
+ const router = useRouter();
337
+ return (
338
+ <>
339
+ <div className="flex justify-between w-full mb-4 px-6 pt-8">
340
+ <h1 className="text-2xl">Cases</h1>
341
+ <Button
342
+ onClick={() => {
343
+ router.push("/cases/new");
344
+ }}
345
+ >
346
+ <PlusCircle className="w-4 h-4 mr-2" /> Add Cases
347
+ </Button>
348
+ </div>
349
+ <CasesTable />
350
+ </>
351
+ );
352
+ }
353
+ ```
354
+
355
+ In this example, `CasePage` is a top level client component rendering the home page from a SPA, which gets redirected to when coming from another page. We want it to render with the latest server-rendered data.
356
+
357
+ NOTE: `force` should only be used when the component is expected to only re-render when new data comes from the server - **and never anytime else**.
358
+
307
359
  ## Suggested Usage
308
360
 
309
361
  There are several suggested "patterns" when using Atoms:
package/dist/Signal.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { Subject } from "rxjs";
2
- export declare class BaseSignal<T = any> {
3
- _subject$: Subject<T>;
1
+ import { Subscription } from "rxjs";
2
+ export declare class Signal<T = any> {
3
+ private _subjects;
4
+ private _defaultSubject;
4
5
  constructor();
5
- ping(value: T): void;
6
- subscribe(...params: Parameters<Subject<T>["subscribe"]>): import("rxjs").Subscription;
6
+ ping(value: T, id?: string): void;
7
+ subscribe(callback: (value: T) => void, id?: string): Subscription;
7
8
  }
8
- export declare function Signal<T>(): void;
9
9
  //# sourceMappingURL=Signal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Signal.d.ts","sourceRoot":"","sources":["../src/Signal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAE/B,qBAAa,UAAU,CAAC,CAAC,GAAG,GAAG;IAC7B,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;;IAOtB,IAAI,CAAC,KAAK,EAAE,CAAC;IAIb,SAAS,CAAC,GAAG,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;CAGzD;AAED,wBAAgB,MAAM,CAAC,CAAC,UAAM"}
1
+ {"version":3,"file":"Signal.d.ts","sourceRoot":"","sources":["../src/Signal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,YAAY,EAAE,MAAM,MAAM,CAAC;AAE7C,qBAAa,MAAM,CAAC,CAAC,GAAG,GAAG;IACzB,OAAO,CAAC,SAAS,CAA0B;IAC3C,OAAO,CAAC,eAAe,CAAa;;IAOpC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM;IAY1B,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,YAAY;CAcnE"}
package/dist/index.cjs.js CHANGED
@@ -335,7 +335,43 @@ function hydrateAtoms(values, options) {
335
335
  }
336
336
  }
337
337
 
338
- function Signal() {}
338
+ var Signal = /*#__PURE__*/function () {
339
+ function Signal() {
340
+ this._subjects = new Map();
341
+ this._defaultSubject = new rxjs.Subject();
342
+ }
343
+ var _proto = Signal.prototype;
344
+ _proto.ping = function ping(value, id) {
345
+ if (id && this._subjects.has(id)) {
346
+ var _this$_subjects$get;
347
+ // If an ID is provided and exists, notify only that subscription.
348
+ (_this$_subjects$get = this._subjects.get(id)) == null ? void 0 : _this$_subjects$get.next(value);
349
+ } else {
350
+ // No ID provided, notify all default subscribers.
351
+ this._defaultSubject.next(value);
352
+ // Additionally, notify all subscriptions as a broadcast.
353
+ this._subjects.forEach(function (subject) {
354
+ return subject.next(value);
355
+ });
356
+ }
357
+ };
358
+ _proto.subscribe = function subscribe(callback, id) {
359
+ if (id) {
360
+ // If an ID is provided, subscribe to the specific ID.
361
+ if (!this._subjects.has(id)) {
362
+ this._subjects.set(id, new rxjs.Subject());
363
+ }
364
+ return this._subjects.get(id).subscribe(callback);
365
+ } else {
366
+ // No ID provided, subscribe to the default subject.
367
+ return this._defaultSubject.subscribe(callback);
368
+ }
369
+ }
370
+
371
+ // Optionally, implement unsubscribe logic to manage subscriptions.
372
+ ;
373
+ return Signal;
374
+ }();
339
375
 
340
376
  exports.Atom = Atom;
341
377
  exports.Signal = Signal;
@@ -333,7 +333,43 @@ var chemicalRx = (function (exports, rxjs, react) {
333
333
  }
334
334
  }
335
335
 
336
- function Signal() {}
336
+ var Signal = /*#__PURE__*/function () {
337
+ function Signal() {
338
+ this._subjects = new Map();
339
+ this._defaultSubject = new rxjs.Subject();
340
+ }
341
+ var _proto = Signal.prototype;
342
+ _proto.ping = function ping(value, id) {
343
+ if (id && this._subjects.has(id)) {
344
+ var _this$_subjects$get;
345
+ // If an ID is provided and exists, notify only that subscription.
346
+ (_this$_subjects$get = this._subjects.get(id)) == null ? void 0 : _this$_subjects$get.next(value);
347
+ } else {
348
+ // No ID provided, notify all default subscribers.
349
+ this._defaultSubject.next(value);
350
+ // Additionally, notify all subscriptions as a broadcast.
351
+ this._subjects.forEach(function (subject) {
352
+ return subject.next(value);
353
+ });
354
+ }
355
+ };
356
+ _proto.subscribe = function subscribe(callback, id) {
357
+ if (id) {
358
+ // If an ID is provided, subscribe to the specific ID.
359
+ if (!this._subjects.has(id)) {
360
+ this._subjects.set(id, new rxjs.Subject());
361
+ }
362
+ return this._subjects.get(id).subscribe(callback);
363
+ } else {
364
+ // No ID provided, subscribe to the default subject.
365
+ return this._defaultSubject.subscribe(callback);
366
+ }
367
+ }
368
+
369
+ // Optionally, implement unsubscribe logic to manage subscriptions.
370
+ ;
371
+ return Signal;
372
+ }();
337
373
 
338
374
  exports.Atom = Atom;
339
375
  exports.Signal = Signal;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { combineLatest, isObservable, BehaviorSubject, map, distinctUntilChanged } from 'rxjs';
1
+ import { combineLatest, isObservable, BehaviorSubject, map, distinctUntilChanged, Subject } from 'rxjs';
2
2
  import { useState, useEffect } from 'react';
3
3
 
4
4
  function _extends() {
@@ -248,6 +248,37 @@ function hydrateAtoms(values, options) {
248
248
  }
249
249
  }
250
250
 
251
- function Signal() {}
251
+ class Signal {
252
+ constructor() {
253
+ this._subjects = new Map();
254
+ this._defaultSubject = new Subject();
255
+ }
256
+ ping(value, id) {
257
+ if (id && this._subjects.has(id)) {
258
+ var _this$_subjects$get;
259
+ // If an ID is provided and exists, notify only that subscription.
260
+ (_this$_subjects$get = this._subjects.get(id)) == null ? void 0 : _this$_subjects$get.next(value);
261
+ } else {
262
+ // No ID provided, notify all default subscribers.
263
+ this._defaultSubject.next(value);
264
+ // Additionally, notify all subscriptions as a broadcast.
265
+ this._subjects.forEach(subject => subject.next(value));
266
+ }
267
+ }
268
+ subscribe(callback, id) {
269
+ if (id) {
270
+ // If an ID is provided, subscribe to the specific ID.
271
+ if (!this._subjects.has(id)) {
272
+ this._subjects.set(id, new Subject());
273
+ }
274
+ return this._subjects.get(id).subscribe(callback);
275
+ } else {
276
+ // No ID provided, subscribe to the default subject.
277
+ return this._defaultSubject.subscribe(callback);
278
+ }
279
+ }
280
+
281
+ // Optionally, implement unsubscribe logic to manage subscriptions.
282
+ }
252
283
 
253
284
  export { Atom, Signal, hydrateAtoms, useAtom, useSelectAtom };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chem-rx",
3
- "version": "0.0.18",
3
+ "version": "0.0.19",
4
4
  "description": "react state primitives powered by rx.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/Signal.ts CHANGED
@@ -1,20 +1,38 @@
1
- import { Subject } from "rxjs";
1
+ import { Subject, Subscription } from "rxjs";
2
2
 
3
- export class BaseSignal<T = any> {
4
- _subject$: Subject<T>;
3
+ export class Signal<T = any> {
4
+ private _subjects: Map<string, Subject<T>>;
5
+ private _defaultSubject: Subject<T>;
5
6
 
6
7
  constructor() {
7
- // if it's just a value just use a regular behavior subject
8
- this._subject$ = new Subject<T>();
8
+ this._subjects = new Map();
9
+ this._defaultSubject = new Subject<T>();
9
10
  }
10
11
 
11
- ping(value: T) {
12
- this._subject$.next(value);
12
+ ping(value: T, id?: string) {
13
+ if (id && this._subjects.has(id)) {
14
+ // If an ID is provided and exists, notify only that subscription.
15
+ this._subjects.get(id)?.next(value);
16
+ } else {
17
+ // No ID provided, notify all default subscribers.
18
+ this._defaultSubject.next(value);
19
+ // Additionally, notify all subscriptions as a broadcast.
20
+ this._subjects.forEach((subject) => subject.next(value));
21
+ }
13
22
  }
14
23
 
15
- subscribe(...params: Parameters<Subject<T>["subscribe"]>) {
16
- return this._subject$.subscribe(...params);
24
+ subscribe(callback: (value: T) => void, id?: string): Subscription {
25
+ if (id) {
26
+ // If an ID is provided, subscribe to the specific ID.
27
+ if (!this._subjects.has(id)) {
28
+ this._subjects.set(id, new Subject<T>());
29
+ }
30
+ return this._subjects.get(id)!.subscribe(callback);
31
+ } else {
32
+ // No ID provided, subscribe to the default subject.
33
+ return this._defaultSubject.subscribe(callback);
34
+ }
17
35
  }
18
- }
19
36
 
20
- export function Signal<T>() {}
37
+ // Optionally, implement unsubscribe logic to manage subscriptions.
38
+ }
@@ -1,4 +1,5 @@
1
1
  import { ArrayAtom, Atom, BaseAtom, ReadOnlyAtom } from "../src/Atom";
2
+ import { Signal } from "../src/Signal";
2
3
  import { BehaviorSubject, map } from "rxjs";
3
4
 
4
5
  test("Base Atom values test", () => {
@@ -540,6 +541,83 @@ test("Test select nullable object", () => {
540
541
  expect(stacySchool.get("graduation")).toBe(2014);
541
542
  });
542
543
 
544
+ describe("SignalWithId", () => {
545
+ it("should broadcast messages to all subscribers when no ID is provided", (done) => {
546
+ const signal = new Signal<string>();
547
+ const mockCallback = jest.fn();
548
+
549
+ signal.subscribe(mockCallback);
550
+ signal.subscribe(mockCallback);
551
+
552
+ signal.ping("test message");
553
+
554
+ setImmediate(() => {
555
+ expect(mockCallback).toHaveBeenCalledTimes(2);
556
+ expect(mockCallback).toHaveBeenCalledWith("test message");
557
+ done();
558
+ });
559
+ });
560
+
561
+ it("should send messages only to subscribers with the specific ID", (done) => {
562
+ const signal = new Signal<string>();
563
+ const mockCallbackWithId = jest.fn();
564
+ const mockCallbackWithoutId = jest.fn();
565
+
566
+ signal.subscribe(mockCallbackWithId, "123");
567
+ signal.subscribe(mockCallbackWithoutId);
568
+
569
+ signal.ping("ID specific message", "123");
570
+
571
+ setImmediate(() => {
572
+ expect(mockCallbackWithId).toHaveBeenCalledTimes(1);
573
+ expect(mockCallbackWithId).toHaveBeenCalledWith("ID specific message");
574
+ expect(mockCallbackWithoutId).toHaveBeenCalledTimes(0);
575
+ done();
576
+ });
577
+ });
578
+
579
+ it("should not notify subscribers with different IDs", (done) => {
580
+ const signal = new Signal<string>();
581
+ const mockCallbackId123 = jest.fn();
582
+ const mockCallbackId456 = jest.fn();
583
+
584
+ signal.subscribe(mockCallbackId123, "123");
585
+ signal.subscribe(mockCallbackId456, "456");
586
+
587
+ signal.ping("Message for 123", "123");
588
+
589
+ setImmediate(() => {
590
+ expect(mockCallbackId123).toHaveBeenCalledTimes(1);
591
+ expect(mockCallbackId123).toHaveBeenCalledWith("Message for 123");
592
+ expect(mockCallbackId456).toHaveBeenCalledTimes(0);
593
+ done();
594
+ });
595
+ });
596
+
597
+ it("should notify all subscribers, including those with specific IDs, when pinging without an ID", (done) => {
598
+ const signal = new Signal<string>();
599
+ const mockCallback = jest.fn();
600
+ const mockCallbackWithId = jest.fn();
601
+
602
+ // Subscribe one callback without ID (for general broadcast)
603
+ signal.subscribe(mockCallback);
604
+ // Subscribe another callback with a specific ID
605
+ signal.subscribe(mockCallbackWithId, "123");
606
+
607
+ // Ping without specifying an ID should notify both subscribers
608
+ signal.ping("broadcast message");
609
+
610
+ setImmediate(() => {
611
+ // Both callbacks should be called once
612
+ expect(mockCallback).toHaveBeenCalledTimes(1);
613
+ expect(mockCallback).toHaveBeenCalledWith("broadcast message");
614
+ expect(mockCallbackWithId).toHaveBeenCalledTimes(1);
615
+ expect(mockCallbackWithId).toHaveBeenCalledWith("broadcast message");
616
+ done();
617
+ });
618
+ });
619
+ });
620
+
543
621
  /*
544
622
  * TODO:
545
623
  * - test react hooks