@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
package/README.md CHANGED
@@ -27,14 +27,15 @@ RxToolkit решает эти проблемы, предоставляя сво
27
27
  - 🔧 **Framework-agnostic** — Стройте систему и описывайте логику в изолированном месте.
28
28
  - ⚡ **Built on RxJS** — Наследует всю мощь RxJS.
29
29
  - 💾 **Кеш-менеджер** — Предоставляет Query реализацию для работы с данными.
30
- - 🔷 **TypeScript-first** — Полная типизация из коробки.
31
- - 🔗 **Интеграция с фреймворками** — Как и RxJS напрямую работает в Angular, Svelte и SolidJS.
32
- Для React предоставляет хуки из коробки.
30
+ - 🔷 **TypeScript-first** — Полная типизация.
31
+ - 🔗 **Интеграция с фреймворками** — Как и RxJS напрямую работает в Angular, Svelte и SolidJS.
32
+ Поставляется с React-хуками из коробки.
33
33
 
34
34
  ## 📚 Документация
35
35
  - [**RxSignals**](./docs/signals/README.md) - реактивные примитивы
36
36
  - [**RxQuery**](./docs/query/README.md) - кеш-менеджер для работы с данными
37
37
  - [**React**](./docs/usage/react/README.md) - интеграция с React
38
+ - [**Devtools**](./docs/devtools/README.md) - инструменты разработчика
38
39
 
39
40
  ## 🌟 Примеры
40
41
 
@@ -59,20 +60,26 @@ count$ = toSignal(store.count$);
59
60
  // Angular pipe
60
61
  {{ store.count$ | async }}
61
62
 
63
+ // SolidJS
64
+ const count$ = from(store.count$)
65
+
62
66
  // Svelte
63
67
  $: count = store.count$;
64
68
  ```
65
69
 
66
70
  ###### Работаем с RxJS
71
+
67
72
  ```typescript
68
73
  // Создаем Observable
74
+
69
75
  const clicker$ = fromEvent(document, 'click').pipe(
70
76
  debounceTime(300),
71
- scan(count => count + 1, 0)
77
+ scan(count => count + 1, 0),
78
+ startWith(0),
72
79
  );
73
80
 
74
81
  // Получаем сигнал из Observable
75
- const clickCount$ = new ReadonlySignal(clicker$);
82
+ const clickCount$ = signalize(clicker$);
76
83
  const doubled$ = new Computed(() => clickCount$.value * 2);
77
84
 
78
85
  console.log(doubled$.value); // Всегда актуальное значение
@@ -84,7 +91,7 @@ const on10click$ = doubled$.pipe(
84
91
  );
85
92
 
86
93
  on10click$.subscribe(() => {
87
- console.log('Great! That you first reached 10 clicks');
94
+ console.log('Great! That you first reached 10 clicks!');
88
95
  });
89
96
 
90
97
  ```
@@ -92,40 +99,39 @@ on10click$.subscribe(() => {
92
99
  ###### RxQuery (Корзина покупок)
93
100
  ```tsx
94
101
  const getCart = createResource({
95
- queryFn: fetchCart,
102
+ queryFn: fetchCart,
96
103
  });
97
104
 
98
105
  const toggleCardItem = createOperation({
99
- queryFn: fetchToggleItem,
100
- link(add) {
101
- add({
102
- resource: getCart,
103
- forwardArgs: () => undefined,
104
- optimisticUpdate: ({ draft, data, args }) => {
105
- const item = draft.items.find(i => i.id === data.id);
106
- if (!item) return;
107
- item.enabled = args.enabled;
108
- }
109
- })
110
- }
106
+ queryFn: fetchToggleCardItem,
107
+ link(add) {
108
+ add({
109
+ resource: getCart,
110
+ forwardArgs: () => undefined,
111
+ optimisticUpdate: ({ draft, data, args }) => {
112
+ const item = draft.items.find(i => i.id === data.id);
113
+ if (!item) return;
114
+ item.enabled = args.enabled;
115
+ }
116
+ })
117
+ }
111
118
  });
112
119
 
