@simplysm/solid 13.0.34 → 13.0.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -42
- package/dist/components/disclosure/DialogContext.d.ts +29 -0
- package/dist/components/disclosure/DialogContext.d.ts.map +1 -1
- package/dist/components/disclosure/DialogContext.js.map +1 -1
- package/dist/components/disclosure/DialogInstanceContext.d.ts +14 -0
- package/dist/components/disclosure/DialogInstanceContext.d.ts.map +1 -1
- package/dist/components/disclosure/DialogInstanceContext.js.map +1 -1
- package/dist/components/feedback/busy/BusyContext.d.ts +18 -0
- package/dist/components/feedback/busy/BusyContext.d.ts.map +1 -1
- package/dist/components/feedback/busy/BusyContext.js.map +1 -1
- package/dist/components/feedback/busy/BusyProvider.d.ts +10 -0
- package/dist/components/feedback/busy/BusyProvider.d.ts.map +1 -1
- package/dist/components/feedback/busy/BusyProvider.js.map +1 -1
- package/dist/components/feedback/notification/NotificationContext.d.ts +29 -0
- package/dist/components/feedback/notification/NotificationContext.d.ts.map +1 -1
- package/dist/components/feedback/notification/NotificationContext.js.map +1 -1
- package/dist/components/feedback/notification/NotificationProvider.d.ts +9 -0
- package/dist/components/feedback/notification/NotificationProvider.d.ts.map +1 -1
- package/dist/components/feedback/notification/NotificationProvider.js.map +1 -1
- package/dist/hooks/useLogger.d.ts +4 -2
- package/dist/hooks/useLogger.d.ts.map +1 -1
- package/dist/hooks/useLogger.js +11 -4
- package/dist/hooks/useLogger.js.map +1 -1
- package/dist/hooks/useSyncConfig.d.ts +2 -0
- package/dist/hooks/useSyncConfig.d.ts.map +1 -1
- package/dist/hooks/useSyncConfig.js +30 -26
- package/dist/hooks/useSyncConfig.js.map +1 -1
- package/dist/index.d.ts +8 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -15
- package/dist/index.js.map +1 -1
- package/dist/providers/InitializeProvider.d.ts +33 -0
- package/dist/providers/InitializeProvider.d.ts.map +1 -0
- package/dist/providers/InitializeProvider.js +75 -0
- package/dist/providers/InitializeProvider.js.map +6 -0
- package/dist/providers/LoggerContext.d.ts +24 -8
- package/dist/providers/LoggerContext.d.ts.map +1 -1
- package/dist/providers/LoggerContext.js +13 -13
- package/dist/providers/LoggerContext.js.map +2 -2
- package/dist/providers/ServiceClientContext.d.ts +13 -0
- package/dist/providers/ServiceClientContext.d.ts.map +1 -1
- package/dist/providers/ServiceClientContext.js.map +1 -1
- package/dist/providers/ServiceClientProvider.d.ts +21 -0
- package/dist/providers/ServiceClientProvider.d.ts.map +1 -1
- package/dist/providers/ServiceClientProvider.js.map +1 -1
- package/dist/providers/SyncStorageContext.d.ts +25 -11
- package/dist/providers/SyncStorageContext.d.ts.map +1 -1
- package/dist/providers/SyncStorageContext.js +13 -13
- package/dist/providers/SyncStorageContext.js.map +2 -2
- package/dist/providers/shared-data/SharedDataChangeEvent.d.ts +8 -0
- package/dist/providers/shared-data/SharedDataChangeEvent.d.ts.map +1 -1
- package/dist/providers/shared-data/SharedDataChangeEvent.js.map +1 -1
- package/dist/providers/shared-data/SharedDataContext.d.ts +39 -0
- package/dist/providers/shared-data/SharedDataContext.d.ts.map +1 -1
- package/dist/providers/shared-data/SharedDataContext.js +1 -3
- package/dist/providers/shared-data/SharedDataContext.js.map +1 -1
- package/dist/providers/shared-data/SharedDataProvider.d.ts +30 -5
- package/dist/providers/shared-data/SharedDataProvider.d.ts.map +1 -1
- package/dist/providers/shared-data/SharedDataProvider.js +59 -38
- package/dist/providers/shared-data/SharedDataProvider.js.map +2 -2
- package/docs/providers.md +70 -195
- package/package.json +3 -3
- package/src/components/disclosure/DialogContext.ts +29 -0
- package/src/components/disclosure/DialogInstanceContext.ts +14 -0
- package/src/components/feedback/busy/BusyContext.ts +18 -0
- package/src/components/feedback/busy/BusyProvider.tsx +10 -0
- package/src/components/feedback/notification/NotificationContext.ts +29 -0
- package/src/components/feedback/notification/NotificationProvider.tsx +9 -0
- package/src/hooks/useLogger.ts +14 -4
- package/src/hooks/useSyncConfig.ts +42 -35
- package/src/index.ts +34 -14
- package/src/providers/InitializeProvider.tsx +74 -0
- package/src/providers/LoggerContext.tsx +39 -10
- package/src/providers/ServiceClientContext.ts +13 -0
- package/src/providers/ServiceClientProvider.tsx +21 -0
- package/src/providers/SyncStorageContext.tsx +40 -15
- package/src/providers/shared-data/SharedDataChangeEvent.ts +8 -0
- package/src/providers/shared-data/SharedDataContext.ts +40 -3
- package/src/providers/shared-data/SharedDataProvider.tsx +102 -54
|
@@ -8,6 +8,27 @@ import { ServiceClientContext, type ServiceClientContextValue } from "./ServiceC
|
|
|
8
8
|
import { useConfig } from "./ConfigContext";
|
|
9
9
|
import { useNotification } from "../components/feedback/notification/NotificationContext";
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* WebSocket 서비스 클라이언트 Provider
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* - ConfigProvider와 NotificationProvider 내부에서 사용해야 함
|
|
16
|
+
* - key 기반 다중 연결 관리
|
|
17
|
+
* - 요청/응답 진행률을 NotificationProvider 알림으로 표시
|
|
18
|
+
* - host, port, ssl 미지정 시 window.location에서 자동 추론
|
|
19
|
+
* - cleanup 시 모든 연결 자동 종료
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* <ConfigProvider clientName="my-app">
|
|
24
|
+
* <NotificationProvider>
|
|
25
|
+
* <ServiceClientProvider>
|
|
26
|
+
* <App />
|
|
27
|
+
* </ServiceClientProvider>
|
|
28
|
+
* </NotificationProvider>
|
|
29
|
+
* </ConfigProvider>
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
11
32
|
export const ServiceClientProvider: ParentComponent = (props) => {
|
|
12
33
|
const config = useConfig();
|
|
13
34
|
const notification = useNotification();
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type Accessor,
|
|
3
|
+
createContext,
|
|
4
|
+
createSignal,
|
|
5
|
+
useContext,
|
|
6
|
+
type ParentComponent,
|
|
7
|
+
} from "solid-js";
|
|
2
8
|
|
|
3
9
|
/**
|
|
4
10
|
* 커스텀 동기화 저장소 어댑터 인터페이스
|
|
@@ -13,40 +19,59 @@ export interface StorageAdapter {
|
|
|
13
19
|
removeItem(key: string): void | Promise<void>;
|
|
14
20
|
}
|
|
15
21
|
|
|
22
|
+
/**
|
|
23
|
+
* 동기화 저장소 Context 값
|
|
24
|
+
*
|
|
25
|
+
* @remarks
|
|
26
|
+
* - `adapter`: 현재 설정된 StorageAdapter (signal). configure 전에는 undefined
|
|
27
|
+
* - `configure`: adapter를 나중에 주입하는 함수
|
|
28
|
+
*/
|
|
29
|
+
export interface SyncStorageContextValue {
|
|
30
|
+
adapter: Accessor<StorageAdapter | undefined>;
|
|
31
|
+
configure: (adapter: StorageAdapter) => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
16
34
|
/**
|
|
17
35
|
* 동기화 저장소 Context
|
|
18
36
|
*
|
|
19
37
|
* @remarks
|
|
20
38
|
* Provider가 없으면 `undefined` (useSyncConfig에서 localStorage로 fallback)
|
|
21
39
|
*/
|
|
22
|
-
export const SyncStorageContext = createContext<
|
|
40
|
+
export const SyncStorageContext = createContext<SyncStorageContextValue>();
|
|
23
41
|
|
|
24
42
|
/**
|
|
25
43
|
* 동기화 저장소 Context에 접근하는 훅
|
|
26
44
|
*
|
|
27
|
-
* @returns
|
|
45
|
+
* @returns SyncStorageContextValue 또는 undefined (Provider가 없으면)
|
|
28
46
|
*/
|
|
29
|
-
export function useSyncStorage():
|
|
47
|
+
export function useSyncStorage(): SyncStorageContextValue | undefined {
|
|
30
48
|
return useContext(SyncStorageContext);
|
|
31
49
|
}
|
|
32
50
|
|
|
33
51
|
/**
|
|
34
52
|
* 동기화 저장소 Provider
|
|
35
53
|
*
|
|
54
|
+
* @remarks
|
|
55
|
+
* - prop 없이 사용. adapter는 `useSyncStorage().configure()`로 나중에 주입
|
|
56
|
+
* - configure 전에는 useSyncConfig이 localStorage로 fallback
|
|
57
|
+
*
|
|
36
58
|
* @example
|
|
37
59
|
* ```tsx
|
|
38
|
-
* <SyncStorageProvider
|
|
39
|
-
* <
|
|
40
|
-
* <App />
|
|
41
|
-
* </ThemeProvider>
|
|
60
|
+
* <SyncStorageProvider>
|
|
61
|
+
* <App />
|
|
42
62
|
* </SyncStorageProvider>
|
|
63
|
+
*
|
|
64
|
+
* // 자식 컴포넌트에서 나중에 설정:
|
|
65
|
+
* useSyncStorage()!.configure(myStorageAdapter);
|
|
43
66
|
* ```
|
|
44
67
|
*/
|
|
45
|
-
export const SyncStorageProvider: ParentComponent
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
68
|
+
export const SyncStorageProvider: ParentComponent = (props) => {
|
|
69
|
+
const [adapter, setAdapter] = createSignal<StorageAdapter | undefined>();
|
|
70
|
+
|
|
71
|
+
const value: SyncStorageContextValue = {
|
|
72
|
+
adapter,
|
|
73
|
+
configure: (a: StorageAdapter) => setAdapter(() => a),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return <SyncStorageContext.Provider value={value}>{props.children}</SyncStorageContext.Provider>;
|
|
52
77
|
};
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { defineEvent } from "@simplysm/service-common";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* SharedData 변경 이벤트 정의
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* 서버-클라이언트 간 공유 데이터 변경을 알리는 이벤트.
|
|
8
|
+
* - 이벤트 정보: `{ name: string; filter: unknown }` — 데이터 이름과 필터
|
|
9
|
+
* - 이벤트 데이터: `(string | number)[] | undefined` — 변경된 항목의 key 배열 (undefined면 전체 갱신)
|
|
10
|
+
*/
|
|
3
11
|
export const SharedDataChangeEvent = defineEvent<
|
|
4
12
|
{ name: string; filter: unknown },
|
|
5
13
|
(string | number)[] | undefined
|
|
@@ -1,36 +1,73 @@
|
|
|
1
1
|
import { type Accessor, createContext, useContext } from "solid-js";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* 공유 데이터 정의
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* SharedDataProvider에 전달하여 서버 데이터 구독을 설정한다.
|
|
8
|
+
*/
|
|
3
9
|
export interface SharedDataDefinition<TData> {
|
|
10
|
+
/** 서비스 연결 key (useServiceClient의 connect key와 동일) */
|
|
4
11
|
serviceKey: string;
|
|
12
|
+
/** 데이터 조회 함수 (changeKeys가 있으면 해당 항목만 부분 갱신) */
|
|
5
13
|
fetch: (changeKeys?: Array<string | number>) => Promise<TData[]>;
|
|
14
|
+
/** 항목의 고유 key 추출 함수 */
|
|
6
15
|
getKey: (item: TData) => string | number;
|
|
16
|
+
/** 정렬 기준 배열 (여러 기준 적용 가능) */
|
|
7
17
|
orderBy: [(item: TData) => unknown, "asc" | "desc"][];
|
|
18
|
+
/** 서버 이벤트 필터 (같은 name의 이벤트 중 filter가 일치하는 것만 수신) */
|
|
8
19
|
filter?: unknown;
|
|
9
20
|
}
|
|
10
21
|
|
|
22
|
+
/**
|
|
23
|
+
* 공유 데이터 접근자
|
|
24
|
+
*
|
|
25
|
+
* @remarks
|
|
26
|
+
* 각 데이터 key에 대한 반응형 접근 및 변경 알림을 제공한다.
|
|
27
|
+
*/
|
|
11
28
|
export interface SharedDataAccessor<TData> {
|
|
29
|
+
/** 반응형 항목 배열 */
|
|
12
30
|
items: Accessor<TData[]>;
|
|
31
|
+
/** key로 단일 항목 조회 */
|
|
13
32
|
get: (key: string | number | undefined) => TData | undefined;
|
|
33
|
+
/** 서버에 변경 이벤트 전파 (모든 구독자에게 refetch 트리거) */
|
|
14
34
|
emit: (changeKeys?: Array<string | number>) => Promise<void>;
|
|
15
35
|
}
|
|
16
36
|
|
|
37
|
+
/**
|
|
38
|
+
* 공유 데이터 Context 값
|
|
39
|
+
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* - configure 호출 전: wait, busy, configure만 접근 가능. 데이터 접근 시 throw
|
|
42
|
+
* - configure 호출 후: 각 데이터 key별 SharedDataAccessor와 전체 상태 관리 메서드 포함
|
|
43
|
+
*/
|
|
17
44
|
export type SharedDataValue<TSharedData extends Record<string, unknown>> = {
|
|
18
45
|
[K in keyof TSharedData]: SharedDataAccessor<TSharedData[K]>;
|
|
19
46
|
} & {
|
|
47
|
+
/** 모든 초기 fetch 완료까지 대기 */
|
|
20
48
|
wait: () => Promise<void>;
|
|
49
|
+
/** fetch 진행 중 여부 */
|
|
21
50
|
busy: Accessor<boolean>;
|
|
51
|
+
/** definitions를 설정하여 데이터 구독 시작 */
|
|
52
|
+
configure: (definitions: {
|
|
53
|
+
[K in keyof TSharedData]: SharedDataDefinition<TSharedData[K]>;
|
|
54
|
+
}) => void;
|
|
22
55
|
};
|
|
23
56
|
|
|
57
|
+
/** 공유 데이터 Context */
|
|
24
58
|
export const SharedDataContext = createContext<SharedDataValue<Record<string, unknown>>>();
|
|
25
59
|
|
|
60
|
+
/**
|
|
61
|
+
* 공유 데이터에 접근하는 훅
|
|
62
|
+
*
|
|
63
|
+
* @throws SharedDataProvider가 없으면 에러 발생
|
|
64
|
+
*/
|
|
26
65
|
export function useSharedData<
|
|
27
66
|
TSharedData extends Record<string, unknown> = Record<string, unknown>,
|
|
28
67
|
>(): SharedDataValue<TSharedData> {
|
|
29
68
|
const context = useContext(SharedDataContext);
|
|
30
69
|
if (!context) {
|
|
31
|
-
throw new Error(
|
|
32
|
-
"useSharedData는 SharedDataProvider 내부에서만 사용할 수 있습니다. SharedDataProvider는 ServiceClientProvider 아래에 위치해야 합니다",
|
|
33
|
-
);
|
|
70
|
+
throw new Error("useSharedData는 SharedDataProvider 내부에서만 사용할 수 있습니다");
|
|
34
71
|
}
|
|
35
72
|
return context as unknown as SharedDataValue<TSharedData>;
|
|
36
73
|
}
|
|
@@ -11,14 +11,41 @@ import { useServiceClient } from "../ServiceClientContext";
|
|
|
11
11
|
import { useNotification } from "../../components/feedback/notification/NotificationContext";
|
|
12
12
|
import { useLogger } from "../../hooks/useLogger";
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
/**
|
|
15
|
+
* 공유 데이터 Provider
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* - ServiceClientProvider와 NotificationProvider 내부에서 사용해야 함
|
|
19
|
+
* - LoggerProvider가 있으면 fetch 실패를 로거에도 기록
|
|
20
|
+
* - configure() 호출 전: wait, busy, configure만 접근 가능. 데이터 접근 시 throw
|
|
21
|
+
* - configure() 호출 후: definitions의 각 key마다 서버 이벤트 리스너를 등록하여 실시간 동기화
|
|
22
|
+
* - 동시 fetch 호출 시 version counter로 데이터 역전 방지
|
|
23
|
+
* - fetch 실패 시 사용자에게 danger 알림 표시
|
|
24
|
+
* - cleanup 시 모든 이벤트 리스너 자동 해제
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```tsx
|
|
28
|
+
* <SharedDataProvider>
|
|
29
|
+
* <App />
|
|
30
|
+
* </SharedDataProvider>
|
|
31
|
+
*
|
|
32
|
+
* // 자식 컴포넌트에서 나중에 설정:
|
|
33
|
+
* useSharedData().configure({
|
|
34
|
+
* users: {
|
|
35
|
+
* serviceKey: "main",
|
|
36
|
+
* fetch: async (changeKeys) => fetchUsers(changeKeys),
|
|
37
|
+
* getKey: (item) => item.id,
|
|
38
|
+
* orderBy: [[(item) => item.name, "asc"]],
|
|
39
|
+
* },
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export function SharedDataProvider(props: { children: JSX.Element }): JSX.Element {
|
|
18
44
|
const serviceClient = useServiceClient();
|
|
19
45
|
const notification = useNotification();
|
|
20
46
|
const logger = useLogger();
|
|
21
47
|
|
|
48
|
+
let configured = false;
|
|
22
49
|
const [busyCount, setBusyCount] = createSignal(0);
|
|
23
50
|
const busy: Accessor<boolean> = () => busyCount() > 0;
|
|
24
51
|
|
|
@@ -26,6 +53,8 @@ export function SharedDataProvider<TSharedData extends Record<string, unknown>>(
|
|
|
26
53
|
const memoMap = new Map<string, Accessor<Map<string | number, unknown>>>();
|
|
27
54
|
const listenerKeyMap = new Map<string, string>();
|
|
28
55
|
const versionMap = new Map<string, number>();
|
|
56
|
+
const accessors: Record<string, SharedDataAccessor<unknown>> = {};
|
|
57
|
+
let currentDefinitions: Record<string, SharedDataDefinition<unknown>> | undefined;
|
|
29
58
|
|
|
30
59
|
function ordering<TT>(data: TT[], orderByList: [(item: TT) => unknown, "asc" | "desc"][]): TT[] {
|
|
31
60
|
let result = [...data];
|
|
@@ -86,70 +115,89 @@ export function SharedDataProvider<TSharedData extends Record<string, unknown>>(
|
|
|
86
115
|
await waitUntil(() => busyCount() <= 0);
|
|
87
116
|
}
|
|
88
117
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const [
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
// eslint-disable-next-line solid/reactivity -- memo 참조를 Map에 저장하는 것은 반응성 접근이 아님
|
|
108
|
-
memoMap.set(name, itemMap);
|
|
109
|
-
|
|
110
|
-
const client = serviceClient.get(def.serviceKey);
|
|
111
|
-
void client
|
|
112
|
-
.addEventListener(SharedDataChangeEvent, { name, filter: def.filter }, async (changeKeys) => {
|
|
113
|
-
await loadData(name, def, changeKeys);
|
|
114
|
-
})
|
|
115
|
-
.then((key) => {
|
|
116
|
-
listenerKeyMap.set(name, key);
|
|
118
|
+
function configure(definitions: Record<string, SharedDataDefinition<unknown>>): void {
|
|
119
|
+
if (configured) {
|
|
120
|
+
throw new Error("SharedDataProvider: configure()는 1회만 호출할 수 있습니다");
|
|
121
|
+
}
|
|
122
|
+
configured = true;
|
|
123
|
+
currentDefinitions = definitions;
|
|
124
|
+
|
|
125
|
+
for (const [name, def] of Object.entries(definitions)) {
|
|
126
|
+
const [items, setItems] = createSignal<unknown[]>([]);
|
|
127
|
+
// eslint-disable-next-line solid/reactivity -- signal 참조를 Map에 저장하는 것은 반응성 접근이 아님
|
|
128
|
+
signalMap.set(name, [items, setItems]);
|
|
129
|
+
|
|
130
|
+
const itemMap = createMemo(() => {
|
|
131
|
+
const map = new Map<string | number, unknown>();
|
|
132
|
+
for (const item of items()) {
|
|
133
|
+
map.set(def.getKey(item as never), item);
|
|
134
|
+
}
|
|
135
|
+
return map;
|
|
117
136
|
});
|
|
137
|
+
// eslint-disable-next-line solid/reactivity -- memo 참조를 Map에 저장하는 것은 반응성 접근이 아님
|
|
138
|
+
memoMap.set(name, itemMap);
|
|
118
139
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
items,
|
|
123
|
-
get: (key: string | number | undefined) => {
|
|
124
|
-
if (key === undefined) return undefined;
|
|
125
|
-
return itemMap().get(key);
|
|
126
|
-
},
|
|
127
|
-
emit: async (changeKeys?: Array<string | number>) => {
|
|
128
|
-
await client.emitToServer(
|
|
140
|
+
const client = serviceClient.get(def.serviceKey);
|
|
141
|
+
void client
|
|
142
|
+
.addEventListener(
|
|
129
143
|
SharedDataChangeEvent,
|
|
130
|
-
|
|
131
|
-
changeKeys
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
144
|
+
{ name, filter: def.filter },
|
|
145
|
+
async (changeKeys) => {
|
|
146
|
+
await loadData(name, def, changeKeys);
|
|
147
|
+
},
|
|
148
|
+
)
|
|
149
|
+
.then((key) => {
|
|
150
|
+
listenerKeyMap.set(name, key);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
void loadData(name, def);
|
|
154
|
+
|
|
155
|
+
accessors[name] = {
|
|
156
|
+
items,
|
|
157
|
+
get: (key: string | number | undefined) => {
|
|
158
|
+
if (key === undefined) return undefined;
|
|
159
|
+
return itemMap().get(key);
|
|
160
|
+
},
|
|
161
|
+
emit: async (changeKeys?: Array<string | number>) => {
|
|
162
|
+
await client.emitToServer(
|
|
163
|
+
SharedDataChangeEvent,
|
|
164
|
+
(info) => info.name === name && objEqual(info.filter, def.filter),
|
|
165
|
+
changeKeys,
|
|
166
|
+
);
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
135
170
|
}
|
|
136
171
|
|
|
137
172
|
onCleanup(() => {
|
|
138
|
-
|
|
173
|
+
if (!currentDefinitions) return;
|
|
174
|
+
for (const [name] of Object.entries(currentDefinitions)) {
|
|
139
175
|
const listenerKey = listenerKeyMap.get(name);
|
|
140
176
|
if (listenerKey != null) {
|
|
141
|
-
const def =
|
|
177
|
+
const def = currentDefinitions[name];
|
|
142
178
|
const client = serviceClient.get(def.serviceKey);
|
|
143
179
|
void client.removeEventListener(listenerKey);
|
|
144
180
|
}
|
|
145
181
|
}
|
|
146
182
|
});
|
|
147
183
|
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
184
|
+
const KNOWN_KEYS = new Set(["wait", "busy", "configure"]);
|
|
185
|
+
|
|
186
|
+
// Proxy: configure 전 데이터 접근 시 throw
|
|
187
|
+
const contextValue = new Proxy(
|
|
188
|
+
{ wait, busy, configure } as SharedDataValue<Record<string, unknown>>,
|
|
189
|
+
{
|
|
190
|
+
get(target, prop: string) {
|
|
191
|
+
if (KNOWN_KEYS.has(prop)) {
|
|
192
|
+
return target[prop];
|
|
193
|
+
}
|
|
194
|
+
if (!configured) {
|
|
195
|
+
throw new Error("SharedDataProvider: configure()를 먼저 호출해야 합니다");
|
|
196
|
+
}
|
|
197
|
+
return accessors[prop];
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
);
|
|
153
201
|
|
|
154
202
|
return (
|
|
155
203
|
<SharedDataContext.Provider value={contextValue}>{props.children}</SharedDataContext.Provider>
|