@simplysm/solid 13.0.34 → 13.0.36
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 +31 -9
- package/dist/providers/LoggerContext.d.ts.map +1 -1
- package/dist/providers/LoggerContext.js +17 -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 +29 -11
- package/dist/providers/SyncStorageContext.d.ts.map +1 -1
- package/dist/providers/SyncStorageContext.js +18 -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 +41 -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 +60 -38
- package/dist/providers/shared-data/SharedDataProvider.js.map +2 -2
- package/docs/hooks.md +15 -0
- 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 +51 -11
- package/src/providers/ServiceClientContext.ts +13 -0
- package/src/providers/ServiceClientProvider.tsx +21 -0
- package/src/providers/SyncStorageContext.tsx +53 -15
- package/src/providers/shared-data/SharedDataChangeEvent.ts +8 -0
- package/src/providers/shared-data/SharedDataContext.ts +44 -3
- package/src/providers/shared-data/SharedDataProvider.tsx +108 -54
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
import { createContext, useContext } from "solid-js";
|
|
2
2
|
import type { ServiceClient, ServiceConnectionConfig } from "@simplysm/service-client";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* WebSocket 서비스 클라이언트 Context 값
|
|
6
|
+
*/
|
|
4
7
|
export interface ServiceClientContextValue {
|
|
8
|
+
/** WebSocket 연결 열기 (key로 다중 연결 관리) */
|
|
5
9
|
connect: (key: string, options?: Partial<ServiceConnectionConfig>) => Promise<void>;
|
|
10
|
+
/** 연결 닫기 */
|
|
6
11
|
close: (key: string) => Promise<void>;
|
|
12
|
+
/** 연결된 클라이언트 인스턴스 가져오기 (연결되지 않은 key면 에러 발생) */
|
|
7
13
|
get: (key: string) => ServiceClient;
|
|
14
|
+
/** 연결 상태 확인 */
|
|
8
15
|
isConnected: (key: string) => boolean;
|
|
9
16
|
}
|
|
10
17
|
|
|
18
|
+
/** WebSocket 서비스 클라이언트 Context */
|
|
11
19
|
export const ServiceClientContext = createContext<ServiceClientContextValue>();
|
|
12
20
|
|
|
21
|
+
/**
|
|
22
|
+
* WebSocket 서비스 클라이언트에 접근하는 훅
|
|
23
|
+
*
|
|
24
|
+
* @throws ServiceClientProvider가 없으면 에러 발생
|
|
25
|
+
*/
|
|
13
26
|
export function useServiceClient(): ServiceClientContextValue {
|
|
14
27
|
const context = useContext(ServiceClientContext);
|
|
15
28
|
if (!context) {
|
|
@@ -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,72 @@ export interface StorageAdapter {
|
|
|
13
19
|
removeItem(key: string): void | Promise<void>;
|
|
14
20
|
}
|
|
15
21
|
|
|
22
|
+
/**
|
|
23
|
+
* 기본 localStorage 기반 어댑터
|
|
24
|
+
*/
|
|
25
|
+
const defaultStorageAdapter: StorageAdapter = {
|
|
26
|
+
getItem: (key) => localStorage.getItem(key),
|
|
27
|
+
setItem: (key, value) => localStorage.setItem(key, value),
|
|
28
|
+
removeItem: (key) => localStorage.removeItem(key),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 동기화 저장소 Context 값
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* - `adapter`: 현재 설정된 StorageAdapter (signal). 기본값은 localStorage 기반 어댑터
|
|
36
|
+
* - `configure`: decorator 함수를 받아 기존 adapter를 감싸서 새 adapter를 설정하는 함수
|
|
37
|
+
*/
|
|
38
|
+
export interface SyncStorageContextValue {
|
|
39
|
+
adapter: Accessor<StorageAdapter>;
|
|
40
|
+
configure: (fn: (origin: StorageAdapter) => StorageAdapter) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
16
43
|
/**
|
|
17
44
|
* 동기화 저장소 Context
|
|
18
45
|
*
|
|
19
46
|
* @remarks
|
|
20
47
|
* Provider가 없으면 `undefined` (useSyncConfig에서 localStorage로 fallback)
|
|
21
48
|
*/
|
|
22
|
-
export const SyncStorageContext = createContext<
|
|
49
|
+
export const SyncStorageContext = createContext<SyncStorageContextValue>();
|
|
23
50
|
|
|
24
51
|
/**
|
|
25
52
|
* 동기화 저장소 Context에 접근하는 훅
|
|
26
53
|
*
|
|
27
|
-
* @returns
|
|
54
|
+
* @returns SyncStorageContextValue 또는 undefined (Provider가 없으면)
|
|
28
55
|
*/
|
|
29
|
-
export function useSyncStorage():
|
|
56
|
+
export function useSyncStorage(): SyncStorageContextValue | undefined {
|
|
30
57
|
return useContext(SyncStorageContext);
|
|
31
58
|
}
|
|
32
59
|
|
|
33
60
|
/**
|
|
34
61
|
* 동기화 저장소 Provider
|
|
35
62
|
*
|
|
63
|
+
* @remarks
|
|
64
|
+
* - prop 없이 사용. 기본적으로 localStorage 기반 어댑터가 설정됨
|
|
65
|
+
* - `configure()`로 decorator 함수를 전달하여 기존 adapter를 감싸거나 교체 가능
|
|
66
|
+
*
|
|
36
67
|
* @example
|
|
37
68
|
* ```tsx
|
|
38
|
-
* <SyncStorageProvider
|
|
39
|
-
* <
|
|
40
|
-
* <App />
|
|
41
|
-
* </ThemeProvider>
|
|
69
|
+
* <SyncStorageProvider>
|
|
70
|
+
* <App />
|
|
42
71
|
* </SyncStorageProvider>
|
|
72
|
+
*
|
|
73
|
+
* // 자식 컴포넌트에서 decorator 패턴으로 adapter 커스터마이징:
|
|
74
|
+
* useSyncStorage()!.configure((origin) => ({
|
|
75
|
+
* getItem: (key) => myCustomGetItem(key),
|
|
76
|
+
* setItem: origin.setItem,
|
|
77
|
+
* removeItem: origin.removeItem,
|
|
78
|
+
* }));
|
|
43
79
|
* ```
|
|
44
80
|
*/
|
|
45
|
-
export const SyncStorageProvider: ParentComponent
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
81
|
+
export const SyncStorageProvider: ParentComponent = (props) => {
|
|
82
|
+
const [adapter, setAdapter] = createSignal<StorageAdapter>(defaultStorageAdapter);
|
|
83
|
+
|
|
84
|
+
const value: SyncStorageContextValue = {
|
|
85
|
+
adapter,
|
|
86
|
+
configure: (fn: (origin: StorageAdapter) => StorageAdapter) => setAdapter((prev) => fn(prev)),
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return <SyncStorageContext.Provider value={value}>{props.children}</SyncStorageContext.Provider>;
|
|
52
90
|
};
|
|
@@ -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,77 @@
|
|
|
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를 설정하여 데이터 구독 시작 (decorator 패턴) */
|
|
52
|
+
configure: (
|
|
53
|
+
fn: (origin: {
|
|
54
|
+
[K in keyof TSharedData]: SharedDataDefinition<TSharedData[K]>;
|
|
55
|
+
}) => {
|
|
56
|
+
[K in keyof TSharedData]: SharedDataDefinition<TSharedData[K]>;
|
|
57
|
+
},
|
|
58
|
+
) => void;
|
|
22
59
|
};
|
|
23
60
|
|
|
61
|
+
/** 공유 데이터 Context */
|
|
24
62
|
export const SharedDataContext = createContext<SharedDataValue<Record<string, unknown>>>();
|
|
25
63
|
|
|
64
|
+
/**
|
|
65
|
+
* 공유 데이터에 접근하는 훅
|
|
66
|
+
*
|
|
67
|
+
* @throws SharedDataProvider가 없으면 에러 발생
|
|
68
|
+
*/
|
|
26
69
|
export function useSharedData<
|
|
27
70
|
TSharedData extends Record<string, unknown> = Record<string, unknown>,
|
|
28
71
|
>(): SharedDataValue<TSharedData> {
|
|
29
72
|
const context = useContext(SharedDataContext);
|
|
30
73
|
if (!context) {
|
|
31
|
-
throw new Error(
|
|
32
|
-
"useSharedData는 SharedDataProvider 내부에서만 사용할 수 있습니다. SharedDataProvider는 ServiceClientProvider 아래에 위치해야 합니다",
|
|
33
|
-
);
|
|
74
|
+
throw new Error("useSharedData는 SharedDataProvider 내부에서만 사용할 수 있습니다");
|
|
34
75
|
}
|
|
35
76
|
return context as unknown as SharedDataValue<TSharedData>;
|
|
36
77
|
}
|
|
@@ -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,95 @@ 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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
await loadData(name, def, changeKeys);
|
|
114
|
-
})
|
|
115
|
-
.then((key) => {
|
|
116
|
-
listenerKeyMap.set(name, key);
|
|
118
|
+
function configure(
|
|
119
|
+
fn: (
|
|
120
|
+
origin: Record<string, SharedDataDefinition<unknown>>,
|
|
121
|
+
) => Record<string, SharedDataDefinition<unknown>>,
|
|
122
|
+
): void {
|
|
123
|
+
if (configured) {
|
|
124
|
+
throw new Error("SharedDataProvider: configure()는 1회만 호출할 수 있습니다");
|
|
125
|
+
}
|
|
126
|
+
configured = true;
|
|
127
|
+
|
|
128
|
+
const definitions = fn({});
|
|
129
|
+
currentDefinitions = definitions;
|
|
130
|
+
|
|
131
|
+
for (const [name, def] of Object.entries(definitions)) {
|
|
132
|
+
const [items, setItems] = createSignal<unknown[]>([]);
|
|
133
|
+
// eslint-disable-next-line solid/reactivity -- signal 참조를 Map에 저장하는 것은 반응성 접근이 아님
|
|
134
|
+
signalMap.set(name, [items, setItems]);
|
|
135
|
+
|
|
136
|
+
const itemMap = createMemo(() => {
|
|
137
|
+
const map = new Map<string | number, unknown>();
|
|
138
|
+
for (const item of items()) {
|
|
139
|
+
map.set(def.getKey(item as never), item);
|
|
140
|
+
}
|
|
141
|
+
return map;
|
|
117
142
|
});
|
|
143
|
+
// eslint-disable-next-line solid/reactivity -- memo 참조를 Map에 저장하는 것은 반응성 접근이 아님
|
|
144
|
+
memoMap.set(name, itemMap);
|
|
118
145
|
|
|
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(
|
|
146
|
+
const client = serviceClient.get(def.serviceKey);
|
|
147
|
+
void client
|
|
148
|
+
.addEventListener(
|
|
129
149
|
SharedDataChangeEvent,
|
|
130
|
-
|
|
131
|
-
changeKeys
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
150
|
+
{ name, filter: def.filter },
|
|
151
|
+
async (changeKeys) => {
|
|
152
|
+
await loadData(name, def, changeKeys);
|
|
153
|
+
},
|
|
154
|
+
)
|
|
155
|
+
.then((key) => {
|
|
156
|
+
listenerKeyMap.set(name, key);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
void loadData(name, def);
|
|
160
|
+
|
|
161
|
+
accessors[name] = {
|
|
162
|
+
items,
|
|
163
|
+
get: (key: string | number | undefined) => {
|
|
164
|
+
if (key === undefined) return undefined;
|
|
165
|
+
return itemMap().get(key);
|
|
166
|
+
},
|
|
167
|
+
emit: async (changeKeys?: Array<string | number>) => {
|
|
168
|
+
await client.emitToServer(
|
|
169
|
+
SharedDataChangeEvent,
|
|
170
|
+
(info) => info.name === name && objEqual(info.filter, def.filter),
|
|
171
|
+
changeKeys,
|
|
172
|
+
);
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
135
176
|
}
|
|
136
177
|
|
|
137
178
|
onCleanup(() => {
|
|
138
|
-
|
|
179
|
+
if (!currentDefinitions) return;
|
|
180
|
+
for (const [name] of Object.entries(currentDefinitions)) {
|
|
139
181
|
const listenerKey = listenerKeyMap.get(name);
|
|
140
182
|
if (listenerKey != null) {
|
|
141
|
-
const def =
|
|
183
|
+
const def = currentDefinitions[name];
|
|
142
184
|
const client = serviceClient.get(def.serviceKey);
|
|
143
185
|
void client.removeEventListener(listenerKey);
|
|
144
186
|
}
|
|
145
187
|
}
|
|
146
188
|
});
|
|
147
189
|
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
190
|
+
const KNOWN_KEYS = new Set(["wait", "busy", "configure"]);
|
|
191
|
+
|
|
192
|
+
// Proxy: configure 전 데이터 접근 시 throw
|
|
193
|
+
const contextValue = new Proxy(
|
|
194
|
+
{ wait, busy, configure } as SharedDataValue<Record<string, unknown>>,
|
|
195
|
+
{
|
|
196
|
+
get(target, prop: string) {
|
|
197
|
+
if (KNOWN_KEYS.has(prop)) {
|
|
198
|
+
return target[prop];
|
|
199
|
+
}
|
|
200
|
+
if (!configured) {
|
|
201
|
+
throw new Error("SharedDataProvider: configure()를 먼저 호출해야 합니다");
|
|
202
|
+
}
|
|
203
|
+
return accessors[prop];
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
);
|
|
153
207
|
|
|
154
208
|
return (
|
|
155
209
|
<SharedDataContext.Provider value={contextValue}>{props.children}</SharedDataContext.Provider>
|