@fozy-labs/rx-toolkit 0.4.1 → 0.4.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.
Files changed (81) hide show
  1. package/README.md +41 -35
  2. package/dist/devtools/combineDevtools.d.ts +2 -0
  3. package/dist/devtools/combineDevtools.js +10 -0
  4. package/dist/devtools/index.d.ts +2 -0
  5. package/dist/devtools/index.js +2 -0
  6. package/dist/devtools/reduxDevtools.d.ts +2 -0
  7. package/dist/devtools/reduxDevtools.js +24 -0
  8. package/dist/index.d.ts +2 -1
  9. package/dist/index.js +2 -1
  10. package/dist/query/api/DefaultOptions.d.ts +2 -2
  11. package/dist/query/core/Opertation/Operation.js +5 -4
  12. package/dist/query/core/Opertation/OperationAgent.d.ts +1 -1
  13. package/dist/query/core/Opertation/OperationAgent.js +3 -3
  14. package/dist/query/core/QueriesCache.js +11 -5
  15. package/dist/query/core/Resource/Resource.d.ts +14 -2
  16. package/dist/query/core/Resource/Resource.js +34 -2
  17. package/dist/query/core/Resource/ResourceAgent.d.ts +1 -1
  18. package/dist/query/core/Resource/ResourceAgent.js +3 -3
  19. package/dist/query/core/Resource/ResourceRef.d.ts +2 -1
  20. package/dist/query/core/Resource/ResourceRef.js +96 -2
  21. package/dist/query/core/SharedOptions.d.ts +2 -2
  22. package/dist/query/index.d.ts +1 -1
  23. package/dist/query/index.js +1 -1
  24. package/dist/query/lib/ReactiveCache.js +2 -2
  25. package/dist/query/react/useResourceRef.d.ts +6 -0
  26. package/dist/query/react/useResourceRef.js +8 -0
  27. package/dist/query/types/Operation.types.d.ts +43 -2
  28. package/dist/query/types/Resource.types.d.ts +16 -1
  29. package/dist/query/types/devtools.d.ts +6 -0
  30. package/dist/react-hooks/useSignal.d.ts +1 -1
  31. package/dist/signals/base/Batcher.d.ts +6 -0
  32. package/dist/signals/base/Batcher.js +55 -0
  33. package/dist/{signal → signals}/base/Computed.d.ts +5 -2
  34. package/dist/{signal → signals}/base/Computed.js +7 -3
  35. package/dist/{signal → signals}/base/Effect.d.ts +3 -3
  36. package/dist/signals/base/Effect.js +51 -0
  37. package/dist/signals/base/Indexer.d.ts +4 -0
  38. package/dist/signals/base/Indexer.js +6 -0
  39. package/dist/{signal → signals}/base/ReadonlySignal.d.ts +2 -0
  40. package/dist/signals/base/ReadonlySignal.js +30 -0
  41. package/dist/{signal → signals}/base/Signal.d.ts +12 -1
  42. package/dist/{signal → signals}/base/Signal.js +23 -3
  43. package/dist/signals/base/Tracker.d.ts +14 -0
  44. package/dist/signals/base/Tracker.js +13 -0
  45. package/dist/{signal → signals}/base/types.d.ts +0 -1
  46. package/dist/signals/base/types.js +1 -0
  47. package/dist/{signal → signals}/extends/LocalSignal.d.ts +1 -1
  48. package/dist/{signal → signals}/extends/LocalSignal.js +3 -3
  49. package/dist/{signal → signals}/operators/filterUpdates.js +1 -1
  50. package/dist/signals/operators/index.d.ts +3 -0
  51. package/dist/signals/operators/index.js +3 -0
  52. package/dist/{signal → signals}/operators/mapSignals.js +1 -1
  53. package/dist/{signal → signals}/operators/signalize.d.ts +1 -1
  54. package/dist/{signal → signals}/operators/signalize.js +1 -1
  55. package/docs/devtools/README.md +95 -0
  56. package/docs/query/README.md +25 -14
  57. package/docs/signals/README.md +10 -7
  58. package/package.json +13 -8
  59. package/dist/query/api/createDevtools.d.ts +0 -1
  60. package/dist/query/api/createDevtools.js +0 -6
  61. package/dist/signal/base/Batcher.d.ts +0 -7
  62. package/dist/signal/base/Batcher.js +0 -21
  63. package/dist/signal/base/Effect.js +0 -69
  64. package/dist/signal/base/ReadonlySignal.js +0 -20
  65. package/dist/signal/base/Tracker.d.ts +0 -5
  66. package/dist/signal/base/Tracker.js +0 -7
  67. package/dist/signal/operators/batch.d.ts +0 -2
  68. package/dist/signal/operators/batch.js +0 -27
  69. package/dist/signal/operators/index.d.ts +0 -4
  70. package/dist/signal/operators/index.js +0 -4
  71. /package/dist/{signal/base/types.js → query/types/devtools.js} +0 -0
  72. /package/dist/{signal → signals}/base/SyncObservable.d.ts +0 -0
  73. /package/dist/{signal → signals}/base/SyncObservable.js +0 -0
  74. /package/dist/{signal → signals}/base/index.d.ts +0 -0
  75. /package/dist/{signal → signals}/base/index.js +0 -0
  76. /package/dist/{signal → signals}/extends/index.d.ts +0 -0
  77. /package/dist/{signal → signals}/extends/index.js +0 -0
  78. /package/dist/{signal → signals}/index.d.ts +0 -0
  79. /package/dist/{signal → signals}/index.js +0 -0
  80. /package/dist/{signal → signals}/operators/filterUpdates.d.ts +0 -0
  81. /package/dist/{signal → signals}/operators/mapSignals.d.ts +0 -0
