mock-fried 1.0.0

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 (55) hide show
  1. package/README.md +229 -0
  2. package/dist/module.d.mts +125 -0
  3. package/dist/module.json +12 -0
  4. package/dist/module.mjs +160 -0
  5. package/dist/runtime/components/ApiExplorer.d.vue.ts +7 -0
  6. package/dist/runtime/components/ApiExplorer.vue +168 -0
  7. package/dist/runtime/components/ApiExplorer.vue.d.ts +7 -0
  8. package/dist/runtime/components/EndpointCard.d.vue.ts +24 -0
  9. package/dist/runtime/components/EndpointCard.vue +173 -0
  10. package/dist/runtime/components/EndpointCard.vue.d.ts +24 -0
  11. package/dist/runtime/components/ResponseViewer.d.vue.ts +16 -0
  12. package/dist/runtime/components/ResponseViewer.vue +78 -0
  13. package/dist/runtime/components/ResponseViewer.vue.d.ts +16 -0
  14. package/dist/runtime/components/RpcMethodCard.d.vue.ts +20 -0
  15. package/dist/runtime/components/RpcMethodCard.vue +129 -0
  16. package/dist/runtime/components/RpcMethodCard.vue.d.ts +20 -0
  17. package/dist/runtime/composables/index.d.ts +1 -0
  18. package/dist/runtime/composables/index.js +1 -0
  19. package/dist/runtime/composables/useApi.d.ts +19 -0
  20. package/dist/runtime/composables/useApi.js +5 -0
  21. package/dist/runtime/plugin.d.ts +7 -0
  22. package/dist/runtime/plugin.js +75 -0
  23. package/dist/runtime/server/handlers/openapi.d.ts +2 -0
  24. package/dist/runtime/server/handlers/openapi.js +346 -0
  25. package/dist/runtime/server/handlers/rpc.d.ts +7 -0
  26. package/dist/runtime/server/handlers/rpc.js +140 -0
  27. package/dist/runtime/server/handlers/schema.d.ts +7 -0
  28. package/dist/runtime/server/handlers/schema.js +190 -0
  29. package/dist/runtime/server/tsconfig.json +3 -0
  30. package/dist/runtime/server/utils/client-parser.d.ts +13 -0
  31. package/dist/runtime/server/utils/client-parser.js +272 -0
  32. package/dist/runtime/server/utils/mock/client-generator.d.ts +108 -0
  33. package/dist/runtime/server/utils/mock/client-generator.js +346 -0
  34. package/dist/runtime/server/utils/mock/index.d.ts +9 -0
  35. package/dist/runtime/server/utils/mock/index.js +38 -0
  36. package/dist/runtime/server/utils/mock/openapi-generator.d.ts +4 -0
  37. package/dist/runtime/server/utils/mock/openapi-generator.js +118 -0
  38. package/dist/runtime/server/utils/mock/pagination/cursor-manager.d.ts +38 -0
  39. package/dist/runtime/server/utils/mock/pagination/cursor-manager.js +129 -0
  40. package/dist/runtime/server/utils/mock/pagination/index.d.ts +8 -0
  41. package/dist/runtime/server/utils/mock/pagination/index.js +18 -0
  42. package/dist/runtime/server/utils/mock/pagination/page-manager.d.ts +41 -0
  43. package/dist/runtime/server/utils/mock/pagination/page-manager.js +96 -0
  44. package/dist/runtime/server/utils/mock/pagination/snapshot-store.d.ts +64 -0
  45. package/dist/runtime/server/utils/mock/pagination/snapshot-store.js +125 -0
  46. package/dist/runtime/server/utils/mock/pagination/types.d.ts +141 -0
  47. package/dist/runtime/server/utils/mock/pagination/types.js +14 -0
  48. package/dist/runtime/server/utils/mock/proto-generator.d.ts +12 -0
  49. package/dist/runtime/server/utils/mock/proto-generator.js +67 -0
  50. package/dist/runtime/server/utils/mock/shared.d.ts +69 -0
  51. package/dist/runtime/server/utils/mock/shared.js +150 -0
  52. package/dist/runtime/server/utils/mock-generator.d.ts +9 -0
  53. package/dist/runtime/server/utils/mock-generator.js +30 -0
  54. package/dist/types.d.mts +9 -0
  55. package/package.json +73 -0
