@fozy-labs/rx-toolkit 0.4.1 → 0.4.3

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 (112) hide show
  1. package/README.md +44 -39
  2. package/dist/common/devtools/combineDevtools.d.ts +2 -0
  3. package/dist/common/devtools/combineDevtools.js +10 -0
  4. package/dist/common/devtools/index.d.ts +3 -0
  5. package/dist/common/devtools/index.js +3 -0
  6. package/dist/common/devtools/reduxDevtools.d.ts +2 -0
  7. package/dist/common/devtools/reduxDevtools.js +24 -0
  8. package/dist/common/devtools/types.d.ts +6 -0
  9. package/dist/{query/api → common/options}/DefaultOptions.d.ts +2 -2
  10. package/dist/{query/api → common/options}/DefaultOptions.js +1 -1
  11. package/dist/common/options/SharedOptions.d.ts +5 -0
  12. package/dist/common/options/index.d.ts +1 -0
  13. package/dist/common/options/index.js +1 -0
  14. package/dist/{react-hooks → common/react}/index.d.ts +2 -3
  15. package/dist/{react-hooks → common/react}/index.js +2 -3
  16. package/dist/{react-hooks → common/react}/useObservable.js +2 -2
  17. package/dist/{react-hooks → common/react}/useSyncObservable.js +2 -1
  18. package/dist/index.d.ts +4 -2
  19. package/dist/index.js +4 -2
  20. package/dist/query/api/createOperation.d.ts +1 -5
  21. package/dist/query/api/createOperation.js +0 -4
  22. package/dist/query/api/createResource.d.ts +1 -1
  23. package/dist/query/core/Opertation/Operation.d.ts +2 -3
  24. package/dist/query/core/Opertation/Operation.js +6 -5
  25. package/dist/query/core/Opertation/OperationAgent.d.ts +3 -3
  26. package/dist/query/core/Opertation/OperationAgent.js +3 -3
  27. package/dist/query/core/QueriesCache.js +12 -6
  28. package/dist/query/core/Resource/Resource.d.ts +15 -3
  29. package/dist/query/core/Resource/Resource.js +36 -4
  30. package/dist/query/core/Resource/ResourceAgent.d.ts +2 -2
  31. package/dist/query/core/Resource/ResourceAgent.js +3 -3
  32. package/dist/query/core/Resource/ResourceRef.d.ts +2 -1
  33. package/dist/query/core/Resource/ResourceRef.js +96 -2
  34. package/dist/query/index.d.ts +1 -2
  35. package/dist/query/index.js +1 -2
  36. package/dist/query/lib/IndirectMap.js +1 -1
  37. package/dist/query/lib/ReactiveCache.js +2 -2
  38. package/dist/query/react/useOperationAgent.d.ts +1 -2
  39. package/dist/query/react/useOperationAgent.js +2 -1
  40. package/dist/query/react/useResourceAgent.d.ts +2 -3
  41. package/dist/query/react/useResourceAgent.js +4 -3
  42. package/dist/query/react/useResourceRef.d.ts +5 -0
  43. package/dist/query/react/useResourceRef.js +8 -0
  44. package/dist/query/types/Operation.types.d.ts +44 -3
  45. package/dist/query/types/Resource.types.d.ts +17 -2
  46. package/dist/query/types/index.d.ts +3 -0
  47. package/dist/query/types/index.js +3 -0
  48. package/dist/signals/base/Batcher.d.ts +6 -0
  49. package/dist/signals/base/Batcher.js +55 -0
  50. package/dist/{signal → signals}/base/Computed.d.ts +5 -2
  51. package/dist/{signal → signals}/base/Computed.js +7 -3
  52. package/dist/{signal → signals}/base/Effect.d.ts +3 -3
  53. package/dist/signals/base/Effect.js +51 -0
  54. package/dist/signals/base/Indexer.d.ts +4 -0
  55. package/dist/signals/base/Indexer.js +6 -0
  56. package/dist/{signal → signals}/base/ReadonlySignal.d.ts +4 -1
  57. package/dist/signals/base/ReadonlySignal.js +46 -0
  58. package/dist/{signal → signals}/base/Signal.d.ts +14 -3
  59. package/dist/{signal → signals}/base/Signal.js +23 -3
  60. package/dist/signals/base/Tracker.d.ts +10 -0
  61. package/dist/signals/base/Tracker.js +7 -0
  62. package/dist/{signal → signals}/base/types.d.ts +9 -1
  63. package/dist/signals/base/types.js +1 -0
  64. package/dist/{signal → signals}/extends/LocalSignal.d.ts +1 -1
  65. package/dist/{signal → signals}/extends/LocalSignal.js +3 -3
  66. package/dist/{signal → signals}/index.d.ts +1 -0
  67. package/dist/{signal → signals}/index.js +1 -0
  68. package/dist/{signal → signals}/operators/filterUpdates.js +1 -1
  69. package/dist/signals/operators/index.d.ts +3 -0
  70. package/dist/signals/operators/index.js +3 -0
  71. package/dist/{signal → signals}/operators/mapSignals.js +1 -1
  72. package/dist/{signal → signals}/operators/signalize.d.ts +1 -1
  73. package/dist/{signal → signals}/operators/signalize.js +1 -1
  74. package/dist/signals/react/index.d.ts +1 -0
  75. package/dist/signals/react/index.js +1 -0
  76. package/dist/{react-hooks → signals/react}/useSignal.d.ts +1 -1
  77. package/dist/{react-hooks → signals/react}/useSignal.js +1 -1
  78. package/docs/devtools/README.md +95 -0
  79. package/docs/query/README.md +25 -14
  80. package/docs/signals/README.md +11 -8
  81. package/package.json +13 -8
  82. package/dist/query/api/createDevtools.d.ts +0 -1
  83. package/dist/query/api/createDevtools.js +0 -6
  84. package/dist/query/api/createSubResource.d.ts +0 -0
  85. package/dist/query/api/createSubResource.js +0 -1
  86. package/dist/query/core/SharedOptions.d.ts +0 -5
  87. package/dist/signal/base/Batcher.d.ts +0 -7
  88. package/dist/signal/base/Batcher.js +0 -21
  89. package/dist/signal/base/Effect.js +0 -69
  90. package/dist/signal/base/ReadonlySignal.js +0 -20
  91. package/dist/signal/base/Tracker.d.ts +0 -5
  92. package/dist/signal/base/Tracker.js +0 -7
  93. package/dist/signal/operators/batch.d.ts +0 -2
  94. package/dist/signal/operators/batch.js +0 -27
  95. package/dist/signal/operators/index.d.ts +0 -4
  96. package/dist/signal/operators/index.js +0 -4
  97. /package/dist/{signal/base → common/devtools}/types.js +0 -0
  98. /package/dist/{query/core → common/options}/SharedOptions.js +0 -0
  99. /package/dist/{react-hooks → common/react}/useConstant.d.ts +0 -0
  100. /package/dist/{react-hooks → common/react}/useConstant.js +0 -0
  101. /package/dist/{react-hooks → common/react}/useEventHandler.d.ts +0 -0
  102. /package/dist/{react-hooks → common/react}/useEventHandler.js +0 -0
  103. /package/dist/{react-hooks → common/react}/useObservable.d.ts +0 -0
  104. /package/dist/{react-hooks → common/react}/useSyncObservable.d.ts +0 -0
  105. /package/dist/{signal → signals}/base/SyncObservable.d.ts +0 -0
  106. /package/dist/{signal → signals}/base/SyncObservable.js +0 -0
  107. /package/dist/{signal → signals}/base/index.d.ts +0 -0
  108. /package/dist/{signal → signals}/base/index.js +0 -0
  109. /package/dist/{signal → signals}/extends/index.d.ts +0 -0
  110. /package/dist/{signal → signals}/extends/index.js +0 -0
  111. /package/dist/{signal → signals}/operators/filterUpdates.d.ts +0 -0
  112. /package/dist/{signal → signals}/operators/mapSignals.d.ts +0 -0