@@ -1,4 +1,4 @@
1
- import { ReadableSignalLike } from "../../signal";
1
+ import { ReadableSignalLike } from "../../signals";
2
2
  import { FallbackOnNever } from "../../query/types/shared.types";
3
3
  import { ResourceDefinition, ResourceInstance } from "./Resource.types";
4
4
  /**
@@ -20,21 +20,62 @@ export type OperationCreateOptions<D extends OperationDefinition> = {
20
20
  * Настройки связи операции с ресурсом
21
21
  */
22
22
  export type LinkOptions<D extends OperationDefinition, RD extends ResourceDefinition> = {
23
+ /**
24
+ * Целевой ресурс, с которым связывается операция
25
+ * @required
26
+ */
23
27
  resource: ResourceInstance<RD>;
28
+ /**
29
+ * Функция для получения аргументов ресурса из аргументов операции.
30
+ * Используется для определения какой именно элемент в кэше ресурса нужно обновить
31
+ * @required
32
+ */
24
33
  forwardArgs: (args: D["Args"]) => RD["Args"];
34
+ /**
35
+ * Флаг для инвалидации (очистки) кэша ресурса после выполнения операции.
36
+ * При true - кэш будет очищен и ресурс будет перезагружен при следующем обращении
37
+ * @optional @default false
38
+ */
25
39
  invalidate?: boolean;
40
+ /**
41
+ * Флаг для блокировки ресурса во время выполнения операции.
42
+ * При true - ресурс будет заблокирован и не сможет выполнять новые запросы
43
+ * @optional @default false
44
+ */
26
45
  lock?: boolean;
46
+ /**
47
+ * Функция для обновления кэша ресурса после успешного выполнения операции.
48
+ * Получает draft объект для мутации, аргументы операции и результат операции
49
+ * @optional
50
+ */
27
51
  update?: (tools: {
52
+ /** Immer draft объект для мутации кэша ресурса */
28
53
  draft: RD["Data"];
54
+ /** Аргументы, с которыми была вызвана операция */
29
55
  args: D["Args"];
56
+ /** Результат выполнения операции */
30
57
  data: D["Data"];
31
58
  }) => void | RD["Data"] | Promise<RD["Data"]>;
59
+ /**
60
+ * Функция для оптимистичного обновления кэша ресурса ДО выполнения операции.
61
+ * Позволяет обновить UI немедленно, до получения ответа от сервера
62
+ * @optional
63
+ */
32
64
  optimisticUpdate?: (tools: {
65
+ /** Immer draft объект для мутации кэша ресурса */
33
66
  draft: RD["Data"];
67
+ /** Аргументы, с которыми была вызвана операция */
34
68
  args: D["Args"];
35
- }) => void | RD["Data"] | Promise<D["Data"]>;
69
+ }) => void | RD["Data"] | Promise<RD["Data"]>;
70
+ /**
71
+ * Функция для создания нового элемента в кэше ресурса.
72
+ * Используется когда операция создает новую сущность, которую нужно добавить в кэш
73
+ * @optional
74
+ */
36
75
  create?: (tools: {
76
+ /** Аргументы, с которыми была вызвана операция */
37
77
  args: D["Args"];
78
+ /** Результат выполнения операции */
38
79
  data: D["Data"];
39
80
  }) => RD["Data"] | Promise<RD["Data"]>;
