@gomjellie/lazyapi 0.0.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.
Files changed (63) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +162 -0
  3. package/dist/App.d.ts +9 -0
  4. package/dist/App.js +65 -0
  5. package/dist/cli.d.ts +5 -0
  6. package/dist/cli.js +39 -0
  7. package/dist/components/common/ErrorMessage.d.ts +10 -0
  8. package/dist/components/common/ErrorMessage.js +17 -0
  9. package/dist/components/common/KeybindingFooter.d.ts +18 -0
  10. package/dist/components/common/KeybindingFooter.js +20 -0
  11. package/dist/components/common/LoadingSpinner.d.ts +9 -0
  12. package/dist/components/common/LoadingSpinner.js +15 -0
  13. package/dist/components/common/SearchInput.d.ts +14 -0
  14. package/dist/components/common/SearchInput.js +72 -0
  15. package/dist/components/common/StatusBar.d.ts +14 -0
  16. package/dist/components/common/StatusBar.js +36 -0
  17. package/dist/components/detail-view/DetailView.d.ts +5 -0
  18. package/dist/components/detail-view/DetailView.js +266 -0
  19. package/dist/components/detail-view/LeftPanel.d.ts +19 -0
  20. package/dist/components/detail-view/LeftPanel.js +363 -0
  21. package/dist/components/detail-view/ParameterForm.d.ts +17 -0
  22. package/dist/components/detail-view/ParameterForm.js +77 -0
  23. package/dist/components/detail-view/RightPanel.d.ts +22 -0
  24. package/dist/components/detail-view/RightPanel.js +251 -0
  25. package/dist/components/list-view/EndpointList.d.ts +12 -0
  26. package/dist/components/list-view/EndpointList.js +72 -0
  27. package/dist/components/list-view/Explorer.d.ts +13 -0
  28. package/dist/components/list-view/Explorer.js +83 -0
  29. package/dist/components/list-view/FilterBar.d.ts +12 -0
  30. package/dist/components/list-view/FilterBar.js +33 -0
  31. package/dist/components/list-view/ListView.d.ts +5 -0
  32. package/dist/components/list-view/ListView.js +304 -0
  33. package/dist/components/list-view/SearchBar.d.ts +11 -0
  34. package/dist/components/list-view/SearchBar.js +14 -0
  35. package/dist/hooks/useApiClient.d.ts +11 -0
  36. package/dist/hooks/useApiClient.js +34 -0
  37. package/dist/hooks/useKeyPress.d.ts +26 -0
  38. package/dist/hooks/useKeyPress.js +57 -0
  39. package/dist/hooks/useNavigation.d.ts +10 -0
  40. package/dist/hooks/useNavigation.js +33 -0
  41. package/dist/services/http-client.d.ts +21 -0
  42. package/dist/services/http-client.js +173 -0
  43. package/dist/services/openapi-parser.d.ts +47 -0
  44. package/dist/services/openapi-parser.js +318 -0
  45. package/dist/store/appSlice.d.ts +14 -0
  46. package/dist/store/appSlice.js +144 -0
  47. package/dist/store/hooks.d.ts +9 -0
  48. package/dist/store/hooks.js +7 -0
  49. package/dist/store/index.d.ts +12 -0
  50. package/dist/store/index.js +12 -0
  51. package/dist/types/app-state.d.ts +126 -0
  52. package/dist/types/app-state.js +4 -0
  53. package/dist/types/openapi.d.ts +86 -0
  54. package/dist/types/openapi.js +115 -0
  55. package/dist/utils/clipboard.d.ts +11 -0
  56. package/dist/utils/clipboard.js +26 -0
  57. package/dist/utils/formatters.d.ts +35 -0
  58. package/dist/utils/formatters.js +93 -0
  59. package/dist/utils/schema-formatter.d.ts +21 -0
  60. package/dist/utils/schema-formatter.js +301 -0
  61. package/dist/utils/validators.d.ts +16 -0
  62. package/dist/utils/validators.js +158 -0
  63. package/package.json +88 -0