@@ -1,3 +1,5 @@
1
+ import { applyPatches, enablePatches, produceWithPatches } from "immer";
2
+ enablePatches();
1
3
  export class ResourceRef {
2
4
  _resource;
3
5
  _args;
@@ -36,13 +38,105 @@ export class ResourceRef {
36
38
  };
37
39
  }
38
40
  const value = cacheItem.value;
39
- this._resource.updateData(this._args, updateFn, { cache: cacheItem });
41
+ this._resource.updateData_legacy(this._args, updateFn, { cache: cacheItem });
40
42
  return {
41
43
  rollback: () => {
42
- this._resource.updateData(this._args, () => value.data, { cache: cacheItem });
44
+ this._resource.updateData_legacy(this._args, () => value.data, { cache: cacheItem });
43
45
  }
44
46
  };
45
47
  }
48
+ patch(patchFn) {
49
+ let isSkipped = true;
50
+ const reapplyFn = (data, savedData, transactions) => {
51
+ if (!transactions || transactions.length === 0) {
52
+ return { data, transactions, savedData };
53
+ }
54
+ // Все валидные committed - пропускаем и убираем из очереди
55
+ // Все aborted - применяем и убираем из очереди
56
+ // Все pending - применяем и оставляем в очереди
57
+ // Все commited (которые после pending) - применяем, но оставляем в очереди
58
+ // Все aborted (которые после pending) - откатываем, но оставляем в очереди
59
+ // Те после применения всех транзакций, очередь должна начинаться с первой pending транзакции (если есть), включая все, что после неё.
60
+ let newSavedData = savedData ?? data;
61
+ let currentData = savedData ?? data;
62
+ const remainingTransactions = [];
63
+ let foundPending = false;
64
+ transactions.forEach((transaction) => {
65
+ if (transaction.status === 'pending') {
66
+ foundPending = true;
67
+ // Применяем pending транзакцию и оставляем в очереди
68
+ currentData = applyPatches(currentData, transaction.patches);
69
+ remainingTransactions.push(transaction);
70
+ }
71
+ else if (foundPending) {
72
+ // После pending транзакции
73
+ if (transaction.status === 'committed') {
74
+ // Применяем и оставляем в очереди
75
+ currentData = applyPatches(currentData, transaction.patches);
76
+ remainingTransactions.push(transaction);
77
+ }
78
+ else if (transaction.status === 'aborted') {
79
+ // Откатываем и оставляем в очереди
80
+ currentData = applyPatches(currentData, transaction.inversePatches);
81
+ remainingTransactions.push(transaction);
82
+ }
83
+ }
84
+ else {
85
+ // До первой pending транзакции
86
+ if (transaction.status === 'committed') {
87
+ // Применяем и убираем из очереди
88
+ const patches = transaction.patches;
89
+ currentData = applyPatches(currentData, patches);
90
+ newSavedData = currentData;
91
+ }
92
+ }
93
+ });
94
+ const hasTransactions = remainingTransactions.length > 0;
95
+ return {
96
+ data: currentData,
97
+ transactions: hasTransactions ? remainingTransactions : null,
98
+ savedData: hasTransactions ? newSavedData : null,
99
+ };
100
+ };
101
+ const reapplyTransactions = () => {
102
+ this._resource.update(this._args, reapplyFn, { cache: this._cacheItem ?? undefined });
103
+ };
104
+ const transaction = {
105
+ patches: [],
106
+ inversePatches: [],
107
+ status: 'pending',
108
+ abort() {
109
+ if (this.status !== 'pending')
110
+ return;
111
+ this.status = 'aborted';
112
+ reapplyTransactions();
113
+ },
114
+ commit() {
115
+ if (this.status !== 'pending')
116
+ return;
117
+ this.status = 'committed';
118
+ reapplyTransactions();
119
+ }
120
+ };
121
+ const updateFn = (data, savedData, transactions) => {
122
+ isSkipped = false;
123
+ const [newData, patches, inversePatches] = produceWithPatches(data, (draft) => patchFn(draft));
124
+ transaction.patches = patches;
125
+ transaction.inversePatches = inversePatches;
126
+ const newTransactions = [...(transactions ?? [])];
127
+ newTransactions.push(transaction);
128
+ return {
129
+ data: newData,
130
+ transactions: newTransactions,
131
+ savedData: savedData ?? data,
132
+ };
133
+ };
134
+ this._cacheItem = this._resource.update(this._args, updateFn, { cache: this._cacheItem ?? undefined });
135
+ if (isSkipped) {
136
+ return null;
137
+ }
138
+ return transaction;
139
+ }
46
140
  create(data) {
47
141
  throw new Error("Method not implemented.");
48
142
  }