40
81
  };
@@ -1,5 +1,6 @@
1
- import { ReadableSignalLike } from "../../signal";
1
+ import { ReadableSignalLike } from "../../signals";
2
2
  import { FallbackOnNever } from "../../query/types/shared.types";
3
+ import { Patch as ImmerPatch } from "immer";
3
4
  /**
4
5
  * Функция создания ресурса
5
6
  */
@@ -72,6 +73,16 @@ export type ResourceQueryState<D extends ResourceDefinition> = {
72
73
  /** Аргументы запроса */
73
74
  args: D["Args"] | undefined;
74
75
  };
76
+ /**
77
+ * Транзакция ресурса
78
+ */
79
+ export type ResourceTransaction = {
80
+ patches: ImmerPatch[];
81
+ inversePatches: ImmerPatch[];
82
+ status: 'pending' | 'committed' | 'aborted';
83
+ abort(): void;
84
+ commit(): void;
85
+ };
75
86
  /**
76
87
  * Эте не ссылка в "классическом" понимании, а абстракция
77
88
  * для работы с элементом кеша ресурса.
@@ -82,9 +93,13 @@ export type ResourceRefInstanse<D extends ResourceDefinition> = {
82
93
  unlock: () => void;
83
94
  };
84
95
  unlockOne(): void;
96
+ /**
97
+ * @deprecated
98
+ */
85
99
  update(updateFn: (data: D['Data']) => D['Data']): {
86
100
  rollback: () => void;
87
101
  };
102
+ patch(patchFn: (data: D['Data']) => void): ResourceTransaction | null;
88
103
  invalidate(): void;
89
104
  create(data: D['Data']): void;
90
105
  };
@@ -0,0 +1,6 @@
1
+ export interface DevtoolsStateLike<T = any> {
2
+ (newState: T): void;
3
+ }
4
+ export interface DevtoolsLike {
5
+ state<T>(name: string, initState: T): DevtoolsStateLike<T>;
6
+ }
@@ -1,2 +1,2 @@
1
- import type { ReadableSignalLike } from "../signal/base/types";
1
+ import { ReadableSignalLike } from "../signals";
2
2
  export declare function useSignal<T>(signal$: ReadableSignalLike<T>): T;