@@ -0,0 +1,144 @@
1
+ /**
2
+ * 애플리케이션 Redux Slice
3
+ */
4
+ import { createSlice } from '@reduxjs/toolkit';
5
+ // 초기 상태
6
+ const initialState = {
7
+ document: null,
8
+ endpoints: [],
9
+ screen: 'LIST',
10
+ mode: 'NORMAL',
11
+ list: {
12
+ selectedIndex: 0,
13
+ filteredEndpoints: [],
14
+ activeFilter: 'ALL',
15
+ activeTag: null,
16
+ focus: 'LIST',
17
+ selectedTagIndex: 0,
18
+ searchQuery: '',
19
+ tagSearchQuery: '',
20
+ scrollOffset: 0,
21
+ },
22
+ detail: null,
23
+ error: null,
24
+ loading: false,
25
+ commandInput: '',
26
+ };
27
+ const appSlice = createSlice({
28
+ name: 'app',
29
+ initialState,
30
+ reducers: {
31
+ setDocument: (state, action) => {
32
+ state.document = action.payload;
33
+ },
34
+ setEndpoints: (state, action) => {
35
+ state.endpoints = action.payload;
36
+ state.list.filteredEndpoints = action.payload;
37
+ },
38
+ setScreen: (state, action) => {
39
+ state.screen = action.payload;
40
+ },
41
+ setMode: (state, action) => {
42
+ state.mode = action.payload;
43
+ if (action.payload === 'COMMAND') {
44
+ state.commandInput = '';
45
+ }
46
+ },
47
+ setCommandInput: (state, action) => {
48
+ state.commandInput = action.payload;
49
+ },
50
+ setError: (state, action) => {
51
+ state.error = action.payload;
52
+ state.loading = false;
53
+ },
54
+ setLoading: (state, action) => {
55
+ state.loading = action.payload;
56
+ },
57
+ listSelect: (state, action) => {
58
+ state.list.selectedIndex = Math.max(0, Math.min(action.payload, state.list.filteredEndpoints.length - 1));
59
+ },
60
+ listSetFilter: (state, action) => {
61
+ state.list.activeFilter = action.payload;
62
+ state.list.selectedIndex = 0;
63
+ },
64
+ listSetTag: (state, action) => {
65
+ state.list.activeTag = action.payload;
66
+ state.list.selectedIndex = 0;
67
+ },
68
+ listSetFocus: (state, action) => {
69
+ state.list.focus = action.payload;
70
+ },
71
+ listSelectTag: (state, action) => {
72
+ state.list.selectedTagIndex = action.payload;
73
+ },
74
+ listSetSearch: (state, action) => {
75
+ state.list.searchQuery = action.payload;
76
+ state.list.selectedIndex = 0;
77
+ },
78
+ listSetTagSearch: (state, action) => {
79
+ state.list.tagSearchQuery = action.payload;
80
+ state.list.selectedTagIndex = 0;
81
+ },
82
+ listScroll: (state, action) => {
83
+ state.list.scrollOffset = action.payload;
84
+ },
85
+ detailSet: (state, action) => {
86
+ const endpoint = action.payload;
87
+ state.screen = 'DETAIL';
88
+ state.detail = {
89
+ endpoint,
90
+ focusedFieldIndex: 0,
91
+ parameterValues: {},
92
+ requestBody: '',
93
+ headers: {},
94
+ activePanel: 'left',
95
+ activeRequestTab: 'headers',
96
+ activeResponseTab: 'body',
97
+ response: null,
98
+ };
99
+ },
100
+ detailSetFocus: (state, action) => {
101
+ if (state.detail) {
102
+ state.detail.focusedFieldIndex = action.payload;
103
+ }
104
+ },
105
+ detailSetParam: (state, action) => {
106
+ if (state.detail) {
107
+ state.detail.parameterValues[action.payload.key] = action.payload.value;
108
+ }
109
+ },
110
+ detailSetBody: (state, action) => {
111
+ if (state.detail) {
112
+ state.detail.requestBody = action.payload;
113
+ }
114
+ },
115
+ detailSetHeader: (state, action) => {
116
+ if (state.detail) {
117
+ state.detail.headers[action.payload.key] = action.payload.value;
118
+ }
119
+ },
120
+ detailSetPanel: (state, action) => {
121
+ if (state.detail) {
122
+ state.detail.activePanel = action.payload;
123
+ }
124
+ },
125
+ detailSetRequestTab: (state, action) => {
126
+ if (state.detail) {
127
+ state.detail.activeRequestTab = action.payload;
128
+ }
129
+ },
130
+ detailSetResponseTab: (state, action) => {
131
+ if (state.detail) {
132
+ state.detail.activeResponseTab = action.payload;
133
+ }
134
+ },
135
+ detailSetResponse: (state, action) => {
136
+ if (state.detail) {
137
+ state.detail.response = action.payload;
138
+ }
139
+ },
140
+ reset: () => initialState,
141
+ },
142
+ });
143
+ export const { setDocument, setEndpoints, setScreen, setMode, setCommandInput, setError, setLoading, listSelect, listSetFilter, listSetTag, listSetFocus, listSelectTag, listSetSearch, listSetTagSearch, listScroll, detailSet, detailSetFocus, detailSetParam, detailSetBody, detailSetHeader, detailSetPanel, detailSetRequestTab, detailSetResponseTab, detailSetResponse, reset, } = appSlice.actions;
144
+ export default appSlice.reducer;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 타입이 지정된 Redux hooks
3
+ */
4
+ export declare const useAppDispatch: import("react-redux").UseDispatch<import("@reduxjs/toolkit").ThunkDispatch<{
5
+ app: import("../types/app-state.js").AppState;
6
+ }, undefined, import("@reduxjs/toolkit").UnknownAction> & import("@reduxjs/toolkit").Dispatch<import("@reduxjs/toolkit").UnknownAction>>;
7
+ export declare const useAppSelector: import("react-redux").UseSelector<{
8
+ app: import("../types/app-state.js").AppState;
9
+ }>;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 타입이 지정된 Redux hooks
3
+ */
4
+ import { useDispatch, useSelector } from 'react-redux';
5
+ // 타입이 지정된 useDispatch와 useSelector
6
+ export const useAppDispatch = useDispatch.withTypes();
7
+ export const useAppSelector = useSelector.withTypes();
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Redux Store 설정
3
+ */
4
+ export declare const store: import("@reduxjs/toolkit").EnhancedStore<{
5
+ app: import("../types/app-state.js").AppState;
6
+ }, import("@reduxjs/toolkit").UnknownAction, import("@reduxjs/toolkit").Tuple<[import("@reduxjs/toolkit").StoreEnhancer<{
7
+ dispatch: import("@reduxjs/toolkit").ThunkDispatch<{
8
+ app: import("../types/app-state.js").AppState;
9
+ }, undefined, import("@reduxjs/toolkit").UnknownAction>;
10
+ }>, import("@reduxjs/toolkit").StoreEnhancer]>>;
11
+ export type RootState = ReturnType<typeof store.getState>;
12
+ export type AppDispatch = typeof store.dispatch;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Redux Store 설정
3
+ */
4
+ import { configureStore } from '@reduxjs/toolkit';
5
+ import appReducer from './appSlice.js';
6
+ export const store = configureStore({
7
+ reducer: {
8
+ app: appReducer,
9
+ },
10
+ // DevTools는 개발 환경에서만 활성화 (기본값)
11
+ devTools: process.env.NODE_ENV !== 'production',
12
+ });
@@ -0,0 +1,126 @@
1
+ /**
2
+ * 애플리케이션 상태 타입 정의
3
+ */
4
+ import { Endpoint, HttpMethod, OpenAPIDocument } from './openapi.js';
5
+ export type AppMode = 'NORMAL' | 'INSERT' | 'SEARCH' | 'TAG_SEARCH' | 'COMMAND';
6
+ export type Screen = 'LIST' | 'DETAIL';
7
+ export interface AppState {
8
+ document: OpenAPIDocument | null;
9
+ endpoints: Endpoint[];
10
+ screen: Screen;
11
+ mode: AppMode;
12
+ commandInput: string;
13
+ list: ListViewState;
14
+ detail: DetailViewState | null;
15
+ error: string | null;
16
+ loading: boolean;
17
+ }
18
+ export interface ListViewState {
19
+ selectedIndex: number;
20
+ filteredEndpoints: Endpoint[];
21
+ activeFilter: HttpMethod | 'ALL';
22
+ activeTag: string | null;
23
+ focus: 'TAGS' | 'LIST';
24
+ selectedTagIndex: number;
25
+ searchQuery: string;
26
+ tagSearchQuery: string;
27
+ scrollOffset: number;
28
+ }
29
+ export interface DetailViewState {
30
+ endpoint: Endpoint;
31
+ focusedFieldIndex: number;
32
+ parameterValues: Record<string, string>;
33
+ requestBody: string;
34
+ headers: Record<string, string>;
35
+ activePanel: 'left' | 'request' | 'response';
36
+ activeRequestTab: 'headers' | 'body' | 'query';
37
+ activeResponseTab: 'body' | 'headers' | 'cookies' | 'curl';
38
+ response: ApiResponse | null;
39
+ }
40
+ export interface ApiResponse {
41
+ status: number;
42
+ statusText: string;
43
+ headers: Record<string, string>;
44
+ body: unknown;
45
+ size: number;
46
+ duration: number;
47
+ cookies?: Record<string, string>;
48
+ }
49
+ export type AppAction = {
50
+ type: 'SET_DOCUMENT';
51
+ payload: OpenAPIDocument;
52
+ } | {
53
+ type: 'SET_ENDPOINTS';
54
+ payload: Endpoint[];
55
+ } | {
56
+ type: 'SET_SCREEN';
57
+ payload: Screen;
58
+ } | {
59
+ type: 'SET_MODE';
60
+ payload: AppMode;
61
+ } | {
62
+ type: 'SET_ERROR';
63
+ payload: string | null;
64
+ } | {
65
+ type: 'SET_LOADING';
66
+ payload: boolean;
67
+ } | {
68
+ type: 'LIST_SELECT';
69
+ payload: number;
70
+ } | {
71
+ type: 'LIST_SET_FILTER';
72
+ payload: HttpMethod | 'ALL';
73
+ } | {
74
+ type: 'LIST_SET_TAG';
75
+ payload: string | null;
76
+ } | {
77
+ type: 'LIST_SET_FOCUS';
78
+ payload: 'TAGS' | 'LIST';
79
+ } | {
80
+ type: 'LIST_SELECT_TAG';
81
+ payload: number;
82
+ } | {
83
+ type: 'LIST_SET_SEARCH';
84
+ payload: string;
85
+ } | {
86
+ type: 'LIST_SET_TAG_SEARCH';
87
+ payload: string;
88
+ } | {
89
+ type: 'LIST_SCROLL';
90
+ payload: number;
91
+ } | {
92
+ type: 'DETAIL_SET';
93
+ payload: Endpoint;
94
+ } | {
95
+ type: 'DETAIL_SET_FOCUS';
96
+ payload: number;
97
+ } | {
98
+ type: 'DETAIL_SET_PARAM';
99
+ payload: {
100
+ key: string;
101
+ value: string;
102
+ };
103
+ } | {
104
+ type: 'DETAIL_SET_BODY';
105
+ payload: string;
106
+ } | {
107
+ type: 'DETAIL_SET_HEADER';
108
+ payload: {
109
+ key: string;
110
+ value: string;
111
+ };
112
+ } | {
113
+ type: 'DETAIL_SET_PANEL';
114
+ payload: 'left' | 'request' | 'response';
115
+ } | {
116
+ type: 'DETAIL_SET_REQUEST_TAB';
117
+ payload: 'headers' | 'body' | 'query';
118
+ } | {
119
+ type: 'DETAIL_SET_RESPONSE_TAB';
120
+ payload: 'body' | 'headers' | 'cookies' | 'curl';
121
+ } | {
122
+ type: 'DETAIL_SET_RESPONSE';
123
+ payload: ApiResponse | null;
124
+ } | {
125
+ type: 'RESET';
126
+ };
@@ -0,0 +1,4 @@
1
+ /**
2
+ * 애플리케이션 상태 타입 정의
3
+ */
4
+ export {};
@@ -0,0 +1,86 @@
1
+ /**
2
+ * OpenAPI 타입 정의
3
+ * openapi-types 패키지를 재수출하고 애플리케이션별 타입을 정의합니다.
4
+ */
5
+ import type { OpenAPI, OpenAPIV2, OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';
6
+ export type { OpenAPI, OpenAPIV2, OpenAPIV3, OpenAPIV3_1 };
7
+ export type OpenAPIDocument = OpenAPI.Document;
8
+ export type PathItemObject = OpenAPIV3_1.PathItemObject | OpenAPIV3.PathItemObject | OpenAPIV2.PathItemObject;
9
+ export type OperationObject = OpenAPIV3_1.OperationObject | OpenAPIV3.OperationObject | OpenAPIV2.OperationObject;
10
+ export type ParameterObject = OpenAPIV3_1.ParameterObject | OpenAPIV3.ParameterObject | OpenAPIV2.Parameter;
11
+ export type RequestBodyObject = OpenAPIV3_1.RequestBodyObject | OpenAPIV3.RequestBodyObject;
12
+ export type ResponseObject = OpenAPIV3_1.ResponseObject | OpenAPIV3.ResponseObject | OpenAPIV2.ResponseObject;
13
+ export type SchemaObject = OpenAPIV3_1.SchemaObject | OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject;
14
+ export type ReferenceObject = OpenAPIV3_1.ReferenceObject | OpenAPIV3.ReferenceObject | OpenAPIV2.ReferenceObject;
15
+ export type ResponsesObject = OpenAPIV3_1.ResponsesObject | OpenAPIV3.ResponsesObject | OpenAPIV2.ResponsesObject;
16
+ export type MediaTypeObject = OpenAPIV3_1.MediaTypeObject | OpenAPIV3.MediaTypeObject;
17
+ export type SecurityRequirementObject = OpenAPIV3_1.SecurityRequirementObject | OpenAPIV3.SecurityRequirementObject | OpenAPIV2.SecurityRequirementObject;
18
+ export type PathItem = PathItemObject;
19
+ export type Operation = OperationObject;
20
+ export type Parameter = ParameterObject;
21
+ export type RequestBody = RequestBodyObject;
22
+ export type Response = ResponseObject;
23
+ export type Schema = SchemaObject;
24
+ export declare function isOpenAPIV2Document(doc: OpenAPIDocument): doc is OpenAPIV2.Document;
25
+ export declare function isOpenAPIV3Document(doc: OpenAPIDocument): doc is OpenAPIV3.Document;
26
+ export declare function isOpenAPIV3_1Document(doc: OpenAPIDocument): doc is OpenAPIV3_1.Document;
27
+ export declare function isReferenceObject(obj: any): obj is ReferenceObject;
28
+ export declare function isSchemaObject(obj: any): obj is SchemaObject;
29
+ export declare function isParameterObject(obj: any): obj is ParameterObject;
30
+ export declare function isRequestBodyObject(obj: any): obj is RequestBodyObject;
31
+ export declare function isResponseObject(obj: any): obj is ResponseObject;
32
+ export interface NormalizedParameter {
33
+ name: string;
34
+ in: 'query' | 'header' | 'path' | 'cookie' | 'body' | 'formData';
35
+ description?: string;
36
+ required?: boolean;
37
+ deprecated?: boolean;
38
+ schema?: SchemaObject;
39
+ type?: string;
40
+ format?: string;
41
+ enum?: any[];
42
+ default?: any;
43
+ example?: any;
44
+ }
45
+ export interface NormalizedRequestBody {
46
+ description?: string;
47
+ required?: boolean;
48
+ schema?: SchemaObject;
49
+ }
50
+ export interface Endpoint {
51
+ id: string;
52
+ method: HttpMethod;
53
+ path: string;
54
+ summary?: string;
55
+ description?: string;
56
+ tags: string[];
57
+ parameters: NormalizedParameter[];
58
+ requestBody?: NormalizedRequestBody;
59
+ responses: ResponsesObject;
60
+ operationId?: string;
61
+ deprecated?: boolean;
62
+ security?: SecurityRequirementObject[];
63
+ }
64
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' | 'HEAD' | 'TRACE';
65
+ export declare const HTTP_METHODS: HttpMethod[];
66
+ export declare const METHOD_COLORS: Record<HttpMethod, string>;
67
+ /**
68
+ * OpenAPI 문서에서 서버 URL 가져오기 (버전별 처리)
69
+ */
70
+ export declare function getServerUrl(document: OpenAPIDocument): string | undefined;
71
+ /**
72
+ * OpenAPI 문서에서 모든 서버 URL 가져오기
73
+ */
74
+ export declare function getServerUrls(document: OpenAPIDocument): string[];
75
+ /**
76
+ * OpenAPI에서 $ref로 참조 가능한 모든 타입
77
+ */
78
+ export type ResolvableObject = SchemaObject | RequestBodyObject | ResponseObject | ParameterObject | MediaTypeObject;
79
+ /**
80
+ * $ref를 resolve하여 실제 객체 반환
81
+ * @param ref - $ref 경로 (예: "#/definitions/Pet", "#/components/schemas/Pet", "#/components/requestBodies/UpdateUserRequest")
82
+ * @param document - OpenAPI 문서
83
+ * @returns 해결된 객체 또는 undefined
84
+ * @template T - resolve할 타입 (기본값: SchemaObject)
85
+ */
86
+ export declare function resolveRef<T extends ResolvableObject = SchemaObject>(ref: string, document: OpenAPIDocument): T | undefined;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * OpenAPI 타입 정의
3
+ * openapi-types 패키지를 재수출하고 애플리케이션별 타입을 정의합니다.
4
+ */
5
+ // Type guards - 버전별 구분
6
+ export function isOpenAPIV2Document(doc) {
7
+ return 'swagger' in doc && doc.swagger === '2.0';
8
+ }
9
+ export function isOpenAPIV3Document(doc) {
10
+ return 'openapi' in doc && typeof doc.openapi === 'string' && doc.openapi.startsWith('3.0');
11
+ }
12
+ export function isOpenAPIV3_1Document(doc) {
13
+ return 'openapi' in doc && typeof doc.openapi === 'string' && doc.openapi.startsWith('3.1');
14
+ }
15
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
+ export function isReferenceObject(obj) {
17
+ return obj && typeof obj === 'object' && '$ref' in obj;
18
+ }
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ export function isSchemaObject(obj) {
21
+ return obj && typeof obj === 'object' && !('$ref' in obj);
22
+ }
23
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
+ export function isParameterObject(obj) {
25
+ return obj && typeof obj === 'object' && 'name' in obj && 'in' in obj && !('$ref' in obj);
26
+ }
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ export function isRequestBodyObject(obj) {
29
+ return obj && typeof obj === 'object' && 'content' in obj && !('$ref' in obj);
30
+ }
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ export function isResponseObject(obj) {
33
+ return obj && typeof obj === 'object' && 'description' in obj && !('$ref' in obj);
34
+ }
35
+ export const HTTP_METHODS = [
36
+ 'GET',
37
+ 'POST',
38
+ 'PUT',
39
+ 'DELETE',
40
+ 'PATCH',
41
+ 'OPTIONS',
42
+ 'HEAD',
43
+ 'TRACE',
44
+ ];
45
+ // HTTP 메서드별 색상 매핑
46
+ export const METHOD_COLORS = {
47
+ GET: '#61afef',
48
+ POST: '#13ec13',
49
+ PUT: '#e5c07b',
50
+ DELETE: '#e06c75',
51
+ PATCH: '#c678dd',
52
+ OPTIONS: '#56b6c2',
53
+ HEAD: '#abb2bf',
54
+ TRACE: '#98c379',
55
+ };
56
+ /**
57
+ * OpenAPI 문서에서 서버 URL 가져오기 (버전별 처리)
58
+ */
59
+ export function getServerUrl(document) {
60
+ if (isOpenAPIV2Document(document)) {
61
+ // Swagger 2.0: scheme + host + basePath
62
+ const scheme = document.schemes?.[0] || 'http';
63
+ const host = document.host || 'localhost';
64
+ const basePath = document.basePath || '';
65
+ return `${scheme}://${host}${basePath}`;
66
+ }
67
+ else if (isOpenAPIV3Document(document) || isOpenAPIV3_1Document(document)) {
68
+ // OpenAPI 3.x: servers[0].url
69
+ return document.servers?.[0]?.url;
70
+ }
71
+ return undefined;
72
+ }
73
+ /**
74
+ * OpenAPI 문서에서 모든 서버 URL 가져오기
75
+ */
76
+ export function getServerUrls(document) {
77
+ if (isOpenAPIV2Document(document)) {
78
+ const urls = [];
79
+ const schemes = document.schemes || ['http'];
80
+ const host = document.host || 'localhost';
81
+ const basePath = document.basePath || '';
82
+ for (const scheme of schemes) {
83
+ urls.push(`${scheme}://${host}${basePath}`);
84
+ }
85
+ return urls;
86
+ }
87
+ else if (isOpenAPIV3Document(document) || isOpenAPIV3_1Document(document)) {
88
+ const servers = document.servers || [];
89
+ return servers.map((s) => s.url);
90
+ }
91
+ return [];
92
+ }
93
+ /**
94
+ * $ref를 resolve하여 실제 객체 반환
95
+ * @param ref - $ref 경로 (예: "#/definitions/Pet", "#/components/schemas/Pet", "#/components/requestBodies/UpdateUserRequest")
96
+ * @param document - OpenAPI 문서
97
+ * @returns 해결된 객체 또는 undefined
98
+ * @template T - resolve할 타입 (기본값: SchemaObject)
99
+ */
100
+ export function resolveRef(ref, document) {
101
+ if (!ref.startsWith('#/')) {
102
+ // 외부 파일 참조는 지원하지 않음
103
+ return undefined;
104
+ }
105
+ const path = ref.slice(2).split('/'); // "#/" 제거하고 경로 분할
106
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
107
+ let current = document;
108
+ for (const segment of path) {
109
+ if (!current || typeof current !== 'object') {
110
+ return undefined;
111
+ }
112
+ current = current[segment];
113
+ }
114
+ return current;
115
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 클립보드 유틸리티
3
+ */
4
+ /**
5
+ * 텍스트를 클립보드에 복사
6
+ */
7
+ export declare function copyToClipboard(text: string): Promise<void>;
8
+ /**
9
+ * 클립보드에서 텍스트 가져오기
10
+ */
11
+ export declare function readFromClipboard(): Promise<string>;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * 클립보드 유틸리티
3
+ */
4
+ import clipboardy from 'clipboardy';
5
+ /**
6
+ * 텍스트를 클립보드에 복사
7
+ */
8
+ export async function copyToClipboard(text) {
9
+ try {
10
+ await clipboardy.write(text);
11
+ }
12
+ catch {
13
+ throw new Error('클립보드 복사 실패');
14
+ }
15
+ }
16
+ /**
17
+ * 클립보드에서 텍스트 가져오기
18
+ */
19
+ export async function readFromClipboard() {
20
+ try {
21
+ return await clipboardy.read();
22
+ }
23
+ catch {
24
+ throw new Error('클립보드 읽기 실패');
25
+ }
26
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * 포맷팅 유틸리티
3
+ */
4
+ /**
5
+ * JSON을 예쁘게 포맷팅
6
+ */
7
+ export declare function formatJSON(data: unknown, indent?: number): string;
8
+ /**
9
+ * 바이트 크기를 읽기 쉬운 형식으로 변환
10
+ */
11
+ export declare function formatBytes(bytes: number): string;
12
+ /**
13
+ * 밀리초를 읽기 쉬운 형식으로 변환
14
+ */
15
+ export declare function formatDuration(ms: number): string;
16
+ /**
17
+ * HTTP 상태 코드를 텍스트로 변환
18
+ */
19
+ export declare function formatStatusCode(status: number, statusText?: string): string;
20
+ /**
21
+ * 날짜를 포맷팅
22
+ */
23
+ export declare function formatTimestamp(date: Date): string;
24
+ /**
25
+ * URL 경로에서 파라미터 추출
26
+ */
27
+ export declare function extractPathParams(path: string): string[];
28
+ /**
29
+ * 긴 텍스트를 잘라내기
30
+ */
31
+ export declare function truncate(text: string, maxLength: number): string;
32
+ /**
33
+ * 라인 넘버 포맷팅
34
+ */
35
+ export declare function formatLineNumber(lineNumber: number, maxDigits: number): string;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * 포맷팅 유틸리티
3
+ */
4
+ /**
5
+ * JSON을 예쁘게 포맷팅
6
+ */
7
+ export function formatJSON(data, indent = 2) {
8
+ try {
9
+ return JSON.stringify(data, null, indent);
10
+ }
11
+ catch {
12
+ return String(data);
13
+ }
14
+ }
15
+ /**
16
+ * 바이트 크기를 읽기 쉬운 형식으로 변환
17
+ */
18
+ export function formatBytes(bytes) {
19
+ if (bytes === 0)
20
+ return '0 B';
21
+ const sizes = ['B', 'KB', 'MB', 'GB'];
22
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
23
+ const value = bytes / Math.pow(1024, i);
24
+ return `${value.toFixed(2)} ${sizes[i]}`;
25
+ }
26
+ /**
27
+ * 밀리초를 읽기 쉬운 형식으로 변환
28
+ */
29
+ export function formatDuration(ms) {
30
+ if (ms < 1000) {
31
+ return `${ms}ms`;
32
+ }
33
+ const seconds = ms / 1000;
34
+ if (seconds < 60) {
35
+ return `${seconds.toFixed(2)}s`;
36
+ }
37
+ const minutes = seconds / 60;
38
+ return `${minutes.toFixed(2)}m`;
39
+ }
40
+ /**
41
+ * HTTP 상태 코드를 텍스트로 변환
42
+ */
43
+ export function formatStatusCode(status, statusText) {
44
+ if (statusText) {
45
+ return `${status} ${statusText}`;
46
+ }
47
+ const statusTexts = {
48
+ 200: 'OK',
49
+ 201: 'Created',
50
+ 202: 'Accepted',
51
+ 204: 'No Content',
52
+ 400: 'Bad Request',
53
+ 401: 'Unauthorized',
54
+ 403: 'Forbidden',
55
+ 404: 'Not Found',
56
+ 500: 'Internal Server Error',
57
+ 502: 'Bad Gateway',
58
+ 503: 'Service Unavailable',
59
+ };
60
+ return `${status} ${statusTexts[status] || 'Unknown'}`;
61
+ }
62
+ /**
63
+ * 날짜를 포맷팅
64
+ */
65
+ export function formatTimestamp(date) {
66
+ const hours = String(date.getHours()).padStart(2, '0');
67
+ const minutes = String(date.getMinutes()).padStart(2, '0');
68
+ const seconds = String(date.getSeconds()).padStart(2, '0');
69
+ return `${hours}:${minutes}:${seconds}`;
70
+ }
71
+ /**
72
+ * URL 경로에서 파라미터 추출
73
+ */
74
+ export function extractPathParams(path) {
75
+ const matches = path.match(/\{([^}]+)\}/g);
76
+ if (!matches)
77
+ return [];
78
+ return matches.map((match) => match.slice(1, -1));
79
+ }
80
+ /**
81
+ * 긴 텍스트를 잘라내기
82
+ */
83
+ export function truncate(text, maxLength) {
84
+ if (text.length <= maxLength)
85
+ return text;
86
+ return text.slice(0, maxLength - 3) + '...';
87
+ }
88
+ /**
89
+ * 라인 넘버 포맷팅
90
+ */
91
+ export function formatLineNumber(lineNumber, maxDigits) {
92
+ return String(lineNumber).padStart(maxDigits, ' ');
93
+ }