@@ -1,7 +1,6 @@
1
1
  export * from './api/createResource';
2
2
  export * from './api/createOperation';
3
- export * from './api/DefaultOptions';
4
- export * from './api/createDevtools';
5
3
  export * from './SKIP_TOKEN';
6
4
  export * from './react/useResourceAgent';
5
+ export * from './react/useResourceRef';
7
6
  export * from './react/useOperationAgent';
@@ -1,7 +1,6 @@
1
1
  export * from './api/createResource';
2
2
  export * from './api/createOperation';
3
- export * from './api/DefaultOptions';
4
- export * from './api/createDevtools';
5
3
  export * from './SKIP_TOKEN';
6
4
  export * from './react/useResourceAgent';
5
+ export * from './react/useResourceRef';
7
6
  export * from './react/useOperationAgent';
@@ -1,4 +1,4 @@
1
- import { shallowEqual } from "../../query/lib/shallowEqual";
1
+ import { shallowEqual } from "./shallowEqual";
2
2
  export class IndirectMap {
3
3
  _compareObjectsFn;
4
4
  _compareCache = new WeakMap();
@@ -1,5 +1,5 @@
1
1
  import { finalize, ReplaySubject, share, Subject, takeUntil, timer } from "rxjs";
2
- import { Signal } from "../../signal";
2
+ import { Signal } from "../../signals";
3
3
  /**
4
4
  * Класс `ReactiveCache` представляет собой реактивный кэш,
5
5
  * который позволяет управлять состоянием и временем жизни кэшированных данных.
@@ -44,7 +44,7 @@ export class ReactiveCache {
44
44
  */
45
45
  constructor(options) {
46
46
  this._cacheLifeTime = options.cacheLifeTime || 60_000;
47
- this._state$ = new Signal(options.initialState);
47
+ this._state$ = new Signal(options.initialState, { disableDevtools: true });
48
48
  this._value = options.initialState;
49
49
  this.spy$ = this._state$.pipe(takeUntil(this.onClean$));
50
50
  this.value$ = this._state$.pipe(finalize(() => {
@@ -1,5 +1,4 @@
1
- import { OperationAgentInstanse, OperationDefinition, OperationQueryState } from "../types/Operation.types";
2
- import { Prettify } from "../types/shared.types";
1
+ import type { Prettify, OperationAgentInstanse, OperationDefinition, OperationQueryState } from "../../query/types";
3
2
  type WithAgent<D extends OperationDefinition> = {
4
3
  createAgent: () => OperationAgentInstanse<D>;
5
4
  };
@@ -1,4 +1,5 @@
1
- import { useConstant, useEventHandler, useSignal } from "../../react-hooks";
1
+ import { useConstant, useEventHandler } from "../../common/react";
2
+ import { useSignal } from "../../signals/react";
2
3
  export function useOperationAgent(op) {
3
4
  const agent = useConstant(() => op.createAgent());
4
5
  const state = useSignal(agent.state$);
@@ -1,6 +1,5 @@
1
- import { ResourceAgentInstance, ResourceDefinition, ResourceQueryState } from "../types/Resource.types";
2
- import { Prettify } from "../types/shared.types";
3
- import { SKIP } from "../SKIP_TOKEN";
1
+ import { Prettify, ResourceAgentInstance, ResourceDefinition, ResourceQueryState } from "../../query/types";
2
+ import { SKIP } from "../../query/SKIP_TOKEN";
4
3
  type WithAgent<D extends ResourceDefinition> = {
5
4
  createAgent: () => ResourceAgentInstance<D>;
6
5
  };
@@ -1,7 +1,8 @@
1
1
  import React from "react";
2
- import { useConstant, useSignal } from "../../react-hooks";
3
- import { shallowEqual } from "../lib/shallowEqual";
4
- import { SKIP } from "../SKIP_TOKEN";
2
+ import { useConstant } from "../../common/react";
3
+ import { useSignal } from "../../signals/react";
4
+ import { shallowEqual } from "../../query/lib/shallowEqual";
5
+ import { SKIP } from "../../query/SKIP_TOKEN";
5
6
  export function useResourceAgent(res, ...argss) {
6
7
  const args = (argss[0] === SKIP ? SKIP : argss[0]);
7
8
  const agent = useConstant(() => {
@@ -0,0 +1,5 @@
1
+ import { SKIP } from "../../query/SKIP_TOKEN";
2
+ import type { Prettify, ResourceDefinition, ResourceInstance, ResourceRefInstanse } from "../../query/types";
3
+ type Result<D extends ResourceDefinition> = Prettify<ResourceRefInstanse<D>> | null;
4
+ export declare function useResourceRef<D extends ResourceDefinition>(res: ResourceInstance<D>, ...argss: D['Args'] extends void ? [] | [typeof SKIP] : [D['Args'] | typeof SKIP]): Result<D>;
5
+ export {};
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ import { SKIP } from "../../query/SKIP_TOKEN";
3
+ export function useResourceRef(res, ...argss) {
4
+ const args = (argss[0] === SKIP ? SKIP : argss[0]);
5
+ return React.useMemo(() => {
6
+ return res.createRef(args);
7
+ }, [args]);
8
+ }
@@ -1,5 +1,5 @@
1
- import { ReadableSignalLike } from "../../signal";
2
- import { FallbackOnNever } from "../../query/types/shared.types";
1
+ import { ReadableSignalLike } from "../../signals";
2
+ import { FallbackOnNever } from "./shared.types";
3
3
  import { ResourceDefinition, ResourceInstance } from "./Resource.types";
4
4
  /**
5
5
  * Функция создания операции
@@ -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";
2
- import { FallbackOnNever } from "../../query/types/shared.types";
1
+ import { Patch as ImmerPatch } from "immer";
2
+ import { ReadableSignalLike } from "../../signals";
3
+ import { FallbackOnNever } from "./shared.types";
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,3 @@
1
+ export * from './Operation.types';
2
+ export * from './Resource.types';
3
+ export * from './shared.types';
@@ -0,0 +1,3 @@
1
+ export * from './Operation.types';
2
+ export * from './Resource.types';
3
+ export * from './shared.types';
@@ -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
+ };
@@ -1,7 +1,10 @@
1
1
  import { Observable, Subscriber, TeardownLogic } from "rxjs";
2
- import { SyncObservable } from "./SyncObservable";
3
2
  import type { ReadableSignalLike } from "./types";
3
+ import { SyncObservable } from "./SyncObservable";
4
4
  export declare class ReadonlySignal<T> extends SyncObservable<T> implements ReadableSignalLike<T> {
5
+ protected rang: number;
6
+ private readonly _devtools;
7
+ private static _logIdIndex;
5
8
  constructor(subscribe?: (this: Observable<T>, subscriber: Subscriber<T>) => TeardownLogic);
6
9
  get value(): T;
7
10
  peek(): T;
@@ -0,0 +1,46 @@
1
+ import { Subscriber } from "rxjs";
2
+ import { SharedOptions } from "../../common/options/SharedOptions";
3
+ import { SyncObservable } from "./SyncObservable";
4
+ import { Tracker } from "./Tracker";
5
+ export class ReadonlySignal extends SyncObservable {
6
+ rang = 0;
7
+ _devtools;
8
+ static _logIdIndex = 0;
9
+ constructor(subscribe) {
10
+ const stateDevtools = SharedOptions.DEVTOOLS?.state;
11
+ const originalSubscribe = subscribe;
12
+ if (stateDevtools && originalSubscribe) {
13
+ subscribe = (subscriber) => {
14
+ const wrappedSubscriber = new Subscriber({
15
+ next: (value) => {
16
+ this._devtools?.(value);
17
+ subscriber.next(value);
18
+ },
19
+ error: (err) => subscriber.error(err),
20
+ complete: () => subscriber.complete()
21
+ });
22
+ return originalSubscribe.call(this, wrappedSubscriber);
23
+ };
24
+ }
25
+ super(subscribe);
26
+ if (stateDevtools) {
27
+ const id = ReadonlySignal._logIdIndex++;
28
+ const key = `ReadonlySignal:i=${id}`;
29
+ const initialValue = this.peek();
30
+ this._devtools = stateDevtools(key, initialValue);
31
+ }
32
+ }
33
+ get value() {
34
+ Tracker.next(this.rang, this);
35
+ return super.value;
36
+ }
37
+ peek() {
38
+ return super.value;
39
+ }
40
+ /**
41
+ * @deprecated
42
+ */
43
+ get() {
44
+ return this.value;
45
+ }
46
+ }
@@ -1,7 +1,13 @@
1
1
  import { BehaviorSubject, Observable } from "rxjs";
2
- import { type ReadableSignalLike, UnaryFunction } from "./types";
3
- export declare class Signal<T> extends BehaviorSubject<T> implements ReadableSignalLike<T> {
4
- constructor(initialValue: T);
2
+ import type { ReadableSignalLike, SignalLike, UnaryFunction } from "./types";
3
+ type SignalOptions = {
4
+ disableDevtools?: boolean;
5
+ devtoolsName?: string;
6
+ };
7
+ export declare class Signal<T> extends BehaviorSubject<T> implements SignalLike<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
1
  import { BehaviorSubject } from "rxjs";
2
+ import { SharedOptions } from "../../common/options/SharedOptions";
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
  */