@@ -0,0 +1,6 @@
1
+ export declare const Batcher: {
2
+ scheduler(rang: number): {
3
+ schedule: (fn: () => void) => void;
4
+ };
5
+ batch<T>(fn: () => T): T;
6
+ };
@@ -0,0 +1,55 @@
1
+ const Scheduled = {
2
+ map: new Map(),
3
+ lowestRang: -1,
4
+ isLocked: false,
5
+ set(rang, fn) {
6
+ if (rang < this.lowestRang)
7
+ this.lowestRang = rang;
8
+ if (!this.map.has(rang)) {
9
+ this.map.set(rang, new Set());
10
+ }
11
+ this.map.get(rang).add(fn);
12
+ },
13
+ done() {
14
+ this.lowestRang = -1;
15
+ this.map.clear();
16
+ },
17
+ handleInfinity() {
18
+ const fns = this.map.get(Infinity);
19
+ this.map.delete(Infinity);
20
+ fns?.forEach((fn) => fn());
21
+ this.done();
22
+ },
23
+ run() {
24
+ if (this.map.size === 1 && this.map.has(Infinity))
25
+ return this.handleInfinity();
26
+ if (this.map.size === 0)
27
+ return this.done();
28
+ const iterationRang = this.lowestRang;
29
+ this.lowestRang += 1;
30
+ const fns = this.map.get(iterationRang);
31
+ this.map.delete(iterationRang);
32
+ fns?.forEach((fn) => fn());
33
+ this.run();
34
+ },
35
+ };
36
+ export const Batcher = {
37
+ scheduler(rang) {
38
+ return {
39
+ schedule: (fn) => {
40
+ if (!Scheduled.isLocked)
41
+ return fn();
42
+ Scheduled.set(rang, fn);
43
+ }
44
+ };
45
+ },
46
+ batch(fn) {
47
+ if (Scheduled.isLocked)
48
+ return fn();
49
+ Scheduled.isLocked = true;
50
+ const v = fn();
51
+ Scheduled.run();
52
+ Scheduled.isLocked = false;
53
+ return v;
54
+ },
55
+ };
@@ -1,10 +1,13 @@
1
1
  import { SubscriptionLike } from "rxjs";
2
- import { ReadableSignalLike } from "../../signal/base/types";
2
+ import { ReadableSignalLike } from "./types";
3
3
  import { Signal } from "./Signal";
4
4
  export declare class Computed<T> extends Signal<T> implements SubscriptionLike, ReadableSignalLike<T> {
5
5
  private static _EMPTY;
6
6
  private _effect;
7
- constructor(computeFn: () => T, doLog?: boolean);
7
+ constructor(computeFn: () => T, options?: {
8
+ disableDevtools?: boolean;
9
+ devtoolsName?: string;
10
+ });
8
11
  unsubscribe(): void;
9
12
  complete(): void;
10
13
  }
@@ -3,16 +3,20 @@ import { Effect } from "./Effect";
3
3
  export class Computed extends Signal {
4
4
  static _EMPTY = Symbol('empty');
5
5
  _effect;
6
- constructor(computeFn, doLog = false) {
6
+ constructor(computeFn, options) {
7
7
  let initialValue = Computed._EMPTY;
8
8
  const effect = new Effect(() => {
9
9
  if (initialValue === Computed._EMPTY) {
10
10
  initialValue = computeFn();
11
11
  return;
12
12
  }
13
+ this._rang = effect._rang;
13
14
  this.value = computeFn();
14
- }, doLog);
15
- super(initialValue);
15
+ });
16
+ super(initialValue, {
17
+ devtoolsName: 'Computed',
18
+ ...options,
19
+ });
16
20
  this._effect = effect;
17
21
  }