package/README.md ADDED
@@ -0,0 +1,229 @@
1
+ # Mock-Fried
2
+
3
+ [![npm version][npm-version-src]][npm-version-href]
4
+ [![npm downloads][npm-downloads-src]][npm-downloads-href]
5
+ [![License][license-src]][license-href]
6
+ [![Nuxt][nuxt-src]][nuxt-href]
7
+
8
+ Nuxt 3 Mock API Module - OpenAPI & Protobuf RPC Mock Server
9
+
10
+ ## Features
11
+
12
+ - **OpenAPI Mock Server** - OpenAPI 스펙 기반 자동 Mock 응답 생성
13
+ - **Protobuf RPC Mock** - Proto 파일 기반 gRPC-style RPC Mock
14
+ - **API Explorer** - Swagger UI 스타일의 인터랙티브 API 테스트 UI
15
+ - **Type-Safe Client** - `$api` 클라이언트로 타입 안전한 API 호출
16
+ - **Zero Config** - 스펙 파일만 있으면 즉시 사용 가능
17
+
18
+ ## Quick Setup
19
+
20
+ ```bash
21
+ # npm
22
+ npm install mock-fried
23
+
24
+ # yarn
25
+ yarn add mock-fried
26
+
27
+ # pnpm
28
+ pnpm add mock-fried
29
+ ```
30
+
31
+ `nuxt.config.ts`에 모듈 추가:
32
+
33
+ ```typescript
34
+ export default defineNuxtConfig({
35
+ modules: ['mock-fried'],
36
+
37
+ mock: {
38
+ enable: true,
39
+ prefix: '/mock',
40
+ openapi: './mocks/openapi.yaml', // OpenAPI 스펙 경로
41
+ proto: './mocks/example.proto', // Proto 파일 경로
42
+ },
43
+ })
44
+ ```
45
+
46
+ ## Configuration
47
+
48
+ | Option | Type | Default | Description |
49
+ |--------|------|---------|-------------|
50
+ | `enable` | `boolean` | `true` | Mock 기능 활성화 |
51
+ | `prefix` | `string` | `'/mock'` | API 라우트 prefix |
52
+ | `openapi` | `string \| object` | - | OpenAPI 스펙 파일 경로 또는 클라이언트 패키지 설정 |
53
+ | `proto` | `string` | - | Proto 파일 경로 |
54
+
55
+ ### OpenAPI 설정 옵션
56
+
57
+ **1. 스펙 파일 직접 사용:**
58
+
59
+ ```typescript
60
+ mock: {
61
+ // 상대 경로
62
+ openapi: './mocks/openapi.yaml',
63
+
64
+ // 절대 경로
65
+ openapi: '/path/to/openapi.yaml',
66
+ }
67
+ ```
68
+
69
+ **2. 생성된 클라이언트 패키지 사용 (권장):**
70
+
71
+ ```typescript
72
+ mock: {
73
+ // openapi-generator로 생성된 TypeScript 클라이언트 패키지
74
+ openapi: {
75
+ package: '@your-org/api-client',
76
+ apisDir: 'src/apis', // optional, default: 'src/apis'
77
+ modelsDir: 'src/models', // optional, default: 'src/models'
78
+ },
79
+ }
80
+ ```
81
+
82
+ 생성된 클라이언트 패키지를 사용하면:
83
+
84
+ - 정확한 타입 기반 Mock 데이터 생성
85
+ - 실제 API 응답 구조와 일치하는 Mock 응답
86
+ - snake_case/camelCase JSON 키 변환 자동 처리
87
+
88
+ ## Usage
89
+
90
+ ### REST API (OpenAPI)
91
+
92
+ ```typescript
93
+ const { $api } = useNuxtApp()
94
+
95
+ // GET 요청
96
+ const users = await $api.rest('/users')
97
+
98
+ // Path 파라미터
99
+ const user = await $api.rest('/users/1')
100
+
101
+ // Query 파라미터
102
+ const products = await $api.rest('/products', {
103
+ params: { category: 'electronics', limit: 10 }
104
+ })
105
+
106
+ // POST 요청
107
+ const newUser = await $api.rest('/users', {
108
+ method: 'POST',
109
+ body: { name: 'John', email: 'john@example.com' }
110
+ })
111
+ ```
112
+
113
+ ### RPC (Protobuf)
114
+
115
+ ```typescript
116
+ const { $api } = useNuxtApp()
117
+
118
+ // 명시적 RPC 호출
119
+ const user = await $api.rpc('UserService', 'GetUser', { id: 1 })
120
+
121
+ // 동적 서비스 접근
122
+ const user = await $api.UserService.GetUser({ id: 1 })
123
+ const products = await $api.ProductService.ListProducts({ page: 1, limit: 10 })
124
+ ```
125
+
126
+ ### API Schema 조회
127
+
128
+ ```typescript
129
+ const { $api } = useNuxtApp()
130
+
131
+ // 전체 스키마 조회
132
+ const schema = await $api.getSchema()
133
+ // { openapi: {...}, rpc: {...} }
134
+ ```
135
+
136
+ ## API Explorer
137
+
138
+ 모듈에 포함된 API Explorer 컴포넌트로 Swagger UI 스타일의 인터페이스 제공:
139
+
140
+ ```vue
141
+ <template>
142
+ <MockApiExplorer
143
+ title="My API Explorer"
144
+ description="Interactive API Testing"
145
+ />
146
+ </template>
147
+ ```
148
+
149
+ ### 포함된 컴포넌트
150
+
151
+ | Component | Description |
152
+ |-----------|-------------|
153
+ | `<MockApiExplorer>` | 메인 API 탐색기 |
154
+ | `<MockEndpointCard>` | REST 엔드포인트 카드 |
155
+ | `<MockRpcMethodCard>` | RPC 메서드 카드 |
156
+ | `<MockResponseViewer>` | 응답 뷰어 모달 |
157
+
158
+ ## Endpoints
159
+
160
+ | Endpoint | Method | Description |
161
+ |----------|--------|-------------|
162
+ | `/mock/__schema` | GET | API 스키마 메타데이터 |
163
+ | `/mock/**` | * | OpenAPI Mock 핸들러 |
164
+ | `/mock/rpc/:service/:method` | POST | RPC Mock 핸들러 |
165
+
166
+ ## Development
167
+
168
+ ```bash
169
+ # Install dependencies
170
+ yarn install
171
+
172
+ # Generate type stubs
173
+ yarn dev:prepare
174
+
175
+ # Develop with OpenAPI playground
176
+ yarn dev:openapi
177
+
178
+ # Develop with Proto playground
179
+ yarn dev:proto
180
+
181
+ # Run ESLint
182
+ yarn lint
183
+ yarn lint:fix
184
+
185
+ # Format with Prettier
186
+ yarn format
187
+
188
+ # Run tests
189
+ yarn test
190
+ yarn test:watch
191
+
192
+ # Type check
193
+ yarn test:types
194
+ ```
195
+
196
+ ## Project Structure
197
+
198
+ ```
199
+ mock-fried/
200
+ ├── src/
201
+ │ ├── module.ts # 모듈 엔트리
202
+ │ ├── types.ts # 타입 정의
203
+ │ └── runtime/
204
+ │ ├── plugin.ts # $api 클라이언트 플러그인
205
+ │ ├── composables/ # useApi composable
206
+ │ ├── components/ # API Explorer 컴포넌트
207
+ │ └── server/
208
+ │ ├── handlers/ # Nitro 서버 핸들러
209
+ │ └── utils/ # Mock 데이터 생성 유틸
210
+ ├── playground-openapi/ # OpenAPI 테스트 환경
211
+ └── playground-proto/ # Proto 테스트 환경
212
+ ```
213
+
214
+ ## License
215
+
216
+ [MIT License](./LICENSE)
217
+
218
+ <!-- Badges -->
219
+ [npm-version-src]: https://img.shields.io/npm/v/mock-fried/latest.svg?style=flat&colorA=020420&colorB=00DC82
220
+ [npm-version-href]: https://npmjs.com/package/mock-fried
221
+
222
+ [npm-downloads-src]: https://img.shields.io/npm/dm/mock-fried.svg?style=flat&colorA=020420&colorB=00DC82
223
+ [npm-downloads-href]: https://npm.chart.dev/mock-fried
224
+
225
+ [license-src]: https://img.shields.io/npm/l/mock-fried.svg?style=flat&colorA=020420&colorB=00DC82
226
+ [license-href]: https://npmjs.com/package/mock-fried
227
+
228
+ [nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt
229
+ [nuxt-href]: https://nuxt.com
@@ -0,0 +1,125 @@
1
+ import * as _nuxt_schema from '@nuxt/schema';
2
+
3
+ /**
4
+ * Pagination 설정
5
+ */
6
+ interface MockPaginationConfig {
7
+ /**
8
+ * 캐싱 활성화
9
+ * @default true
10
+ */
11
+ cache?: boolean;
12
+ /**
13
+ * 캐시 TTL (밀리초)
14
+ * @default 1800000 (30분)
15
+ */
16
+ cacheTTL?: number;
17
+ /**
18
+ * 기본 총 아이템 수
19
+ * @default 100
20
+ */
21
+ defaultTotal?: number;
22
+ /**
23
+ * 기본 페이지 크기
24
+ * @default 20
25
+ */
26
+ defaultLimit?: number;
27
+ /**
28
+ * 응답에 snapshotId 포함 여부
29
+ * @default false
30
+ */
31
+ includeSnapshotId?: boolean;
32
+ }
33
+ /**
34
+ * Cursor 설정
35
+ */
36
+ interface MockCursorConfig {
37
+ /**
38
+ * 만료 활성화
39
+ * @default true
40
+ */
41
+ enableExpiry?: boolean;
42
+ /**
43
+ * Cursor TTL (밀리초)
44
+ * @default 3600000 (1시간)
45
+ */
46
+ cursorTTL?: number;
47
+ /**
48
+ * 정렬 정보 포함 여부
49
+ * @default false
50
+ */
51
+ includeSortInfo?: boolean;
52
+ }
53
+ /**
54
+ * 응답 포맷 타입
55
+ * - 'auto': 기존 동작 유지 (스키마 기반 자동)
56
+ * - 'standardized': 표준화된 응답 형식
57
+ */
58
+ type MockResponseFormat = 'auto' | 'standardized';
59
+ /**
60
+ * Mock Module Options
61
+ * nuxt.config.ts에서 mock 키로 설정
62
+ */
63
+ interface MockModuleOptions {
64
+ /**
65
+ * Mock 기능 활성화 여부
66
+ * @default true
67
+ */
68
+ enable: boolean;
69
+ /**
70
+ * Mock API 라우트 prefix
71
+ * @default '/mock'
72
+ */
73
+ prefix: string;
74
+ /**
75
+ * OpenAPI 설정
76
+ * - 문자열: 스펙 파일 경로 (yaml/json)
77
+ * - 객체: 생성된 클라이언트 패키지 설정
78
+ * @example './mocks/openapi.yaml'
79
+ * @example { package: '@org/openapi-client' }
80
+ */
81
+ openapi?: string | OpenApiClientConfig;
82
+ /**
83
+ * Proto 파일 또는 디렉토리 경로 (상대 경로)
84
+ * @example './mocks/example.proto' 또는 './mocks'
85
+ */
86
+ proto?: string;
87
+ /**
88
+ * Pagination 설정
89
+ */
90
+ pagination?: MockPaginationConfig;
91
+ /**
92
+ * Cursor 설정
93
+ */
94
+ cursor?: MockCursorConfig;
95
+ /**
96
+ * 응답 포맷
97
+ * @default 'auto'
98
+ */
99
+ responseFormat?: MockResponseFormat;
100
+ }
101
+ /**
102
+ * OpenAPI 클라이언트 패키지 설정
103
+ */
104
+ interface OpenApiClientConfig {
105
+ /**
106
+ * npm 패키지명
107
+ * @example '@ptcorp-eosikahair/openapi'
108
+ */
109
+ package: string;
110
+ /**
111
+ * API 파일 디렉토리 (패키지 루트 기준)
112
+ * @default 'src/apis'
113
+ */
114
+ apisDir?: string;
115
+ /**
116
+ * Model 파일 디렉토리 (패키지 루트 기준)
117
+ * @default 'src/models'
118
+ */
119
+ modelsDir?: string;
120
+ }
121
+
122
+ declare const _default: _nuxt_schema.NuxtModule<MockModuleOptions, MockModuleOptions, false>;
123
+
124
+ export { _default as default };
125
+ export type { MockModuleOptions, OpenApiClientConfig };
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "mock-fried",
3
+ "configKey": "mock",
4
+ "compatibility": {
5
+ "nuxt": ">=3.0.0"
6
+ },
7
+ "version": "1.0.0",
8
+ "builder": {
9
+ "@nuxt/module-builder": "1.0.2",
10
+ "unbuild": "3.6.1"
11
+ }
12
+ }
@@ -0,0 +1,160 @@
1
+ import { defineNuxtModule, createResolver, useLogger, addServerHandler, addPlugin, addImports, addComponentsDir } from '@nuxt/kit';
2
+ import { normalize, resolve } from 'pathe';
3
+ import { createRequire } from 'node:module';
4
+
5
+ function parsePackagePath(specPath) {
6
+ if (specPath.startsWith("@")) {
7
+ const parts = specPath.split("/");
8
+ if (parts.length >= 2) {
9
+ const pkg = `${parts[0]}/${parts[1]}`;
10
+ const subpath = parts.slice(2).join("/");
11
+ return { pkg, subpath: subpath || void 0 };
12
+ }
13
+ }
14
+ const slashIndex = specPath.indexOf("/");
15
+ if (slashIndex > 0) {
16
+ return {
17
+ pkg: specPath.slice(0, slashIndex),
18
+ subpath: specPath.slice(slashIndex + 1)
19
+ };
20
+ }
21
+ return { pkg: specPath };
22
+ }
23
+ function resolvePackagePath(specPath, rootDir, defaultFiles = []) {
24
+ if (specPath.startsWith("./") || specPath.startsWith("../")) {
25
+ return normalize(resolve(rootDir, specPath));
26
+ }
27
+ if (specPath.startsWith("/") || /^[a-z]:/i.test(specPath)) {
28
+ return normalize(specPath);
29
+ }
30
+ try {
31
+ const require = createRequire(rootDir + "/package.json");
32
+ const { pkg, subpath } = parsePackagePath(specPath);
33
+ const pkgJsonPath = require.resolve(`${pkg}/package.json`);
34
+ const pkgRoot = resolve(pkgJsonPath, "..");
35
+ if (subpath) {
36
+ return normalize(resolve(pkgRoot, subpath));
37
+ }
38
+ for (const file of defaultFiles) {
39
+ const filePath = resolve(pkgRoot, file);
40
+ return normalize(filePath);
41
+ }
42
+ const mainPath = require.resolve(pkg);
43
+ return normalize(mainPath);
44
+ } catch {
45
+ return normalize(resolve(rootDir, specPath));
46
+ }
47
+ }
48
+ function resolveOpenAPIPath(specPath, rootDir) {
49
+ return resolvePackagePath(specPath, rootDir, [
50
+ "openapi.yaml",
51
+ "openapi.yml",
52
+ "openapi.json",
53
+ "spec.yaml",
54
+ "spec.yml",
55
+ "spec.json",
56
+ "swagger.yaml",
57
+ "swagger.json"
58
+ ]);
59
+ }
60
+ function resolveProtoPath(specPath, rootDir) {
61
+ return resolvePackagePath(specPath, rootDir, [
62
+ "proto/index.proto",
63
+ "protos/index.proto",
64
+ "index.proto",
65
+ "main.proto",
66
+ "service.proto"
67
+ ]);
68
+ }
69
+ const module$1 = defineNuxtModule({
70
+ meta: {
71
+ name: "mock-fried",
72
+ configKey: "mock",
73
+ compatibility: {
74
+ nuxt: ">=3.0.0"
75
+ }
76
+ },
77
+ defaults: {
78
+ enable: true,
79
+ prefix: "/mock"
80
+ },
81
+ setup(options, nuxt) {
82
+ const resolver = createResolver(import.meta.url);
83
+ const logger = useLogger("mock-fried");
84
+ if (!options.enable) {
85
+ logger.info("Mock module is disabled");
86
+ return;
87
+ }
88
+ const rootDir = nuxt.options.rootDir;
89
+ const prefix = options.prefix || "/mock";
90
+ let openapiPath;
91
+ let clientPackagePath;
92
+ let clientPackageConfig;
93
+ if (options.openapi) {
94
+ if (typeof options.openapi === "string") {
95
+ openapiPath = resolveOpenAPIPath(options.openapi, rootDir);
96
+ } else {
97
+ clientPackageConfig = options.openapi;
98
+ try {
99
+ const require = createRequire(rootDir + "/package.json");
100
+ const pkgJsonPath = require.resolve(`${options.openapi.package}/package.json`);
101
+ clientPackagePath = normalize(resolve(pkgJsonPath, ".."));
102
+ } catch {
103
+ logger.warn(`Failed to resolve client package: ${options.openapi.package}`);
104
+ }
105
+ }
106
+ }
107
+ const protoPath = options.proto ? resolveProtoPath(options.proto, rootDir) : void 0;
108
+ nuxt.options.runtimeConfig.mock = {
109
+ enable: options.enable,
110
+ prefix,
111
+ openapiPath,
112
+ clientPackagePath,
113
+ clientPackageConfig,
114
+ protoPath,
115
+ pagination: options.pagination,
116
+ cursor: options.cursor,
117
+ responseFormat: options.responseFormat ?? "auto"
118
+ };
119
+ nuxt.options.runtimeConfig.public.mock = {
120
+ enable: options.enable,
121
+ prefix
122
+ };
123
+ addServerHandler({
124
+ route: `${prefix}/__schema`,
125
+ method: "get",
126
+ handler: resolver.resolve("./runtime/server/handlers/schema")
127
+ });
128
+ logger.info(`Schema handler registered at GET ${prefix}/__schema`);
129
+ if (protoPath) {
130
+ addServerHandler({
131
+ route: `${prefix}/rpc/:service/:method`,
132
+ method: "post",
133
+ handler: resolver.resolve("./runtime/server/handlers/rpc")
134
+ });
135
+ logger.info(`RPC mock handler registered at POST ${prefix}/rpc/:service/:method`);
136
+ }
137
+ if (openapiPath || clientPackagePath) {
138
+ addServerHandler({
139
+ route: `${prefix}/**`,
140
+ handler: resolver.resolve("./runtime/server/handlers/openapi")
141
+ });
142
+ logger.info(`OpenAPI mock handler registered at ${prefix}/**`);
143
+ }
144
+ addPlugin(resolver.resolve("./runtime/plugin"));
145
+ addImports({
146
+ name: "useApi",
147
+ from: resolver.resolve("./runtime/composables")
148
+ });
149
+ addComponentsDir({
150
+ path: resolver.resolve("./runtime/components"),
151
+ prefix: "Mock"
152
+ });
153
+ logger.success(`Mock module initialized with prefix: ${prefix}`);
154
+ if (openapiPath) logger.info(` OpenAPI spec: ${openapiPath}`);
155
+ if (clientPackagePath) logger.info(` Client package: ${clientPackageConfig?.package}`);
156
+ if (protoPath) logger.info(` Proto path: ${protoPath}`);
157
+ }
158
+ });
159
+
160
+ export { module$1 as default };
@@ -0,0 +1,7 @@
1
+ type __VLS_Props = {
2
+ title?: string;
3
+ description?: string;
4
+ };
5
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -0,0 +1,168 @@
1
+ <template>
2
+ <div class="api-explorer">
3
+ <header class="explorer-header">
4
+ <h1>{{ title }}</h1>
5
+ <p
6
+ v-if="description"
7
+ class="description"
8
+ >
9
+ {{ description }}
10
+ </p>
11
+ </header>
12
+
13
+ <div
14
+ v-if="loading"
15
+ class="loading"
16
+ >
17
+ Loading API schema...
18
+ </div>
19
+
20
+ <div
21
+ v-else-if="error"
22
+ class="error"
23
+ >
24
+ {{ error }}
25
+ </div>
26
+
27
+ <template v-else>
28
+ <!-- OpenAPI Section -->
29
+ <section
30
+ v-if="schema?.openapi"
31
+ class="api-section"
32
+ >
33
+ <h2 class="section-title">
34
+ <span class="badge rest">REST</span>
35
+ {{ schema.openapi.info.title }}
36
+ <span class="version">v{{ schema.openapi.info.version }}</span>
37
+ </h2>
38
+
39
+ <div class="endpoints">
40
+ <EndpointCard
41
+ v-for="endpoint in schema.openapi.paths"
42
+ :key="`${endpoint.method}-${endpoint.path}`"
43
+ :endpoint="endpoint"
44
+ type="rest"
45
+ @execute="executeRest"
46
+ />
47
+ </div>
48
+ </section>
49
+
50
+ <!-- RPC Section -->
51
+ <section
52
+ v-if="schema?.rpc"
53
+ class="api-section"
54
+ >
55
+ <h2 class="section-title">
56
+ <span class="badge rpc">RPC</span>
57
+ {{ schema.rpc.package || "Services" }}
58
+ </h2>
59
+
60
+ <div
61
+ v-for="service in schema.rpc.services"
62
+ :key="service.name"
63
+ class="service-group"
64
+ >
65
+ <h3 class="service-name">
66
+ {{ service.name }}
67
+ </h3>
68
+ <div class="endpoints">
69
+ <RpcMethodCard
70
+ v-for="method in service.methods"
71
+ :key="method.name"
72
+ :service="service.name"
73
+ :method="method"
74
+ @execute="executeRpc"
75
+ />
76
+ </div>
77
+ </div>
78
+ </section>
79
+ </template>
80
+
81
+ <!-- Response Modal -->
82
+ <ResponseViewer
83
+ v-if="response"
84
+ :response="response"
85
+ @close="response = null"
86
+ />
87
+ </div>
88
+ </template>
89
+
90
+ <script setup>
91
+ import EndpointCard from "./EndpointCard.vue";
92
+ import RpcMethodCard from "./RpcMethodCard.vue";
93
+ import ResponseViewer from "./ResponseViewer.vue";
94
+ defineProps({
95
+ title: { type: String, required: false },
96
+ description: { type: String, required: false }
97
+ });
98
+ const { $api } = useNuxtApp();
99
+ const schema = ref(null);
100
+ const loading = ref(true);
101
+ const error = ref(null);
102
+ const response = ref(null);
103
+ onMounted(async () => {
104
+ try {
105
+ schema.value = await $api.getSchema();
106
+ } catch (e) {
107
+ error.value = e instanceof Error ? e.message : "Failed to load schema";
108
+ } finally {
109
+ loading.value = false;
110
+ }
111
+ });
112
+ async function executeRest(params) {
113
+ const startTime = Date.now();
114
+ try {
115
+ let finalPath = params.path;
116
+ if (params.pathParams) {
117
+ for (const [key, value] of Object.entries(params.pathParams)) {
118
+ finalPath = finalPath.replace(`{${key}}`, value);
119
+ }
120
+ }
121
+ const data = await $api.rest(finalPath, {
122
+ method: params.method,
123
+ params: params.queryParams,
124
+ body: params.body
125
+ });
126
+ response.value = {
127
+ success: true,
128
+ data,
129
+ time: Date.now() - startTime,
130
+ type: "rest",
131
+ info: `${params.method} ${finalPath}`
132
+ };
133
+ } catch (e) {
134
+ response.value = {
135
+ success: false,
136
+ data: e instanceof Error ? e.message : "Unknown error",
137
+ time: Date.now() - startTime,
138
+ type: "rest",
139
+ info: `${params.method} ${params.path}`
140
+ };
141
+ }
142
+ }
143
+ async function executeRpc(params) {
144
+ const startTime = Date.now();
145
+ try {
146
+ const data = await $api.rpc(params.service, params.method, params.body);
147
+ response.value = {
148
+ success: true,
149
+ data,
150
+ time: Date.now() - startTime,
151
+ type: "rpc",
152
+ info: `${params.service}.${params.method}`
153
+ };
154
+ } catch (e) {
155
+ response.value = {
156
+ success: false,
157
+ data: e instanceof Error ? e.message : "Unknown error",
158
+ time: Date.now() - startTime,
159
+ type: "rpc",
160
+ info: `${params.service}.${params.method}`
161
+ };
162
+ }
163
+ }
164
+ </script>
165
+
166
+ <style scoped>
167
+ .api-explorer{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;margin:0 auto;max-width:1200px;padding:2rem}.explorer-header{margin-bottom:2rem}.explorer-header h1{color:#1a1a1a;font-size:1.75rem;margin:0 0 .5rem}.explorer-header .description{color:#666;margin:0}.error,.loading{border-radius:8px;padding:2rem;text-align:center}.loading{background:#f5f5f5;color:#666}.error{background:#fff5f5;border:1px solid #dc3545;color:#dc3545}.api-section{margin:2rem 0}.section-title{align-items:center;color:#333;display:flex;font-size:1.25rem;gap:.75rem;margin:0 0 1rem}.section-title .version{color:#666;font-size:.875rem;font-weight:400}.badge{border-radius:4px;display:inline-block;font-size:.75rem;font-weight:600;padding:.25rem .5rem;text-transform:uppercase}.badge.rest{background:#61affe;color:#fff}.badge.rpc{background:#7c3aed;color:#fff}.service-group{margin:1.5rem 0}.service-name{border-bottom:2px solid #e0e0e0;color:#444;font-size:1.1rem;margin:0 0 1rem;padding-bottom:.5rem}.endpoints{display:flex;flex-direction:column;gap:.75rem}
168
+ </style>