synapse-storage 4.0.1 → 4.1.1
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/dist/api/utils/fetch-base-query.d.ts +20 -0
- package/dist/api/utils/fetch-base-query.d.ts.map +1 -1
- package/dist/api/utils/fetch-base-query.js +38 -2
- package/dist/api/utils/fetch-base-query.js.map +1 -1
- package/dist/core/storage/adapters/indexed-DB.service.d.ts +15 -0
- package/dist/core/storage/adapters/indexed-DB.service.d.ts.map +1 -1
- package/dist/core/storage/adapters/indexed-DB.service.js +39 -33
- package/dist/core/storage/adapters/indexed-DB.service.js.map +1 -1
- package/dist/react/hooks/useSelector.d.ts +15 -0
- package/dist/react/hooks/useSelector.d.ts.map +1 -1
- package/dist/react/hooks/useSelector.js +17 -1
- package/dist/react/hooks/useSelector.js.map +1 -1
- package/dist/react/utils/awaitSynapse.js +20 -4
- package/dist/react/utils/awaitSynapse.js.map +1 -1
- package/dist/react/utils/createSynapseCtx.d.ts.map +1 -1
- package/dist/react/utils/createSynapseCtx.js +53 -51
- package/dist/react/utils/createSynapseCtx.js.map +1 -1
- package/dist/reactive/dispatcher/standalone.d.ts +73 -4
- package/dist/reactive/dispatcher/standalone.d.ts.map +1 -1
- package/dist/reactive/dispatcher/standalone.js +103 -8
- package/dist/reactive/dispatcher/standalone.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
1
2
|
import { useEffect, useState } from "react";
|
|
2
3
|
import { createSynapseAwaiter } from "../../utils/index.js";
|
|
3
4
|
|
|
@@ -5,11 +6,20 @@ import { createSynapseAwaiter } from "../../utils/index.js";
|
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
|
|
9
|
+
|
|
10
|
+
|
|
8
11
|
/**
|
|
9
12
|
* React-обертка для фреймворк-независимой утилиты ожидания Synapse
|
|
10
13
|
* Добавляет React-специфичные методы поверх createSynapseAwaiter
|
|
11
14
|
*/ function awaitSynapse(synapseStorePromise, options) {
|
|
12
|
-
const { loadingComponent = /*#__PURE__*/
|
|
15
|
+
const { loadingComponent = /*#__PURE__*/ jsx("div", {
|
|
16
|
+
children: "Инициализация..."
|
|
17
|
+
}), errorComponent = (error)=>/*#__PURE__*/ jsxs("div", {
|
|
18
|
+
children: [
|
|
19
|
+
"Ошибка инициализации: ",
|
|
20
|
+
error.message
|
|
21
|
+
]
|
|
22
|
+
}) } = options || {};
|
|
13
23
|
const awaiter = createSynapseAwaiter(synapseStorePromise);
|
|
14
24
|
/**
|
|
15
25
|
* Хук для получения текущего состояния готовности
|
|
@@ -55,11 +65,17 @@ import { createSynapseAwaiter } from "../../utils/index.js";
|
|
|
55
65
|
function WrappedComponent(props) {
|
|
56
66
|
const { isReady, isError, error } = useSynapseReady();
|
|
57
67
|
// Показываем ошибку
|
|
58
|
-
if (isError && error) return /*#__PURE__*/
|
|
68
|
+
if (isError && error) return /*#__PURE__*/ jsx(Fragment, {
|
|
69
|
+
children: errorComponent(error)
|
|
70
|
+
});
|
|
59
71
|
// Показываем загрузку
|
|
60
|
-
if (!isReady) return /*#__PURE__*/
|
|
72
|
+
if (!isReady) return /*#__PURE__*/ jsx(Fragment, {
|
|
73
|
+
children: loadingComponent
|
|
74
|
+
});
|
|
61
75
|
// Рендерим компонент когда все готово
|
|
62
|
-
return /*#__PURE__*/
|
|
76
|
+
return /*#__PURE__*/ jsx(Component, {
|
|
77
|
+
...props
|
|
78
|
+
});
|
|
63
79
|
}
|
|
64
80
|
// Устанавливаем отображаемое имя для отладки
|
|
65
81
|
const componentName = Component.displayName || Component.name || 'Component';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react/utils/awaitSynapse.js","sources":["../../../src/react/utils/awaitSynapse.tsx"],"sourcesContent":["import { ComponentType, PropsWithChildren, ReactNode, useEffect, useState } from 'react'\n\nimport { IStorage } from '../../core'\nimport { AnySynapseStore, createSynapseAwaiter } from '../../utils'\n\ninterface ReactAwaitSynapseOptions {\n loadingComponent?: ReactNode\n errorComponent?: (error: Error) => ReactNode\n}\n\n/**\n * React-обертка для фреймворк-независимой утилиты ожидания Synapse\n * Добавляет React-специфичные методы поверх createSynapseAwaiter\n */\nexport function awaitSynapse<TStore extends Record<string, any>, TStorage extends IStorage<TStore>, TSelectors = any, TActions = any>(\n synapseStorePromise: Promise<AnySynapseStore<TStore, TStorage, TSelectors, TActions>> | AnySynapseStore<TStore, TStorage, TSelectors, TActions>,\n options?: ReactAwaitSynapseOptions,\n) {\n const { loadingComponent = <div>Инициализация...</div>, errorComponent = (error: Error) => <div>Ошибка инициализации: {error.message}</div> } = options || {}\n\n const awaiter = createSynapseAwaiter(synapseStorePromise)\n\n /**\n * Хук для получения текущего состояния готовности\n */\n function useSynapseReady() {\n const [status, setStatus] = useState<'pending' | 'ready' | 'error'>(() => awaiter.getStatus())\n const [store, setStore] = useState<AnySynapseStore<TStore, TStorage, TSelectors, TActions> | undefined>(() => awaiter.getStoreIfReady())\n const [error, setError] = useState<Error | null>(() => awaiter.getError())\n\n useEffect(() => {\n // Проверяем текущее состояние при монтировании\n const currentStatus = awaiter.getStatus()\n const currentStore = awaiter.getStoreIfReady()\n const currentError = awaiter.getError()\n\n setStatus(currentStatus)\n setStore(currentStore)\n setError(currentError)\n\n // Подписываемся на изменения\n const unsubscribeReady = awaiter.onReady((readyStore) => {\n setStatus('ready')\n setStore(readyStore)\n setError(null)\n })\n\n const unsubscribeError = awaiter.onError((err) => {\n setStatus('error')\n setStore(undefined)\n setError(err)\n })\n\n return () => {\n unsubscribeReady()\n unsubscribeError()\n }\n }, [])\n\n return {\n isReady: status === 'ready',\n isError: status === 'error',\n isPending: status === 'pending',\n store,\n error,\n }\n }\n\n /**\n * Обертка, которая ждет готовности Synapse\n */\n function withSynapseReady<ComponentProps>(Component: ComponentType<ComponentProps>) {\n function WrappedComponent(props: ComponentProps) {\n const { isReady, isError, error } = useSynapseReady()\n\n // Показываем ошибку\n if (isError && error) return <>{errorComponent(error)}</>\n\n // Показываем загрузку\n if (!isReady) return <>{loadingComponent}</>\n\n // Рендерим компонент когда все готово\n return <Component {...(props as PropsWithChildren<ComponentProps>)} />\n }\n\n // Устанавливаем отображаемое имя для отладки\n const componentName = Component.displayName || Component.name || 'Component'\n WrappedComponent.displayName = `AwaitSynapse(${componentName})`\n\n return WrappedComponent\n }\n\n return {\n // React методы\n withSynapseReady,\n useSynapseReady,\n\n // Проксируем все методы из awaiter (обёртки сохраняют контекст при деструктуризации)\n waitForReady: () => awaiter.waitForReady(),\n isReady: () => awaiter.isReady(),\n getStoreIfReady: () => awaiter.getStoreIfReady(),\n onReady: (cb: Parameters<typeof awaiter.onReady>[0]) => awaiter.onReady(cb),\n onError: (cb: Parameters<typeof awaiter.onError>[0]) => awaiter.onError(cb),\n getStatus: () => awaiter.getStatus(),\n getError: () => awaiter.getError(),\n destroy: () => awaiter.destroy(),\n }\n}\n"],"names":["useEffect","useState","createSynapseAwaiter","awaitSynapse","synapseStorePromise","options","loadingComponent","errorComponent","error","awaiter","useSynapseReady","status","setStatus","store","setStore","setError","currentStatus","currentStore","currentError","unsubscribeReady","readyStore","unsubscribeError","err","undefined","withSynapseReady","Component","WrappedComponent","props","isReady","isError","componentName","cb"],"mappings":"
|
|
1
|
+
{"version":3,"file":"react/utils/awaitSynapse.js","sources":["../../../src/react/utils/awaitSynapse.tsx"],"sourcesContent":["import { ComponentType, PropsWithChildren, ReactNode, useEffect, useState } from 'react'\n\nimport { IStorage } from '../../core'\nimport { AnySynapseStore, createSynapseAwaiter } from '../../utils'\n\ninterface ReactAwaitSynapseOptions {\n loadingComponent?: ReactNode\n errorComponent?: (error: Error) => ReactNode\n}\n\n/**\n * React-обертка для фреймворк-независимой утилиты ожидания Synapse\n * Добавляет React-специфичные методы поверх createSynapseAwaiter\n */\nexport function awaitSynapse<TStore extends Record<string, any>, TStorage extends IStorage<TStore>, TSelectors = any, TActions = any>(\n synapseStorePromise: Promise<AnySynapseStore<TStore, TStorage, TSelectors, TActions>> | AnySynapseStore<TStore, TStorage, TSelectors, TActions>,\n options?: ReactAwaitSynapseOptions,\n) {\n const { loadingComponent = <div>Инициализация...</div>, errorComponent = (error: Error) => <div>Ошибка инициализации: {error.message}</div> } = options || {}\n\n const awaiter = createSynapseAwaiter(synapseStorePromise)\n\n /**\n * Хук для получения текущего состояния готовности\n */\n function useSynapseReady() {\n const [status, setStatus] = useState<'pending' | 'ready' | 'error'>(() => awaiter.getStatus())\n const [store, setStore] = useState<AnySynapseStore<TStore, TStorage, TSelectors, TActions> | undefined>(() => awaiter.getStoreIfReady())\n const [error, setError] = useState<Error | null>(() => awaiter.getError())\n\n useEffect(() => {\n // Проверяем текущее состояние при монтировании\n const currentStatus = awaiter.getStatus()\n const currentStore = awaiter.getStoreIfReady()\n const currentError = awaiter.getError()\n\n setStatus(currentStatus)\n setStore(currentStore)\n setError(currentError)\n\n // Подписываемся на изменения\n const unsubscribeReady = awaiter.onReady((readyStore) => {\n setStatus('ready')\n setStore(readyStore)\n setError(null)\n })\n\n const unsubscribeError = awaiter.onError((err) => {\n setStatus('error')\n setStore(undefined)\n setError(err)\n })\n\n return () => {\n unsubscribeReady()\n unsubscribeError()\n }\n }, [])\n\n return {\n isReady: status === 'ready',\n isError: status === 'error',\n isPending: status === 'pending',\n store,\n error,\n }\n }\n\n /**\n * Обертка, которая ждет готовности Synapse\n */\n function withSynapseReady<ComponentProps>(Component: ComponentType<ComponentProps>) {\n function WrappedComponent(props: ComponentProps) {\n const { isReady, isError, error } = useSynapseReady()\n\n // Показываем ошибку\n if (isError && error) return <>{errorComponent(error)}</>\n\n // Показываем загрузку\n if (!isReady) return <>{loadingComponent}</>\n\n // Рендерим компонент когда все готово\n return <Component {...(props as PropsWithChildren<ComponentProps>)} />\n }\n\n // Устанавливаем отображаемое имя для отладки\n const componentName = Component.displayName || Component.name || 'Component'\n WrappedComponent.displayName = `AwaitSynapse(${componentName})`\n\n return WrappedComponent\n }\n\n return {\n // React методы\n withSynapseReady,\n useSynapseReady,\n\n // Проксируем все методы из awaiter (обёртки сохраняют контекст при деструктуризации)\n waitForReady: () => awaiter.waitForReady(),\n isReady: () => awaiter.isReady(),\n getStoreIfReady: () => awaiter.getStoreIfReady(),\n onReady: (cb: Parameters<typeof awaiter.onReady>[0]) => awaiter.onReady(cb),\n onError: (cb: Parameters<typeof awaiter.onError>[0]) => awaiter.onError(cb),\n getStatus: () => awaiter.getStatus(),\n getError: () => awaiter.getError(),\n destroy: () => awaiter.destroy(),\n }\n}\n"],"names":["useEffect","useState","createSynapseAwaiter","awaitSynapse","synapseStorePromise","options","loadingComponent","errorComponent","error","awaiter","useSynapseReady","status","setStatus","store","setStore","setError","currentStatus","currentStore","currentError","unsubscribeReady","readyStore","unsubscribeError","err","undefined","withSynapseReady","Component","WrappedComponent","props","isReady","isError","componentName","cb"],"mappings":";;;;;;;;AAAwF;AAGrB;AAOnE;;;CAGC,GACM,SAASG,YAAYA,CAC1BC,mBAA+I,EAC/IC,OAAkC;IAElC,MAAM,EAAEC,iCAAmB,IAAC;kBAAI;MAAsB,EAAEC,iBAAiB,CAACC,sBAAiB,KAAC;;gBAAI;gBAAuBA,MAAM,OAAO;;UAAO,EAAE,GAAGH,WAAW,CAAC;IAE5J,MAAMI,UAAUP,oBAAoBA,CAACE;IAErC;;GAEC,GACD,SAASM;QACP,MAAM,CAACC,QAAQC,UAAU,GAAGX,QAAQA,CAAgC,IAAMQ,QAAQ,SAAS;QAC3F,MAAM,CAACI,OAAOC,SAAS,GAAGb,QAAQA,CAAsE,IAAMQ,QAAQ,eAAe;QACrI,MAAM,CAACD,OAAOO,SAAS,GAAGd,QAAQA,CAAe,IAAMQ,QAAQ,QAAQ;QAEvET,SAASA,CAAC;YACR,+CAA+C;YAC/C,MAAMgB,gBAAgBP,QAAQ,SAAS;YACvC,MAAMQ,eAAeR,QAAQ,eAAe;YAC5C,MAAMS,eAAeT,QAAQ,QAAQ;YAErCG,UAAUI;YACVF,SAASG;YACTF,SAASG;YAET,6BAA6B;YAC7B,MAAMC,mBAAmBV,QAAQ,OAAO,CAAC,CAACW;gBACxCR,UAAU;gBACVE,SAASM;gBACTL,SAAS;YACX;YAEA,MAAMM,mBAAmBZ,QAAQ,OAAO,CAAC,CAACa;gBACxCV,UAAU;gBACVE,SAASS;gBACTR,SAASO;YACX;YAEA,OAAO;gBACLH;gBACAE;YACF;QACF,GAAG,EAAE;QAEL,OAAO;YACL,SAASV,WAAW;YACpB,SAASA,WAAW;YACpB,WAAWA,WAAW;YACtBE;YACAL;QACF;IACF;IAEA;;GAEC,GACD,SAASgB,iBAAiCC,SAAwC;QAChF,SAASC,iBAAiBC,KAAqB;YAC7C,MAAM,EAAEC,OAAO,EAAEC,OAAO,EAAErB,KAAK,EAAE,GAAGE;YAEpC,oBAAoB;YACpB,IAAImB,WAAWrB,OAAO,qBAAO;0BAAGD,eAAeC;;YAE/C,sBAAsB;YACtB,IAAI,CAACoB,SAAS,qBAAO;0BAAGtB;;YAExB,sCAAsC;YACtC,qBAAO,IAACmB;gBAAW,GAAIE,KAAK;;QAC9B;QAEA,6CAA6C;QAC7C,MAAMG,gBAAgBL,UAAU,WAAW,IAAIA,UAAU,IAAI,IAAI;QACjEC,iBAAiB,WAAW,GAAG,CAAC,aAAa,EAAEI,cAAc,CAAC,CAAC;QAE/D,OAAOJ;IACT;IAEA,OAAO;QACL,eAAe;QACfF;QACAd;QAEA,qFAAqF;QACrF,cAAc,IAAMD,QAAQ,YAAY;QACxC,SAAS,IAAMA,QAAQ,OAAO;QAC9B,iBAAiB,IAAMA,QAAQ,eAAe;QAC9C,SAAS,CAACsB,KAA8CtB,QAAQ,OAAO,CAACsB;QACxE,SAAS,CAACA,KAA8CtB,QAAQ,OAAO,CAACsB;QACxE,WAAW,IAAMtB,QAAQ,SAAS;QAClC,UAAU,IAAMA,QAAQ,QAAQ;QAChC,SAAS,IAAMA,QAAQ,OAAO;IAChC;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createSynapseCtx.d.ts","sourceRoot":"","sources":["../../../src/react/utils/createSynapseCtx.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAiF,MAAM,OAAO,CAAA;AACpH,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAA;AAGjC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrC,OAAO,
|
|
1
|
+
{"version":3,"file":"createSynapseCtx.d.ts","sourceRoot":"","sources":["../../../src/react/utils/createSynapseCtx.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAiF,MAAM,OAAO,CAAA;AACpH,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAA;AAGjC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrC,OAAO,EAAyC,iBAAiB,EAAE,0BAA0B,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAK3I,UAAU,iBAAiB;IACzB,gBAAgB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACnC;AAED;;GAEG;AAGH,wBAAgB,gBAAgB,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,SAAS,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,QAAQ,EAC1H,mBAAmB,EAAE,OAAO,CAAC,uBAAuB,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,GAAG,uBAAuB,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,CAAC,EAC/J,OAAO,CAAC,EAAE,iBAAiB,GAC1B;IACD,cAAc,EAAE,CAAC,kBAAkB,EAAE,SAAS,EAAE,aAAa,CAAC,kBAAkB,CAAC,KAAK,aAAa,CAAC,kBAAkB,CAAC,CAAA;IACvH,iBAAiB,EAAE,MAAM,QAAQ,CAAA;IACjC,mBAAmB,EAAE,MAAM,UAAU,CAAA;IACrC,iBAAiB,EAAE,MAAM,QAAQ,CAAA;IACjC,gBAAgB,EAAE,MAAM,UAAU,CAAC,MAAM,CAAC,CAAA;IAC1C,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACpC,CAAA;AAGD,wBAAgB,gBAAgB,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,SAAS,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,QAAQ,EAC1H,mBAAmB,EAAE,OAAO,CAAC,0BAA0B,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,GAAG,0BAA0B,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,CAAC,EACrK,OAAO,CAAC,EAAE,iBAAiB,GAC1B;IACD,cAAc,EAAE,CAAC,kBAAkB,EAAE,SAAS,EAAE,aAAa,CAAC,kBAAkB,CAAC,KAAK,aAAa,CAAC,kBAAkB,CAAC,CAAA;IACvH,iBAAiB,EAAE,MAAM,QAAQ,CAAA;IACjC,mBAAmB,EAAE,MAAM,UAAU,CAAA;IACrC,iBAAiB,EAAE,MAAM,QAAQ,CAAA;IACjC,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACpC,CAAA;AAGD,wBAAgB,gBAAgB,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,SAAS,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,EAChH,mBAAmB,EAAE,OAAO,CAAC,iBAAiB,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC,GAAG,iBAAiB,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,EAC/H,OAAO,CAAC,EAAE,iBAAiB,GAC1B;IACD,cAAc,EAAE,CAAC,kBAAkB,EAAE,SAAS,EAAE,aAAa,CAAC,kBAAkB,CAAC,KAAK,aAAa,CAAC,kBAAkB,CAAC,CAAA;IACvH,iBAAiB,EAAE,MAAM,QAAQ,CAAA;IACjC,mBAAmB,EAAE,MAAM,UAAU,CAAA;IACrC,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACpC,CAAA"}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
1
2
|
import { createContext, forwardRef, useContext, useEffect, useState } from "react";
|
|
2
|
-
import { handleCleanupError
|
|
3
|
+
import { handleCleanupError } from "../../_utils/error-handling.util.js";
|
|
4
|
+
import { createSynapseAwaiter } from "../../utils/index.js";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
3
9
|
|
|
4
10
|
|
|
5
11
|
|
|
@@ -9,22 +15,15 @@ const ERROR_HOOK_MESSAGE = 'Хук необходимо использовать
|
|
|
9
15
|
const ERROR_CONTEXT_INIT = 'Ошибка при инициализации контекста:';
|
|
10
16
|
// Основная реализация
|
|
11
17
|
function createSynapseCtx(synapseStorePromise, options) {
|
|
12
|
-
const { loadingComponent = /*#__PURE__*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return store;
|
|
22
|
-
} catch (error) {
|
|
23
|
-
handleOperationError('createSynapseCtx: Synapse storage initialization error', error);
|
|
24
|
-
}
|
|
25
|
-
})();
|
|
26
|
-
}
|
|
27
|
-
return storeInitPromise;
|
|
18
|
+
const { loadingComponent = /*#__PURE__*/ jsx("div", {
|
|
19
|
+
children: "Инициализация контекста..."
|
|
20
|
+
}) } = options || {};
|
|
21
|
+
// Lazy-инициализация: awaiter создаётся при первом обращении и сбрасывается при cleanup.
|
|
22
|
+
// Сам awaiter (createSynapseAwaiter) инкапсулирует ожидание готовности, статус и подписки.
|
|
23
|
+
let awaiter = null;
|
|
24
|
+
const getAwaiter = ()=>{
|
|
25
|
+
if (!awaiter) awaiter = createSynapseAwaiter(synapseStorePromise);
|
|
26
|
+
return awaiter;
|
|
28
27
|
};
|
|
29
28
|
const SynapseContext = /*#__PURE__*/ createContext(null);
|
|
30
29
|
const useSynapseStorage = ()=>{
|
|
@@ -59,39 +58,41 @@ function createSynapseCtx(synapseStorePromise, options) {
|
|
|
59
58
|
* Декоратор для обертывания компонентов в контекст Synapse
|
|
60
59
|
*/ function contextSynapse(Component) {
|
|
61
60
|
const WrappedComponent = /*#__PURE__*/ forwardRef(function WrappedComponent(props, ref) {
|
|
62
|
-
const [synapseStore, setSynapseStore] = useState(
|
|
63
|
-
const [
|
|
64
|
-
const [error, setError] = useState(null);
|
|
61
|
+
const [synapseStore, setSynapseStore] = useState(()=>getAwaiter().getStoreIfReady());
|
|
62
|
+
const [error, setError] = useState(()=>getAwaiter().getError());
|
|
65
63
|
useEffect(()=>{
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
initializeContext();
|
|
64
|
+
// awaiter мог поменять состояние между рендером и эффектом — синхронизируемся
|
|
65
|
+
const instance = getAwaiter();
|
|
66
|
+
setSynapseStore(instance.getStoreIfReady());
|
|
67
|
+
setError(instance.getError());
|
|
68
|
+
const unsubscribeReady = instance.onReady((store)=>{
|
|
69
|
+
setSynapseStore(store);
|
|
70
|
+
setError(null);
|
|
71
|
+
});
|
|
72
|
+
const unsubscribeError = instance.onError((err)=>{
|
|
73
|
+
setSynapseStore(undefined);
|
|
74
|
+
setError(err);
|
|
75
|
+
});
|
|
81
76
|
return ()=>{
|
|
82
|
-
|
|
77
|
+
unsubscribeReady();
|
|
78
|
+
unsubscribeError();
|
|
83
79
|
};
|
|
84
80
|
}, []);
|
|
85
81
|
// Показываем ошибку если что-то пошло не так
|
|
86
|
-
if (error) return /*#__PURE__*/
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
82
|
+
if (error) return /*#__PURE__*/ jsx("div", {
|
|
83
|
+
children: `${ERROR_CONTEXT_INIT} ${error.message}`
|
|
84
|
+
});
|
|
85
|
+
// Показываем загрузку пока store не готов
|
|
86
|
+
if (!synapseStore) return /*#__PURE__*/ jsx(Fragment, {
|
|
87
|
+
children: loadingComponent
|
|
88
|
+
});
|
|
89
|
+
return /*#__PURE__*/ jsx(SynapseContext.Provider, {
|
|
90
|
+
value: synapseStore,
|
|
91
|
+
children: /*#__PURE__*/ jsx(Component, {
|
|
92
|
+
...props,
|
|
93
|
+
ref: ref
|
|
94
|
+
})
|
|
95
|
+
});
|
|
95
96
|
});
|
|
96
97
|
// Устанавливаем отображаемое имя для отладки
|
|
97
98
|
const componentName = Component.displayName || Component.name || 'Component';
|
|
@@ -113,14 +114,15 @@ function createSynapseCtx(synapseStorePromise, options) {
|
|
|
113
114
|
return WrappedComponent;
|
|
114
115
|
}
|
|
115
116
|
const cleanupSynapse = async ()=>{
|
|
117
|
+
if (!awaiter) return;
|
|
118
|
+
const instance = awaiter;
|
|
119
|
+
awaiter = null; // сбрасываем сразу, чтобы следующий маунт создал новый awaiter
|
|
116
120
|
try {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return store?.destroy() || Promise.resolve();
|
|
121
|
-
}
|
|
121
|
+
const store = instance.getStoreIfReady() ?? await instance.waitForReady();
|
|
122
|
+
instance.destroy();
|
|
123
|
+
await store?.destroy();
|
|
122
124
|
} catch (error) {
|
|
123
|
-
|
|
125
|
+
instance.destroy();
|
|
124
126
|
handleCleanupError('createSynapseCtx: error during Synapse cleanup', error);
|
|
125
127
|
}
|
|
126
128
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react/utils/createSynapseCtx.js","sources":["../../../src/react/utils/createSynapseCtx.tsx"],"sourcesContent":["import { ComponentType, createContext, forwardRef, PropsWithChildren, useContext, useEffect, useState } from 'react'\nimport { Observable } from 'rxjs'\n\nimport { handleCleanupError, handleOperationError } from '../../_utils/error-handling.util'\nimport { IStorage } from '../../core'\nimport { AnySynapseStore, SynapseStoreBasic, SynapseStoreWithDispatcher, SynapseStoreWithEffects } from '../../utils'\n\nconst ERROR_HOOK_MESSAGE = 'Хук необходимо использовать внутри компонента contextSynapse'\nconst ERROR_CONTEXT_INIT = 'Ошибка при инициализации контекста:'\n\ninterface SimplifiedOptions {\n loadingComponent?: React.ReactNode\n}\n\n/**\n * Перегрузки для createSynapseCtx в зависимости от типа хранилища\n */\n\n// Для хранилища с effects\nexport function createSynapseCtx<TStore extends Record<string, any>, TStorage extends IStorage<TStore>, TSelectors, TActions>(\n synapseStorePromise: Promise<SynapseStoreWithEffects<TStore, TStorage, TSelectors, TActions>> | SynapseStoreWithEffects<TStore, TStorage, TSelectors, TActions>,\n options?: SimplifiedOptions,\n): {\n contextSynapse: <SelfComponentProps>(Component: ComponentType<SelfComponentProps>) => ComponentType<SelfComponentProps>\n useSynapseStorage: () => TStorage\n useSynapseSelectors: () => TSelectors\n useSynapseActions: () => TActions\n useSynapseState$: () => Observable<TStore>\n cleanupSynapse: () => Promise<void>\n}\n\n// Для хранилища с dispatcher (без effects)\nexport function createSynapseCtx<TStore extends Record<string, any>, TStorage extends IStorage<TStore>, TSelectors, TActions>(\n synapseStorePromise: Promise<SynapseStoreWithDispatcher<TStore, TStorage, TSelectors, TActions>> | SynapseStoreWithDispatcher<TStore, TStorage, TSelectors, TActions>,\n options?: SimplifiedOptions,\n): {\n contextSynapse: <SelfComponentProps>(Component: ComponentType<SelfComponentProps>) => ComponentType<SelfComponentProps>\n useSynapseStorage: () => TStorage\n useSynapseSelectors: () => TSelectors\n useSynapseActions: () => TActions\n cleanupSynapse: () => Promise<void>\n}\n\n// Для базового хранилища\nexport function createSynapseCtx<TStore extends Record<string, any>, TStorage extends IStorage<TStore>, TSelectors>(\n synapseStorePromise: Promise<SynapseStoreBasic<TStore, TStorage, TSelectors>> | SynapseStoreBasic<TStore, TStorage, TSelectors>,\n options?: SimplifiedOptions,\n): {\n contextSynapse: <SelfComponentProps>(Component: ComponentType<SelfComponentProps>) => ComponentType<SelfComponentProps>\n useSynapseStorage: () => TStorage\n useSynapseSelectors: () => TSelectors\n cleanupSynapse: () => Promise<void>\n}\n\n// Основная реализация\nexport function createSynapseCtx<TStore extends Record<string, any>, TStorage extends IStorage<TStore>, TSelectors = any, TActions = any>(\n synapseStorePromise: Promise<AnySynapseStore<TStore, TStorage, TSelectors, TActions>> | AnySynapseStore<TStore, TStorage, TSelectors, TActions>,\n options?: SimplifiedOptions,\n) {\n const { loadingComponent = <div>Инициализация контекста...</div> } = options || {}\n\n // Lazy-инициализация: Promise создаётся при первом обращении и сбрасывается при cleanup\n let storeInitPromise: Promise<AnySynapseStore<TStore, TStorage, TSelectors, TActions>> | null = null\n\n const getStoreInitPromise = () => {\n if (!storeInitPromise) {\n storeInitPromise = (async () => {\n try {\n const store = await (synapseStorePromise instanceof Promise ? synapseStorePromise : Promise.resolve(synapseStorePromise))\n await store.storage.waitForReady()\n return store\n } catch (error) {\n handleOperationError('createSynapseCtx: Synapse storage initialization error', error)\n }\n })()\n }\n return storeInitPromise\n }\n\n const SynapseContext = createContext<AnySynapseStore<TStore, TStorage, TSelectors, TActions> | null>(null)\n\n const useSynapseStorage = (): TStorage => {\n const context = useContext(SynapseContext)\n if (!context) throw new Error(`useSynapseStorage: ${ERROR_HOOK_MESSAGE}`)\n return context.storage\n }\n\n const useSynapseSelectors = (): TSelectors => {\n const context = useContext(SynapseContext)\n if (!context) throw new Error(`useSynapseSelectors: ${ERROR_HOOK_MESSAGE}`)\n return context.selectors\n }\n\n // Условный хук для actions (только если есть dispatcher)\n const useSynapseActions = (): TActions => {\n const context = useContext(SynapseContext)\n if (!context) throw new Error(`useSynapseActions: ${ERROR_HOOK_MESSAGE}`)\n\n if ('actions' in context) {\n return (context as SynapseStoreWithDispatcher<TStore, TStorage, TSelectors, TActions> | SynapseStoreWithEffects<TStore, TStorage, TSelectors, TActions>).actions\n }\n\n throw new Error('useSynapseActions: actions недоступны для этого типа хранилища. Убедитесь, что передана функция createDispatcherFn при создании хранилища.')\n }\n\n // Условный хук для state$ (только если есть effects)\n const useSynapseState$ = (): Observable<TStore> => {\n const context = useContext(SynapseContext)\n if (!context) throw new Error(`useSynapseState$: ${ERROR_HOOK_MESSAGE}`)\n\n if ('state$' in context) {\n return (context as SynapseStoreWithEffects<TStore, TStorage, TSelectors, TActions>).state$\n }\n\n throw new Error('useSynapseState$: state$ недоступен для этого типа хранилища. Убедитесь, что переданы функции createDispatcherFn и createEffectConfig при создании хранилища.')\n }\n\n /**\n * Декоратор для обертывания компонентов в контекст Synapse\n */\n function contextSynapse<SelfComponentProps>(Component: ComponentType<SelfComponentProps>) {\n const WrappedComponent = forwardRef<unknown, SelfComponentProps>(function WrappedComponent(props, ref) {\n const [synapseStore, setSynapseStore] = useState<AnySynapseStore<TStore, TStorage, TSelectors, TActions> | null>(null)\n const [isReady, setIsReady] = useState(false)\n const [error, setError] = useState<Error | null>(null)\n\n useEffect(() => {\n let mounted = true\n\n const initializeContext = async () => {\n try {\n const store = await getStoreInitPromise()\n\n if (mounted) {\n setSynapseStore(store)\n setIsReady(true)\n }\n } catch (err) {\n if (mounted) {\n setError(err instanceof Error ? err : new Error(String(err)))\n }\n }\n }\n\n initializeContext()\n\n return () => {\n mounted = false\n }\n }, [])\n\n // Показываем ошибку если что-то пошло не так\n if (error) return <div>{`${ERROR_CONTEXT_INIT}: ${error.message}`}</div>\n\n // Показываем загрузку пока все не готово\n if (!isReady || !synapseStore) return <>{loadingComponent}</>\n\n return (\n <SynapseContext.Provider value={synapseStore}>\n <Component {...(props as PropsWithChildren<SelfComponentProps>)} ref={ref} />\n </SynapseContext.Provider>\n )\n })\n\n // Устанавливаем отображаемое имя для отладки\n const componentName = Component.displayName || Component.name || 'Component'\n WrappedComponent.displayName = `SynapseContext(${componentName})`\n\n // Копируем статические свойства оригинального компонента\n const excludedKeys = new Set(['$$typeof', 'render', 'defaultProps', 'displayName', 'propTypes'])\n Object.keys(Component).forEach((key) => {\n if (!excludedKeys.has(key)) {\n ;(WrappedComponent as any)[key] = (Component as any)[key]\n }\n })\n\n return WrappedComponent as ComponentType<SelfComponentProps>\n }\n\n const cleanupSynapse = async (): Promise<void> => {\n try {\n if (storeInitPromise) {\n const store = await storeInitPromise\n storeInitPromise = null\n return store?.destroy() || Promise.resolve()\n }\n } catch (error) {\n storeInitPromise = null\n handleCleanupError('createSynapseCtx: error during Synapse cleanup', error)\n }\n }\n\n return {\n contextSynapse,\n useSynapseStorage,\n useSynapseSelectors,\n useSynapseActions,\n useSynapseState$,\n cleanupSynapse,\n }\n}\n"],"names":["createContext","forwardRef","useContext","useEffect","useState","handleCleanupError","handleOperationError","ERROR_HOOK_MESSAGE","ERROR_CONTEXT_INIT","createSynapseCtx","synapseStorePromise","options","loadingComponent","storeInitPromise","getStoreInitPromise","store","Promise","error","SynapseContext","useSynapseStorage","context","Error","useSynapseSelectors","useSynapseActions","useSynapseState$","contextSynapse","Component","WrappedComponent","props","ref","synapseStore","setSynapseStore","isReady","setIsReady","setError","mounted","initializeContext","err","String","componentName","excludedKeys","Set","Object","key","cleanupSynapse"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"react/utils/createSynapseCtx.js","sources":["../../../src/react/utils/createSynapseCtx.tsx"],"sourcesContent":["import { ComponentType, createContext, forwardRef, PropsWithChildren, useContext, useEffect, useState } from 'react'\nimport { Observable } from 'rxjs'\n\nimport { handleCleanupError } from '../../_utils/error-handling.util'\nimport { IStorage } from '../../core'\nimport { AnySynapseStore, createSynapseAwaiter, SynapseStoreBasic, SynapseStoreWithDispatcher, SynapseStoreWithEffects } from '../../utils'\n\nconst ERROR_HOOK_MESSAGE = 'Хук необходимо использовать внутри компонента contextSynapse'\nconst ERROR_CONTEXT_INIT = 'Ошибка при инициализации контекста:'\n\ninterface SimplifiedOptions {\n loadingComponent?: React.ReactNode\n}\n\n/**\n * Перегрузки для createSynapseCtx в зависимости от типа хранилища\n */\n\n// Для хранилища с effects\nexport function createSynapseCtx<TStore extends Record<string, any>, TStorage extends IStorage<TStore>, TSelectors, TActions>(\n synapseStorePromise: Promise<SynapseStoreWithEffects<TStore, TStorage, TSelectors, TActions>> | SynapseStoreWithEffects<TStore, TStorage, TSelectors, TActions>,\n options?: SimplifiedOptions,\n): {\n contextSynapse: <SelfComponentProps>(Component: ComponentType<SelfComponentProps>) => ComponentType<SelfComponentProps>\n useSynapseStorage: () => TStorage\n useSynapseSelectors: () => TSelectors\n useSynapseActions: () => TActions\n useSynapseState$: () => Observable<TStore>\n cleanupSynapse: () => Promise<void>\n}\n\n// Для хранилища с dispatcher (без effects)\nexport function createSynapseCtx<TStore extends Record<string, any>, TStorage extends IStorage<TStore>, TSelectors, TActions>(\n synapseStorePromise: Promise<SynapseStoreWithDispatcher<TStore, TStorage, TSelectors, TActions>> | SynapseStoreWithDispatcher<TStore, TStorage, TSelectors, TActions>,\n options?: SimplifiedOptions,\n): {\n contextSynapse: <SelfComponentProps>(Component: ComponentType<SelfComponentProps>) => ComponentType<SelfComponentProps>\n useSynapseStorage: () => TStorage\n useSynapseSelectors: () => TSelectors\n useSynapseActions: () => TActions\n cleanupSynapse: () => Promise<void>\n}\n\n// Для базового хранилища\nexport function createSynapseCtx<TStore extends Record<string, any>, TStorage extends IStorage<TStore>, TSelectors>(\n synapseStorePromise: Promise<SynapseStoreBasic<TStore, TStorage, TSelectors>> | SynapseStoreBasic<TStore, TStorage, TSelectors>,\n options?: SimplifiedOptions,\n): {\n contextSynapse: <SelfComponentProps>(Component: ComponentType<SelfComponentProps>) => ComponentType<SelfComponentProps>\n useSynapseStorage: () => TStorage\n useSynapseSelectors: () => TSelectors\n cleanupSynapse: () => Promise<void>\n}\n\n// Основная реализация\nexport function createSynapseCtx<TStore extends Record<string, any>, TStorage extends IStorage<TStore>, TSelectors = any, TActions = any>(\n synapseStorePromise: Promise<AnySynapseStore<TStore, TStorage, TSelectors, TActions>> | AnySynapseStore<TStore, TStorage, TSelectors, TActions>,\n options?: SimplifiedOptions,\n) {\n const { loadingComponent = <div>Инициализация контекста...</div> } = options || {}\n\n // Lazy-инициализация: awaiter создаётся при первом обращении и сбрасывается при cleanup.\n // Сам awaiter (createSynapseAwaiter) инкапсулирует ожидание готовности, статус и подписки.\n let awaiter: ReturnType<typeof createSynapseAwaiter<TStore, TStorage, TSelectors, TActions>> | null = null\n\n const getAwaiter = () => {\n if (!awaiter) awaiter = createSynapseAwaiter(synapseStorePromise)\n return awaiter\n }\n\n const SynapseContext = createContext<AnySynapseStore<TStore, TStorage, TSelectors, TActions> | null>(null)\n\n const useSynapseStorage = (): TStorage => {\n const context = useContext(SynapseContext)\n if (!context) throw new Error(`useSynapseStorage: ${ERROR_HOOK_MESSAGE}`)\n return context.storage\n }\n\n const useSynapseSelectors = (): TSelectors => {\n const context = useContext(SynapseContext)\n if (!context) throw new Error(`useSynapseSelectors: ${ERROR_HOOK_MESSAGE}`)\n return context.selectors\n }\n\n // Условный хук для actions (только если есть dispatcher)\n const useSynapseActions = (): TActions => {\n const context = useContext(SynapseContext)\n if (!context) throw new Error(`useSynapseActions: ${ERROR_HOOK_MESSAGE}`)\n\n if ('actions' in context) {\n return (context as SynapseStoreWithDispatcher<TStore, TStorage, TSelectors, TActions> | SynapseStoreWithEffects<TStore, TStorage, TSelectors, TActions>).actions\n }\n\n throw new Error('useSynapseActions: actions недоступны для этого типа хранилища. Убедитесь, что передана функция createDispatcherFn при создании хранилища.')\n }\n\n // Условный хук для state$ (только если есть effects)\n const useSynapseState$ = (): Observable<TStore> => {\n const context = useContext(SynapseContext)\n if (!context) throw new Error(`useSynapseState$: ${ERROR_HOOK_MESSAGE}`)\n\n if ('state$' in context) {\n return (context as SynapseStoreWithEffects<TStore, TStorage, TSelectors, TActions>).state$\n }\n\n throw new Error('useSynapseState$: state$ недоступен для этого типа хранилища. Убедитесь, что переданы функции createDispatcherFn и createEffectConfig при создании хранилища.')\n }\n\n /**\n * Декоратор для обертывания компонентов в контекст Synapse\n */\n function contextSynapse<SelfComponentProps>(Component: ComponentType<SelfComponentProps>) {\n const WrappedComponent = forwardRef<unknown, SelfComponentProps>(function WrappedComponent(props, ref) {\n const [synapseStore, setSynapseStore] = useState<AnySynapseStore<TStore, TStorage, TSelectors, TActions> | undefined>(() => getAwaiter().getStoreIfReady())\n const [error, setError] = useState<Error | null>(() => getAwaiter().getError())\n\n useEffect(() => {\n // awaiter мог поменять состояние между рендером и эффектом — синхронизируемся\n const instance = getAwaiter()\n setSynapseStore(instance.getStoreIfReady())\n setError(instance.getError())\n\n const unsubscribeReady = instance.onReady((store) => {\n setSynapseStore(store)\n setError(null)\n })\n const unsubscribeError = instance.onError((err) => {\n setSynapseStore(undefined)\n setError(err)\n })\n\n return () => {\n unsubscribeReady()\n unsubscribeError()\n }\n }, [])\n\n // Показываем ошибку если что-то пошло не так\n if (error) return <div>{`${ERROR_CONTEXT_INIT} ${error.message}`}</div>\n\n // Показываем загрузку пока store не готов\n if (!synapseStore) return <>{loadingComponent}</>\n\n return (\n <SynapseContext.Provider value={synapseStore}>\n <Component {...(props as PropsWithChildren<SelfComponentProps>)} ref={ref} />\n </SynapseContext.Provider>\n )\n })\n\n // Устанавливаем отображаемое имя для отладки\n const componentName = Component.displayName || Component.name || 'Component'\n WrappedComponent.displayName = `SynapseContext(${componentName})`\n\n // Копируем статические свойства оригинального компонента\n const excludedKeys = new Set(['$$typeof', 'render', 'defaultProps', 'displayName', 'propTypes'])\n Object.keys(Component).forEach((key) => {\n if (!excludedKeys.has(key)) {\n ;(WrappedComponent as any)[key] = (Component as any)[key]\n }\n })\n\n return WrappedComponent as ComponentType<SelfComponentProps>\n }\n\n const cleanupSynapse = async (): Promise<void> => {\n if (!awaiter) return\n\n const instance = awaiter\n awaiter = null // сбрасываем сразу, чтобы следующий маунт создал новый awaiter\n\n try {\n const store = instance.getStoreIfReady() ?? (await instance.waitForReady())\n instance.destroy()\n await store?.destroy()\n } catch (error) {\n instance.destroy()\n handleCleanupError('createSynapseCtx: error during Synapse cleanup', error)\n }\n }\n\n return {\n contextSynapse,\n useSynapseStorage,\n useSynapseSelectors,\n useSynapseActions,\n useSynapseState$,\n cleanupSynapse,\n }\n}\n"],"names":["createContext","forwardRef","useContext","useEffect","useState","handleCleanupError","createSynapseAwaiter","ERROR_HOOK_MESSAGE","ERROR_CONTEXT_INIT","createSynapseCtx","synapseStorePromise","options","loadingComponent","awaiter","getAwaiter","SynapseContext","useSynapseStorage","context","Error","useSynapseSelectors","useSynapseActions","useSynapseState$","contextSynapse","Component","WrappedComponent","props","ref","synapseStore","setSynapseStore","error","setError","instance","unsubscribeReady","store","unsubscribeError","err","undefined","componentName","excludedKeys","Set","Object","key","cleanupSynapse"],"mappings":"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"}
|
|
@@ -30,10 +30,29 @@ export interface WatcherRecipe<TState extends Record<string, any>, R> {
|
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
33
|
+
* Статусы жизненного цикла API-запроса.
|
|
34
|
+
*
|
|
35
|
+
* Сделано const-объектом (а не TS `enum`) намеренно: значения остаются обычными
|
|
36
|
+
* строковыми литералами, поэтому `ApiStatus.Loading` и строка `'loading'`
|
|
37
|
+
* взаимозаменяемы и тип обратно совместим с прежним строковым union'ом. С `enum`
|
|
38
|
+
* это не так — его член не присваивается к литералу и наоборот.
|
|
39
|
+
*
|
|
40
|
+
* `ApiStatus` — одновременно значение (для `ApiStatus.Loading` в коде) и тип
|
|
41
|
+
* (union всех статусов).
|
|
42
|
+
*/
|
|
43
|
+
export declare const ApiStatus: {
|
|
44
|
+
readonly Idle: "idle";
|
|
45
|
+
readonly Loading: "loading";
|
|
46
|
+
readonly Success: "success";
|
|
47
|
+
readonly Error: "error";
|
|
48
|
+
readonly Reset: "reset";
|
|
49
|
+
};
|
|
50
|
+
export type ApiStatus = (typeof ApiStatus)[keyof typeof ApiStatus];
|
|
51
|
+
/**
|
|
52
|
+
* Состояние API-запроса для createApiActions / createKeyedApiActions
|
|
34
53
|
*/
|
|
35
54
|
export interface ApiRequestState {
|
|
36
|
-
status:
|
|
55
|
+
status: ApiStatus;
|
|
37
56
|
error: string | null;
|
|
38
57
|
}
|
|
39
58
|
/**
|
|
@@ -86,6 +105,11 @@ export declare function defineWatcher<TState extends Record<string, any>>(): <R>
|
|
|
86
105
|
*
|
|
87
106
|
* @param accessor - Функция-accessor, указывающая на поле ApiRequestState в стейте
|
|
88
107
|
*
|
|
108
|
+
* @typeParam TInitPayload - Тип payload'а `init`-экшена. По умолчанию `void`
|
|
109
|
+
* (init без параметров). Если задать — `init` принимает payload и возвращает
|
|
110
|
+
* его, что удобно для intent-паттерна: эффект слушает `init` и читает payload
|
|
111
|
+
* намерения (target, фильтры и т.п.), а статус при этом сбрасывается в `idle`.
|
|
112
|
+
*
|
|
89
113
|
* @example
|
|
90
114
|
* ```ts
|
|
91
115
|
* const listRequest = createApiActions<MyState>(
|
|
@@ -100,13 +124,58 @@ export declare function defineWatcher<TState extends Record<string, any>>(): <R>
|
|
|
100
124
|
* loadListFailure: listRequest.failure,
|
|
101
125
|
* loadListReset: listRequest.reset,
|
|
102
126
|
* })
|
|
127
|
+
*
|
|
128
|
+
* // init с payload (intent): эффект получит { entityId } из возврата экшена.
|
|
129
|
+
* const usersReq = createApiActions<MyState, { entityId: string }>(
|
|
130
|
+
* (draft) => draft.api.usersRequest
|
|
131
|
+
* )
|
|
103
132
|
* ```
|
|
104
133
|
*/
|
|
105
|
-
export declare function createApiActions<TState extends Record<string, any
|
|
106
|
-
init: ActionRecipe<TState,
|
|
134
|
+
export declare function createApiActions<TState extends Record<string, any>, TInitPayload = void>(accessor: (draft: TState) => ApiRequestState): {
|
|
135
|
+
init: ActionRecipe<TState, TInitPayload, TInitPayload>;
|
|
107
136
|
loading: ActionRecipe<TState, void, void>;
|
|
108
137
|
success: ActionRecipe<TState, void, void>;
|
|
109
138
|
failure: ActionRecipe<TState, string, void>;
|
|
110
139
|
reset: ActionRecipe<TState, void, void>;
|
|
111
140
|
};
|
|
141
|
+
/**
|
|
142
|
+
* Keyed-вариант createApiActions: статус хранится ПО КЛЮЧУ в `Record<string,
|
|
143
|
+
* ApiRequestState>`, а не один на весь запрос. Нужен, когда один и тот же запрос
|
|
144
|
+
* летит параллельно для нескольких независимых ключей и у каждого свой
|
|
145
|
+
* loading/error: комменты по таргетам, детали сущностей по id, per-row действия
|
|
146
|
+
* в таблице/ленте, пагинация по секциям — всё, что лежит как `Record<key, data>`.
|
|
147
|
+
*
|
|
148
|
+
* Все статус-экшены принимают `key` (и возвращают его — удобно эффектам), кроме
|
|
149
|
+
* `failure`, который принимает `{ key, error }`. Записи мутируются иммутабельно
|
|
150
|
+
* по одному ключу — соседние ключи (их срезы) по ссылке не затрагиваются, что и
|
|
151
|
+
* нужно для гранулярной изоляции ре-рендеров (см. useKeyedSliceSelector).
|
|
152
|
+
*
|
|
153
|
+
* @param accessor - Функция-accessor, указывающая на поле `Record<string, ApiRequestState>`
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```ts
|
|
157
|
+
* const commentsReq = createKeyedApiActions<MyState>((d) => d.api.commentsRequest)
|
|
158
|
+
*
|
|
159
|
+
* createDispatcher({ storage }, {
|
|
160
|
+
* commentsInit: commentsReq.init, // (key) => key
|
|
161
|
+
* commentsLoading: commentsReq.loading, // (key) => key
|
|
162
|
+
* commentsSuccess: commentsReq.success, // (key) => key
|
|
163
|
+
* commentsFailure: commentsReq.failure, // ({ key, error })
|
|
164
|
+
* commentsReset: commentsReq.reset, // (key) => key
|
|
165
|
+
* })
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
export declare function createKeyedApiActions<TState extends Record<string, any>>(accessor: (draft: TState) => Record<string, ApiRequestState>): {
|
|
169
|
+
init: ActionRecipe<TState, string, string>;
|
|
170
|
+
loading: ActionRecipe<TState, string, string>;
|
|
171
|
+
success: ActionRecipe<TState, string, string>;
|
|
172
|
+
reset: ActionRecipe<TState, string, string>;
|
|
173
|
+
failure: ActionRecipe<TState, {
|
|
174
|
+
key: string;
|
|
175
|
+
error: string;
|
|
176
|
+
}, {
|
|
177
|
+
key: string;
|
|
178
|
+
error: string;
|
|
179
|
+
}>;
|
|
180
|
+
};
|
|
112
181
|
//# sourceMappingURL=standalone.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"standalone.d.ts","sourceRoot":"","sources":["../../../src/reactive/dispatcher/standalone.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAM1C;;GAEG;AACH,MAAM,WAAW,sBAAsB,CAAC,OAAO,EAAE,OAAO;IACtD,OAAO,CAAC,EAAE,CAAC,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,KAAK,OAAO,CAAA;CAC5F;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,OAAO;IAChF,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAA;IAC/B,QAAQ,CAAC,OAAO,EAAE;QAChB,MAAM,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;QAClF,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAC3B,CAAA;IACD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,sBAAsB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;CACtE;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;IAClE,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAA;IAChC,QAAQ,CAAC,OAAO,EAAE;QAChB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,CAAC,CAAA;QAC9B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QAC1B,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,SAAS,EAAE,OAAO,EAAE,CAAC,KAAK,OAAO,CAAA;QAC5D,oBAAoB,CAAC,EAAE,OAAO,CAAA;KAC/B,CAAA;CACF;AAED
|
|
1
|
+
{"version":3,"file":"standalone.d.ts","sourceRoot":"","sources":["../../../src/reactive/dispatcher/standalone.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Статусы жизненного цикла API-запроса.
|
|
3
|
+
*
|
|
4
|
+
* Сделано const-объектом (а не TS `enum`) намеренно: значения остаются обычными
|
|
5
|
+
* строковыми литералами, поэтому `ApiStatus.Loading` и строка `'loading'`
|
|
6
|
+
* взаимозаменяемы и тип обратно совместим с прежним строковым union'ом. С `enum`
|
|
7
|
+
* это не так — его член не присваивается к литералу и наоборот.
|
|
8
|
+
*
|
|
9
|
+
* `ApiStatus` — одновременно значение (для `ApiStatus.Loading` в коде) и тип
|
|
10
|
+
* (union всех статусов).
|
|
11
|
+
*/ const ApiStatus = {
|
|
12
|
+
Idle: 'idle',
|
|
13
|
+
Loading: 'loading',
|
|
14
|
+
Success: 'success',
|
|
15
|
+
Error: 'error',
|
|
16
|
+
Reset: 'reset'
|
|
17
|
+
};
|
|
1
18
|
// ────────────────────────────────────────────────────────────────────────────
|
|
2
19
|
// defineAction
|
|
3
20
|
// ────────────────────────────────────────────────────────────────────────────
|
|
@@ -82,6 +99,11 @@
|
|
|
82
99
|
*
|
|
83
100
|
* @param accessor - Функция-accessor, указывающая на поле ApiRequestState в стейте
|
|
84
101
|
*
|
|
102
|
+
* @typeParam TInitPayload - Тип payload'а `init`-экшена. По умолчанию `void`
|
|
103
|
+
* (init без параметров). Если задать — `init` принимает payload и возвращает
|
|
104
|
+
* его, что удобно для intent-паттерна: эффект слушает `init` и читает payload
|
|
105
|
+
* намерения (target, фильтры и т.п.), а статус при этом сбрасывается в `idle`.
|
|
106
|
+
*
|
|
85
107
|
* @example
|
|
86
108
|
* ```ts
|
|
87
109
|
* const listRequest = createApiActions<MyState>(
|
|
@@ -96,6 +118,11 @@
|
|
|
96
118
|
* loadListFailure: listRequest.failure,
|
|
97
119
|
* loadListReset: listRequest.reset,
|
|
98
120
|
* })
|
|
121
|
+
*
|
|
122
|
+
* // init с payload (intent): эффект получит { entityId } из возврата экшена.
|
|
123
|
+
* const usersReq = createApiActions<MyState, { entityId: string }>(
|
|
124
|
+
* (draft) => draft.api.usersRequest
|
|
125
|
+
* )
|
|
99
126
|
* ```
|
|
100
127
|
*/ function createApiActions(accessor) {
|
|
101
128
|
const path = resolvePath(accessor);
|
|
@@ -105,38 +132,106 @@
|
|
|
105
132
|
};
|
|
106
133
|
return {
|
|
107
134
|
init: action({
|
|
108
|
-
|
|
109
|
-
|
|
135
|
+
// Сбрасываем статус в idle и пробрасываем payload намерения дальше (эффектам).
|
|
136
|
+
action: (storage, payload)=>{
|
|
137
|
+
update(storage, {
|
|
138
|
+
status: ApiStatus.Idle,
|
|
110
139
|
error: null
|
|
111
|
-
})
|
|
140
|
+
});
|
|
141
|
+
return payload;
|
|
142
|
+
}
|
|
112
143
|
}),
|
|
113
144
|
loading: action({
|
|
114
145
|
action: (storage)=>update(storage, {
|
|
115
|
-
status:
|
|
146
|
+
status: ApiStatus.Loading,
|
|
116
147
|
error: null
|
|
117
148
|
})
|
|
118
149
|
}),
|
|
119
150
|
success: action({
|
|
120
151
|
action: (storage)=>update(storage, {
|
|
121
|
-
status:
|
|
152
|
+
status: ApiStatus.Success,
|
|
122
153
|
error: null
|
|
123
154
|
})
|
|
124
155
|
}),
|
|
125
156
|
failure: action({
|
|
126
157
|
action: (storage, error)=>update(storage, {
|
|
127
|
-
status:
|
|
158
|
+
status: ApiStatus.Error,
|
|
128
159
|
error
|
|
129
160
|
})
|
|
130
161
|
}),
|
|
131
162
|
reset: action({
|
|
132
163
|
action: (storage)=>update(storage, {
|
|
133
|
-
status:
|
|
164
|
+
status: ApiStatus.Reset,
|
|
134
165
|
error: null
|
|
135
166
|
})
|
|
136
167
|
})
|
|
137
168
|
};
|
|
138
169
|
}
|
|
170
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
171
|
+
// createKeyedApiActions
|
|
172
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
173
|
+
/**
|
|
174
|
+
* Keyed-вариант createApiActions: статус хранится ПО КЛЮЧУ в `Record<string,
|
|
175
|
+
* ApiRequestState>`, а не один на весь запрос. Нужен, когда один и тот же запрос
|
|
176
|
+
* летит параллельно для нескольких независимых ключей и у каждого свой
|
|
177
|
+
* loading/error: комменты по таргетам, детали сущностей по id, per-row действия
|
|
178
|
+
* в таблице/ленте, пагинация по секциям — всё, что лежит как `Record<key, data>`.
|
|
179
|
+
*
|
|
180
|
+
* Все статус-экшены принимают `key` (и возвращают его — удобно эффектам), кроме
|
|
181
|
+
* `failure`, который принимает `{ key, error }`. Записи мутируются иммутабельно
|
|
182
|
+
* по одному ключу — соседние ключи (их срезы) по ссылке не затрагиваются, что и
|
|
183
|
+
* нужно для гранулярной изоляции ре-рендеров (см. useKeyedSliceSelector).
|
|
184
|
+
*
|
|
185
|
+
* @param accessor - Функция-accessor, указывающая на поле `Record<string, ApiRequestState>`
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```ts
|
|
189
|
+
* const commentsReq = createKeyedApiActions<MyState>((d) => d.api.commentsRequest)
|
|
190
|
+
*
|
|
191
|
+
* createDispatcher({ storage }, {
|
|
192
|
+
* commentsInit: commentsReq.init, // (key) => key
|
|
193
|
+
* commentsLoading: commentsReq.loading, // (key) => key
|
|
194
|
+
* commentsSuccess: commentsReq.success, // (key) => key
|
|
195
|
+
* commentsFailure: commentsReq.failure, // ({ key, error })
|
|
196
|
+
* commentsReset: commentsReq.reset, // (key) => key
|
|
197
|
+
* })
|
|
198
|
+
* ```
|
|
199
|
+
*/ function createKeyedApiActions(accessor) {
|
|
200
|
+
const path = resolvePath(accessor);
|
|
201
|
+
const action = defineAction();
|
|
202
|
+
const write = (storage, key, request)=>{
|
|
203
|
+
// [...path, key] → setByPath доходит до самого Record и кладёт значение по ключу
|
|
204
|
+
storage.update((s)=>setByPath(s, [
|
|
205
|
+
...path,
|
|
206
|
+
key
|
|
207
|
+
], request));
|
|
208
|
+
};
|
|
209
|
+
const writer = (status)=>action({
|
|
210
|
+
action: (storage, key)=>{
|
|
211
|
+
write(storage, key, {
|
|
212
|
+
status,
|
|
213
|
+
error: null
|
|
214
|
+
});
|
|
215
|
+
return key;
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
return {
|
|
219
|
+
init: writer(ApiStatus.Idle),
|
|
220
|
+
loading: writer(ApiStatus.Loading),
|
|
221
|
+
success: writer(ApiStatus.Success),
|
|
222
|
+
reset: writer(ApiStatus.Reset),
|
|
223
|
+
failure: action({
|
|
224
|
+
action: (storage, payload)=>{
|
|
225
|
+
write(storage, payload.key, {
|
|
226
|
+
status: ApiStatus.Error,
|
|
227
|
+
error: payload.error
|
|
228
|
+
});
|
|
229
|
+
return payload;
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
};
|
|
233
|
+
}
|
|
139
234
|
|
|
140
|
-
export { createApiActions, defineAction, defineWatcher };
|
|
235
|
+
export { ApiStatus, createApiActions, createKeyedApiActions, defineAction, defineWatcher };
|
|
141
236
|
|
|
142
237
|
//# sourceMappingURL=standalone.js.map
|