18
22
  unsubscribe() {
@@ -1,9 +1,9 @@
1
1
  import { SubscriptionLike } from "rxjs";
2
2
  export declare class Effect implements SubscriptionLike {
3
- private _doLog;
4
- closed: boolean;
5
3
  private _subscriptions;
6
- constructor(effectFn: (runInTrackedContext: (fn: () => void) => void) => void, _doLog?: boolean);
4
+ closed: boolean;
5
+ _rang: number;
6
+ constructor(effectFn: (ctx: (fn: () => void) => void) => void);
7
7
  /**
8
8
  * Выполняет функцию в tracked-контексте, подписываясь на Tracker.
9
9
  */
@@ -0,0 +1,51 @@
1
+ import { Batcher } from "./Batcher";
2
+ import { Tracker } from "./Tracker";
3
+ export class Effect {
4
+ _subscriptions = [];
5
+ closed = false;
6
+ _rang = 0;
7
+ constructor(effectFn) {
8
+ this._runInTrackedContext(effectFn, false);
9
+ }
10
+ /**
11
+ * Выполняет функцию в tracked-контексте, подписываясь на Tracker.
12
+ */
13
+ _runInTrackedContext(effectFn, isAsyncRun = false) {
14
+ // Отписываемся от предыдущих подписок
15
+ if (!isAsyncRun) {
16
+ this._rang = 0;
17
+ this._subscriptions.forEach((sub) => sub.unsubscribe());
18
+ this._subscriptions = [];
19
+ }
20
+ let isTrackedContext = true;
21
+ // TODO подумать как организовать планировщик при асинхронном запуске
22
+ let scheduler;
23
+ const scheduledFn = () => {
24
+ this._runInTrackedContext(effectFn);
25
+ };
26
+ // Подписываемся на Tracker. Во время выполнения подпишемся на все tracked наблюдатели.
27
+ const trackerSub = Tracker.tracked$.subscribe((tracked) => {
28
+ if (!isTrackedContext)
29
+ return;
30
+ if (tracked.rang <= this._rang) {
31
+ this._rang = tracked.rang + 1;
32
+ }
33
+ this._subscriptions.push(tracked.obsv$.subscribe(() => {
34
+ if (isTrackedContext) {
35
+ return;
36
+ }
37
+ scheduler.schedule(scheduledFn);
38
+ }));
39
+ });
40
+ effectFn((fn) => {
41
+ this._runInTrackedContext(fn, true);
42
+ });
43
+ trackerSub.unsubscribe();
44
+ isTrackedContext = false;
45
+ scheduler = Batcher.scheduler(this._rang);
46
+ }
47
+ unsubscribe() {
48
+ this.closed = true;
49
+ this._subscriptions.forEach((sub) => sub.unsubscribe());
50
+ }
51
+ }
@@ -0,0 +1,4 @@
1
+ export declare const Indexer: {
2
+ currentIndex: number;
3
+ getIndex(): number;
4
+ };
@@ -0,0 +1,6 @@
1
+ export const Indexer = {
2
+ currentIndex: 0,
3
+ getIndex() {
4
+ return this.currentIndex++;
5
+ }
6
+ };
@@ -2,6 +2,8 @@ import { Observable, Subscriber, TeardownLogic } from "rxjs";
2
2
  import { SyncObservable } from "./SyncObservable";
3
3
  import type { ReadableSignalLike } from "./types";
4
4
  export declare class ReadonlySignal<T> extends SyncObservable<T> implements ReadableSignalLike<T> {
5
+ private readonly _devtools;
6
+ private static _logIdIndex;
5
7
  constructor(subscribe?: (this: Observable<T>, subscriber: Subscriber<T>) => TeardownLogic);
6
8
  get value(): T;
7
9
  peek(): T;
@@ -0,0 +1,30 @@
1
+ import { SharedOptions } from "../../query/core/SharedOptions";
2
+ import { SyncObservable } from "./SyncObservable";
3
+ import { Tracker } from "./Tracker";
4
+ export class ReadonlySignal extends SyncObservable {
5
+ _devtools;
6
+ static _logIdIndex = 0;
7
+ constructor(subscribe) {
8
+ super(subscribe);
9
+ const stateDevtools = SharedOptions.DEVTOOLS?.state;
10
+ if (stateDevtools) {
11
+ const id = ReadonlySignal._logIdIndex++;
12
+ const key = `ReadonlySignal:i=${id}`;
13
+ const initialValue = this.peek();
14
+ this._devtools = stateDevtools(key, initialValue);
15
+ }
16
+ }
17
+ get value() {
18
+ Tracker.next_legacy(this);
19
+ return super.value;
20
+ }
21
+ peek() {
22
+ return super.value;
23
+ }
24
+ /**
25
+ * @deprecated
26
+ */
27
+ get() {
28
+ return this.value;
29
+ }
30
+ }
@@ -1,7 +1,13 @@
1
1
  import { BehaviorSubject, Observable } from "rxjs";
2
2
  import { type ReadableSignalLike, UnaryFunction } from "./types";
3
+ type SignalOptions = {
4
+ disableDevtools?: boolean;
5
+ devtoolsName?: string;
6
+ };
3
7
  export declare class Signal<T> extends BehaviorSubject<T> implements ReadableSignalLike<T> {
4
- constructor(initialValue: T);
8
+ private readonly _devtools;
9
+ protected _rang: number;
10
+ constructor(initialValue: T, options?: SignalOptions);
5
11
  protected _onChange(value: T): void;
6
12
  get value(): T;
7
13
  set value(value: T);
@@ -11,6 +17,10 @@ export declare class Signal<T> extends BehaviorSubject<T> implements ReadableSig
11
17
  * @deprecated use `value` instead.
12
18
  */
13
19
  get(): T;
20
+ /**
21
+ * @deprecated use `peek()` instead.
22
+ */
23
+ getValue(): T;
14
24
  /**
15
25
  * @deprecated use `next(value)` instead.
16
26
  */
@@ -28,3 +38,4 @@ export declare class Signal<T> extends BehaviorSubject<T> implements ReadableSig
28
38
  pipe<A extends Observable<any>, B extends Observable<any>, C extends Observable<any>, D extends Observable<any>, E extends Observable<any>, F extends Observable<any>, G extends Observable<any>, H extends Observable<any>, I extends Observable<any>>(op1: UnaryFunction<Signal<T>, A>, op2: UnaryFunction<A, B>, op3: UnaryFunction<B, C>, op4: UnaryFunction<C, D>, op5: UnaryFunction<D, E>, op6: UnaryFunction<E, F>, op7: UnaryFunction<F, G>, op8: UnaryFunction<G, H>, op9: UnaryFunction<H, I>, ...operations: UnaryFunction<Observable<any>, Observable<any>>[]): Observable<unknown>;
29
39
  asReadonly(): ReadableSignalLike<T>;
30
40
  }
41
+ export {};
@@ -1,14 +1,28 @@
1
+ import { SharedOptions } from "../../query/core/SharedOptions";
1
2
  import { BehaviorSubject } from "rxjs";
3
+ import { Batcher } from "./Batcher";
4
+ import { Indexer } from "./Indexer";
2
5
  import { Tracker } from "./Tracker";
3
6
  export class Signal extends BehaviorSubject {
4
- constructor(initialValue) {
7
+ _devtools;
8
+ _rang = 0;
9
+ constructor(initialValue, options) {
5
10
  super(initialValue);
11
+ const stateDevtools = SharedOptions.DEVTOOLS?.state;
12
+ if (stateDevtools && options?.disableDevtools !== true) {
13
+ const id = Indexer.getIndex();
14
+ const key = `${options?.devtoolsName || 'Signal'}:i=${id}`;
15
+ this._devtools = stateDevtools(key, initialValue);
16
+ }
6
17
  }
7
18
  _onChange(value) {
8
- super.next(value);
19
+ Batcher.batch(() => {
20
+ this._devtools?.(value);
21
+ super.next(value);
22
+ });
9
23
  }
10
24
  get value() {
11
- Tracker.next(this);
25
+ Tracker.next(this._rang, this);
12
26
  return super.value;
13
27
  }
14
28
  set value(value) {
@@ -26,6 +40,12 @@ export class Signal extends BehaviorSubject {
26
40
  get() {
27
41
  return this.value;
28
42
  }
43
+ /**
44
+ * @deprecated use `peek()` instead.
45
+ */
46
+ getValue() {
47
+ return super.getValue();
48
+ }
29
49
  /**
30
50
  * @deprecated use `next(value)` instead.
31
51
  */
@@ -0,0 +1,14 @@
1
+ import { type Observable, Subject } from "rxjs";
2
+ type TrackedValue = {
3
+ rang: number;
4
+ obsv$: Observable<unknown>;
5
+ };
6
+ export declare const Tracker: {
7
+ /** @deprecated */
8
+ tracked_legacy$: Subject<Observable<unknown>>;
9
+ /** @deprecated */
10
+ next_legacy(value: Observable<unknown>): void;
11
+ tracked$: Subject<TrackedValue>;
12
+ next(rang: number, observable: Observable<unknown>): void;
13
+ };
14
+ export {};
@@ -0,0 +1,13 @@
1
+ import { Subject } from "rxjs";
2
+ export const Tracker = {
3
+ /** @deprecated */
4
+ tracked_legacy$: new Subject(),
5
+ /** @deprecated */
6
+ next_legacy(value) {
7
+ Tracker.tracked_legacy$.next(value);
8
+ },
9
+ tracked$: new Subject(),
10
+ next(rang, observable) {
11
+ Tracker.tracked$.next({ rang, obsv$: observable, });
12
+ }
13
+ };
@@ -12,4 +12,3 @@ export interface UnaryFunction<T, R> {
12
12
  }
13
13
  export type SignalOperatorFn<T, R> = UnaryFunction<ReadableSignalLike<T>, ReadableSignalLike<R>>;
14
14
  export type MonoTypeSignalOperatorFn<T> = SignalOperatorFn<T, T>;
15
- export type ObservableToSignalFn<T> = UnaryFunction<Observable<T>, ReadableSignalLike<T>>;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,6 @@
1
1
  import { ZodType } from "zod/v4";
2
2
  import { Observable } from "rxjs";
3
- import { Computed } from "../../signal/base";
3
+ import { Computed } from "../base";
4
4
  type Options<T> = {
5
5
  zodSchema?: ZodType<T>;
6
6
  key: string;
@@ -1,6 +1,6 @@
1
- import { signalize } from "../../signal/operators";
2
1
  import { z } from "zod/v4";
3
- import { Computed } from "../../signal/base";
2
+ import { Computed } from "../base";
3
+ import { signalize } from "../operators";
4
4
  const NullOrString = z.string().nullable();
5
5
  const NONE = Symbol('NONE');
6
6
  export class LocalSignal extends Computed {
@@ -56,7 +56,7 @@ export class LocalSignal extends Computed {
56
56
  return checkEffect(value) ? value : defaultValue;
57
57
  }
58
58
  return value;
59
- });
59
+ }, { devtoolsName: 'LocalSignal' });
60
60
  this._options = _options;
61
61
  }
62
62
  _onChange(value) {
@@ -1,5 +1,5 @@
1
1
  import { createOperatorSubscriber } from "rxjs/internal/operators/OperatorSubscriber";
2
- import { ReadonlySignal } from "../../signal/base/ReadonlySignal";
2
+ import { ReadonlySignal } from "../base";
3
3
  export function filterUpdates(predicate, thisArg) {
4
4
  return (source) => new ReadonlySignal((destination) => {
5
5
  let index = 0;
@@ -0,0 +1,3 @@
1
+ export * from './filterUpdates';
2
+ export * from './mapSignals';
3
+ export * from './signalize';
@@ -0,0 +1,3 @@
1
+ export * from './filterUpdates';
2
+ export * from './mapSignals';
3
+ export * from './signalize';
@@ -1,5 +1,5 @@
1
1
  import { createOperatorSubscriber } from "rxjs/internal/operators/OperatorSubscriber";
2
- import { ReadonlySignal } from "../../signal/base/ReadonlySignal";
2
+ import { ReadonlySignal } from "../base";
3
3
  export function mapSignals(project, thisArg) {
4
4
  return (source) => new ReadonlySignal((destination) => {
5
5
  let index = 0;
@@ -1,3 +1,3 @@
1
1
  import { Observable } from "rxjs";
2
- import { ReadonlySignal } from "../../signal/base";
2
+ import { ReadonlySignal } from "../base";
3
3
  export declare function signalize<T>(observable: Observable<T>): ReadonlySignal<T>;
@@ -1,4 +1,4 @@
1
- import { ReadonlySignal } from "../../signal/base";
1
+ import { ReadonlySignal } from "../base";
2
2
  export function signalize(observable) {
3
3
  return new ReadonlySignal((destination) => {
4
4
  return observable.subscribe(destination);
@@ -0,0 +1,95 @@
1
+ # Devtools
2
+
3
+ RxToolkit предоставляет интеграцию с популярными инструментами разработчика для отладки реактивных приложений в реальном времени. Вы можете отслеживать изменения сигналов, выполнение операций и состояние ресурсов, анализировать граф зависимостей и исследовать поведение реактивной системы.
4
+
5
+ **Отслеживает изменения состояния:**
6
+ - Сигналов (Signal/Computed)
7
+ - Ресурсов и операций (Resource/Operation)
8
+
9
+ ## Redux DevTools
10
+
11
+ Популярное браузерное расширение для отладки состояния приложений. **RxToolkit включает встроенный адаптер `reduxDevtools()`**.
12
+
13
+ **Установка:**
14
+ 1. Установите [расширение](https://github.com/reduxjs/redux-devtools) для браузера
15
+ 2. Подключите в коде:
16
+
17
+ ```typescript
18
+ import { DefaultOptions, reduxDevtools } from '@fozy-labs/rx-toolkit';
19
+
20
+ DefaultOptions.update({
21
+ DEVTOOLS: reduxDevtools()
22
+ });
23
+ ```
24
+
25
+ ## @reatom/devtools
26
+
27
+ Npm пакет, содержащий отладчик, работающий прямо в браузере.
28
+ Совместим по API с `rx-toolkit`.
29
+ После подключения в углу страницы появляется кнопка,
30
+ которая открывает панель инструментов.
31
+
32
+ **Установка:**
33
+ ```bash
34
+ npm install @reatom/devtools
35
+ ```
36
+
37
+ **Подключение:**
38
+ ```typescript
39
+ import { DefaultOptions } from '@fozy-labs/rx-toolkit';
40
+ import { createDevtools } from '@reatom/devtools';
41
+
42
+ DefaultOptions.update({
43
+ DEVTOOLS: createDevtools({
44
+ initVisibility: true
45
+ })
46
+ });
47
+ ```
48
+
49
+ **Может пригодиться:**
50
+ - Если в вашей среде не удобно или проблематично установить браузерное расширение.
51
+ - Если вы уже работали с Reatom.
52
+
53
+ ## Практики
54
+
55
+ ### Development-режим
56
+
57
+ Подключайте devtools только в разработке:
58
+
59
+ ```typescript
60
+ // Node.js/Webpack
61
+ if (process.env.NODE_ENV !== 'production') {
62
+ DefaultOptions.update({ DEVTOOLS: reduxDevtools() });
63
+ }
64
+
65
+ // Vite
66
+ if (import.meta.env.DEV) {
67
+ DefaultOptions.update({ DEVTOOLS: reduxDevtools() });
68
+ }
69
+ ```
70
+
71
+ ### SSR-совместимость
72
+
73
+ Защитите от выполнения на сервере:
74
+
75
+ ```typescript
76
+ if (typeof window !== 'undefined' && process.env.NODE_ENV !== 'production') {
77
+ DefaultOptions.update({ DEVTOOLS: reduxDevtools() });
78
+ }
79
+ ```
80
+
81
+ ### Несколько инструментов
82
+
83
+ Можно комбинировать несколько devtools:
84
+
85
+ ```typescript
86
+ import { combineDevtools, reduxDevtools } from '@fozy-labs/rx-toolkit';
87
+ import { createDevtools } from '@reatom/devtools';
88
+
89
+ DefaultOptions.update({
90
+ DEVTOOLS: combineDevtools(
91
+ reduxDevtools({ name: 'MyApp' }),
92
+ createDevtools({ initVisibility: true })
93
+ )
94
+ });
95
+ ```