113
120
  function ShoppingCart() {
114
- const cartQuery = useResourceAgent(getCart);
115
- const [toggleItem] = useOperationAgent(toggleCardItem);
116
- const cart = cartQuery.data;
117
-
118
- return (
119
- <Container isLoading={cartQuery.isLoading}>
120
- {cart?.items.map(item => (
121
- <CartItem
122
- key={item.id}
123
- item={item}
124
- onToggle={() => toggleItem({ id: item.id, enabled: !item.enabled })}
125
- />
126
- ))}
127
- <Total amount={cart?.total} />
128
- </Container>
129
- );
121
+ const cartQuery = useResourceAgent(getCart);
122
+ const [toggleItem] = useOperationAgent(toggleCardItem);
123
+ const cart = cartQuery.data;
124
+
125
+ return (
126
+ <Container isLoading={cartQuery.isLoading}>
127
+ {cart?.items.map(item => (
128
+ <CartItem
129
+ key={item.id}
130
+ item={item}
131
+ onToggle={() => toggleItem({ id: item.id, enabled: !item.enabled })}
132
+ />
133
+ ))}
134
+ </Container>
135
+ );
130
136
  }
131
137
  ```
@@ -0,0 +1,2 @@
1
+ import { DevtoolsLike } from "../query/types/devtools";
2
+ export declare function combineDevtools(...devtools: DevtoolsLike[]): DevtoolsLike;
@@ -0,0 +1,10 @@
1
+ export function combineDevtools(...devtools) {
2
+ return {
3
+ state(name, initState) {
4
+ const updaters = devtools.map(d => d.state(name, initState));
5
+ return (newState) => {
6
+ updaters.forEach(update => update(newState));
7
+ };
8
+ }
9
+ };
10
+ }
@@ -0,0 +1,2 @@
1
+ export * from './reduxDevtools';
2
+ export * from './combineDevtools';
@@ -0,0 +1,2 @@
1
+ export * from './reduxDevtools';
2
+ export * from './combineDevtools';
@@ -0,0 +1,2 @@
1
+ import { DevtoolsLike } from "../query/types/devtools";
2
+ export declare function reduxDevtools(): DevtoolsLike;
@@ -0,0 +1,24 @@
1
+ import { Batcher } from "../signals";
2
+ export function reduxDevtools() {
3
+ let state = {};
4
+ // @ts-ignore
5
+ const reduxDevtools = window.__REDUX_DEVTOOLS_EXTENSION__.connect({ name: 'RxToolkit' });
6
+ reduxDevtools.init(state);
7
+ const scheduler = Batcher.scheduler(Infinity);
8
+ const updateFn = () => {
9
+ reduxDevtools.send({ type: 'update' }, state);
10
+ };
11
+ const createFn = () => {
12
+ reduxDevtools.send({ type: 'create' }, state);
13
+ };
14
+ return {
15
+ state(name, initState) {
16
+ state = { ...state, [name]: initState };
17
+ scheduler.schedule(createFn);
18
+ return (newState) => {
19
+ state = { ...state, [name]: newState };
20
+ scheduler.schedule(updateFn);
21
+ };
22
+ }
23
+ };
24
+ }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './query';
2
- export * from './signal';
2
+ export * from './signals';
3
3
  export * from './react-hooks';
4
+ export * from './devtools';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './query';
2
- export * from './signal';
2
+ export * from './signals';
3
3
  export * from './react-hooks';
4
+ export * from './devtools';
@@ -1,6 +1,6 @@
1
- import { Devtools } from "@reatom/devtools";
1
+ import { DevtoolsLike } from "../../query/types/devtools";
2
2
  type Update = Partial<{
3
- DEVTOOLS: Devtools | null;
3
+ DEVTOOLS: DevtoolsLike | null;
4
4
  onError: (error: unknown) => void;
5
5
  }>;
6
6
  export declare class DefaultOptions {
@@ -50,7 +50,7 @@ class OperationQueryState {
50
50
  }
51
51
  export class Operation {
52
52
  _options;
53
- _queriesCache = new QueriesCache('opr');
53
+ _queriesCache = new QueriesCache('Operation');
54
54
  _links = [];
55
55
  constructor(_options) {
56
56
  this._options = _options;
@@ -89,7 +89,7 @@ export class Operation {
89
89
  state.unlocker = ref.lock();
90
90
  }
91
91
  if (link.optimisticUpdate && ref.has) {
92
- state.patch = ref.update((draft) => {
92
+ state.patch = ref.patch((draft) => {
93
93
  return link.optimisticUpdate({ draft, args });
94
94
  });
95
95
  }
@@ -102,7 +102,7 @@ export class Operation {
102
102
  /**
103
103
  * Обновляем связанные ресурсы
104
104
  */
105
- linksMeta.forEach(({ link, ref }) => {
105
+ linksMeta.forEach(({ link, ref, state }) => {
106
106
  if (link.update && ref.has) {
107
107
  ref.update((draft) => {
108
108
  return link.update({ draft, args, data });
@@ -111,6 +111,7 @@ export class Operation {
111
111
  if (link.create && !ref.has) {
112
112
  ref.create(link.create({ args, data }));
113
113
  }
114
+ state.patch?.commit();
114
115
  });
115
116
  })
116
117
  .catch((error) => {
@@ -120,7 +121,7 @@ export class Operation {
120
121
  * Обновляем связанные ресурсы
121
122
  */
122
123
  linksMeta.forEach(({ state }) => {
123
- state.patch?.rollback();
124
+ state.patch?.abort();
124
125
  });
125
126
  })
126
127
  .finally(() => {
@@ -1,5 +1,5 @@
1
- import { Computed } from "../../../signal";
2
1
  import { OperationAgentInstanse, OperationDefinition } from "../../../query/types/Operation.types";
2
+ import { Computed } from "../../../signals";
3
3
  import type { Operation } from "./Operation";
4
4
  export declare class OperationAgent<D extends OperationDefinition> implements OperationAgentInstanse<D> {
5
5
  private _operation;
@@ -1,9 +1,9 @@
1
- import { Computed, Signal } from "../../../signal";
1
+ import { Computed, Signal } from "../../../signals";
2
2
  export class OperationAgent {
3
3
  _operation;
4
4
  _operations$ = new Signal({
5
5
  current$: null,
6
- });
6
+ }, { disableDevtools: true });
7
7
  state$ = new Computed(() => {
8
8
  const operations = this._operations$.value;
9
9
  const currState = operations.current$?.value;
@@ -28,7 +28,7 @@ export class OperationAgent {
28
28
  data: currState.data ?? undefined,
29
29
  args: currState.arg,
30
30
  };
31
- });
31
+ }, { disableDevtools: true });
32
32
  constructor(_operation) {
33
33
  this._operation = _operation;
34
34
  }
@@ -1,5 +1,6 @@
1
1
  import { IndirectMap } from "../../query/lib/IndirectMap";
2
2
  import { ReactiveCache } from "../../query/lib/ReactiveCache";
3
+ import { Indexer } from "../../signals/base/Indexer";
3
4
  import { SharedOptions } from "./SharedOptions";
4
5
  export class QueriesCache {
5
6
  _logname;
@@ -14,12 +15,17 @@ export class QueriesCache {
14
15
  const cache = new ReactiveCache({
15
16
  initialState,
16
17
  });
17
- const devtoolsState = SharedOptions.DEVTOOLS?.state;
18
- if (devtoolsState) {
19
- const randomId = Math.random().toString(36).substring(2, 16);
20
- const key = `rxt:${this._logname}:${JSON.stringify(args)}:${randomId}`;
18
+ const stateDevtools = SharedOptions.DEVTOOLS?.state;
19
+ if (stateDevtools) {
20
+ const key = `${this._logname}:${JSON.stringify(args)}:i=${Indexer.getIndex()}`;
21
+ let devtools = stateDevtools(key, initialState);
21
22
  cache.spy$.subscribe((state) => {
22
- devtoolsState(key, state);
23
+ if (state === initialState)
24
+ return;
25
+ devtools(state);
26
+ });
27
+ cache.onClean$.subscribe(() => {
28
+ devtools('$CLEANED');
23
29
  });
24
30
  }
25
31
  cache.onClean$.subscribe(() => {
@@ -1,10 +1,12 @@
1
1
  import { ReactiveCache } from "../../../query/lib/ReactiveCache";
2
- import type { ResourceCreateOptions, ResourceDefinition, ResourceInstance, ResourceRefInstanse } from "../../../query/types/Resource.types";
2
+ import type { ResourceCreateOptions, ResourceDefinition, ResourceInstance, ResourceRefInstanse, ResourceTransaction } from "../../../query/types/Resource.types";
3
3
  import { QueriesCache } from "../QueriesCache";
4
4
  import { ResourceAgent } from "./ResourceAgent";
5
5
  export type CoreResourceQueryState<D extends ResourceDefinition> = {
6
+ transactions: ResourceTransaction[] | null;
6
7
  abortController: AbortController | null;
7
8
  args: D['Args'] | null;
9
+ savedData: D['Data'] | null;
8
10
  data: D['Data'] | null;
9
11
  error: unknown | null;
10
12
  isError: boolean;
@@ -31,7 +33,17 @@ export declare class Resource<D extends ResourceDefinition> implements ResourceI
31
33
  decrementLock(args: D['Args'], options?: {
32
34
  cache?: CoreResourceQueryCache<D>;
33
35
  }): CoreResourceQueryCache<D> | null;
34
- updateData(args: D['Args'], updateFn: (data: D['Data']) => D['Data'], options?: {
36
+ /**
37
+ * @deprecated
38
+ */
39
+ updateData_legacy(args: D['Args'], updateFn: (data: D['Data']) => D['Data'], options?: {
40
+ cache?: CoreResourceQueryCache<D>;
41
+ }): CoreResourceQueryCache<D> | null;
42
+ update(args: D['Args'], updateFn: (data: D['Data'], savedData: D['Data'] | null, transactions: ResourceTransaction[] | null) => {
43
+ data: D['Data'];
44
+ transactions: ResourceTransaction[] | null;
45
+ savedData: D['Data'] | null;
46
+ }, options?: {
35
47
  cache?: CoreResourceQueryCache<D>;
36
48
  }): CoreResourceQueryCache<D> | null;
37
49
  initiate(args: D['Args'], options?: {
@@ -5,6 +5,8 @@ import { ResourceRef } from "./ResourceRef";
5
5
  class ResourceQueryState {
6
6
  static create() {
7
7
  return {
8
+ transactions: null,
9
+ savedData: null,
8
10
  abortController: null,
9
11
  args: null,
10
12
  data: null,
@@ -31,6 +33,8 @@ class ResourceQueryState {
31
33
  static success(state, data) {
32
34
  return {
33
35
  ...state,
36
+ savedData: null,
37
+ transactions: null,
34
38
  data,
35
39
  isLoading: false,
36
40
  isReloading: false,
@@ -67,16 +71,28 @@ class ResourceQueryState {
67
71
  lockCount
68
72
  };
69
73
  }
74
+ static update(state, data, savedData, transactions) {
75
+ return {
76
+ ...state,
77
+ transactions,
78
+ savedData,
79
+ data
80
+ };
81
+ }
82
+ /**
83
+ * @deprecated
84
+ */
70
85
  static setData(state, data) {
71
86
  return {
72
87
  ...state,
88
+ transactions: null,
73
89
  data
74
90
  };
75
91
  }
76
92
  }
77
93
  export class Resource {
78
94
  _options;
79
- _queriesCache = new QueriesCache('res');
95
+ _queriesCache = new QueriesCache('Resource');
80
96
  constructor(_options) {
81
97
  this._options = _options;
82
98
  }
@@ -108,7 +124,10 @@ export class Resource {
108
124
  cache.next(ResourceQueryState.decrementLock(cache.value));
109
125
  return cache;
110
126
  }
111
- updateData(args, updateFn, options) {
127
+ /**
128
+ * @deprecated
129
+ */
130
+ updateData_legacy(args, updateFn, options) {
112
131
  let cache = options?.cache ?? this.getQueryCache(args);
113
132
  if (!cache) {
114
133
  return null;
@@ -121,6 +140,19 @@ export class Resource {
121
140
  cache.next(ResourceQueryState.setData(cache.value, newData));
122
141
  return cache;
123
142
  }
143
+ update(args, updateFn, options) {
144
+ let cache = options?.cache ?? this.getQueryCache(args);
145
+ if (!cache) {
146
+ return null;
147
+ }
148
+ const cacheValue = cache.value;
149
+ if (!cacheValue.isDone) {
150
+ return cache;
151
+ }
152
+ const { data, transactions, savedData } = updateFn(cacheValue.data, cacheValue.savedData, cacheValue.transactions);
153
+ cache.next(ResourceQueryState.update(cache.value, data, savedData, transactions));
154
+ return cache;
155
+ }
124
156
  initiate(args, options) {
125
157
  let cache = options?.cache ?? this._queriesCache.getQueryCache(args);
126
158
  const state = ResourceQueryState.load(cache?.value, args);
@@ -1,4 +1,4 @@
1
- import { Computed } from "../../../signal";
1
+ import { Computed } from "../../../signals";
2
2
  import { ResourceAgentInstance, ResourceDefinition } from "../../../query/types/Resource.types";
3
3
  import type { Resource } from "./Resource";
4
4
  export declare class ResourceAgent<D extends ResourceDefinition> implements ResourceAgentInstance<D> {
@@ -1,10 +1,10 @@
1
- import { Computed, Signal } from "../../../signal";
1
+ import { Computed, Signal } from "../../../signals";
2
2
  export class ResourceAgent {
3
3
  _resource;
4
4
  _resources$ = new Signal({
5
5
  previous$: null,
6
6
  current$: null,
7
- });
7
+ }, { disableDevtools: true });
8
8
  state$ = new Computed(() => {
9
9
  const resources = this._resources$.value;
10
10
  let prevState;
@@ -41,7 +41,7 @@ export class ResourceAgent {
41
41
  data: isShowPrev ? prevState.data ?? undefined : currState.data ?? undefined,
42
42
  args: currState.args ?? undefined,
43
43
  };
44
- });
44
+ }, { disableDevtools: true });
45
45
  constructor(_resource) {
46
46
  this._resource = _resource;
47
47
  }
@@ -1,5 +1,5 @@
1
1
  import { Resource } from "./Resource";
2
- import { ResourceDefinition, ResourceRefInstanse } from "../../types/Resource.types";
2
+ import { ResourceDefinition, ResourceRefInstanse, ResourceTransaction } from "../../types/Resource.types";
3
3
  export declare class ResourceRef<D extends ResourceDefinition> implements ResourceRefInstanse<D> {
4
4
  private readonly _resource;
5
5
  private readonly _args;
@@ -13,6 +13,7 @@ export declare class ResourceRef<D extends ResourceDefinition> implements Resour
13
13
  update(updateFn: (data: D["Data"]) => D["Data"]): {
14
14
  rollback: () => void;
15
15
  };
16
+ patch(patchFn: (data: D["Data"]) => void): ResourceTransaction | null;
16
17
  create(data: D["Data"]): void;
17
18
  invalidate(): void;
18
19
  }
@@ -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,5 +1,5 @@
1
- import { Devtools } from '@reatom/devtools';
1
+ import { DevtoolsLike } from "../../query/types/devtools";
2
2
  export declare class SharedOptions {
3
- static DEVTOOLS: Devtools | null;
3
+ static DEVTOOLS: DevtoolsLike | null;
4
4
  static onError: ((error: unknown) => void) | null;
5
5
  }
@@ -1,7 +1,7 @@
1
1
  export * from './api/createResource';
2
2
  export * from './api/createOperation';
3
3
  export * from './api/DefaultOptions';
4
- export * from './api/createDevtools';
5
4
  export * from './SKIP_TOKEN';
6
5
  export * from './react/useResourceAgent';
6
+ export * from './react/useResourceRef';
7
7
  export * from './react/useOperationAgent';
@@ -1,7 +1,7 @@
1
1
  export * from './api/createResource';
2
2
  export * from './api/createOperation';
3
3
  export * from './api/DefaultOptions';
4
- export * from './api/createDevtools';
5
4
  export * from './SKIP_TOKEN';
6
5
  export * from './react/useResourceAgent';
6
+ export * from './react/useResourceRef';
7
7
  export * from './react/useOperationAgent';
@@ -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(() => {
@@ -0,0 +1,6 @@
1
+ import { SKIP } from "../../query/SKIP_TOKEN";
2
+ import { ResourceDefinition, ResourceInstance, ResourceRefInstanse } from "../../query/types/Resource.types";
3
+ import { Prettify } from "../../query/types/shared.types";
4
+ type Result<D extends ResourceDefinition> = Prettify<ResourceRefInstanse<D>> | null;
5
+ export declare function useResourceRef<D extends ResourceDefinition>(res: ResourceInstance<D>, ...argss: D['Args'] extends void ? [] | [typeof SKIP] : [D['Args'] | typeof SKIP]): Result<D>;
6
+ export {};
@@ -0,0 +1,8 @@
1
+ import { SKIP } from "../../query/SKIP_TOKEN";
2
+ import React from "react";
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
+ }