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.
- package/README.md +229 -0
- package/dist/module.d.mts +125 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +160 -0
- package/dist/runtime/components/ApiExplorer.d.vue.ts +7 -0
- package/dist/runtime/components/ApiExplorer.vue +168 -0
- package/dist/runtime/components/ApiExplorer.vue.d.ts +7 -0
- package/dist/runtime/components/EndpointCard.d.vue.ts +24 -0
- package/dist/runtime/components/EndpointCard.vue +173 -0
- package/dist/runtime/components/EndpointCard.vue.d.ts +24 -0
- package/dist/runtime/components/ResponseViewer.d.vue.ts +16 -0
- package/dist/runtime/components/ResponseViewer.vue +78 -0
- package/dist/runtime/components/ResponseViewer.vue.d.ts +16 -0
- package/dist/runtime/components/RpcMethodCard.d.vue.ts +20 -0
- package/dist/runtime/components/RpcMethodCard.vue +129 -0
- package/dist/runtime/components/RpcMethodCard.vue.d.ts +20 -0
- package/dist/runtime/composables/index.d.ts +1 -0
- package/dist/runtime/composables/index.js +1 -0
- package/dist/runtime/composables/useApi.d.ts +19 -0
- package/dist/runtime/composables/useApi.js +5 -0
- package/dist/runtime/plugin.d.ts +7 -0
- package/dist/runtime/plugin.js +75 -0
- package/dist/runtime/server/handlers/openapi.d.ts +2 -0
- package/dist/runtime/server/handlers/openapi.js +346 -0
- package/dist/runtime/server/handlers/rpc.d.ts +7 -0
- package/dist/runtime/server/handlers/rpc.js +140 -0
- package/dist/runtime/server/handlers/schema.d.ts +7 -0
- package/dist/runtime/server/handlers/schema.js +190 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/runtime/server/utils/client-parser.d.ts +13 -0
- package/dist/runtime/server/utils/client-parser.js +272 -0
- package/dist/runtime/server/utils/mock/client-generator.d.ts +108 -0
- package/dist/runtime/server/utils/mock/client-generator.js +346 -0
- package/dist/runtime/server/utils/mock/index.d.ts +9 -0
- package/dist/runtime/server/utils/mock/index.js +38 -0
- package/dist/runtime/server/utils/mock/openapi-generator.d.ts +4 -0
- package/dist/runtime/server/utils/mock/openapi-generator.js +118 -0
- package/dist/runtime/server/utils/mock/pagination/cursor-manager.d.ts +38 -0
- package/dist/runtime/server/utils/mock/pagination/cursor-manager.js +129 -0
- package/dist/runtime/server/utils/mock/pagination/index.d.ts +8 -0
- package/dist/runtime/server/utils/mock/pagination/index.js +18 -0
- package/dist/runtime/server/utils/mock/pagination/page-manager.d.ts +41 -0
- package/dist/runtime/server/utils/mock/pagination/page-manager.js +96 -0
- package/dist/runtime/server/utils/mock/pagination/snapshot-store.d.ts +64 -0
- package/dist/runtime/server/utils/mock/pagination/snapshot-store.js +125 -0
- package/dist/runtime/server/utils/mock/pagination/types.d.ts +141 -0
- package/dist/runtime/server/utils/mock/pagination/types.js +14 -0
- package/dist/runtime/server/utils/mock/proto-generator.d.ts +12 -0
- package/dist/runtime/server/utils/mock/proto-generator.js +67 -0
- package/dist/runtime/server/utils/mock/shared.d.ts +69 -0
- package/dist/runtime/server/utils/mock/shared.js +150 -0
- package/dist/runtime/server/utils/mock-generator.d.ts +9 -0
- package/dist/runtime/server/utils/mock-generator.js +30 -0
- package/dist/types.d.mts +9 -0
- 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 };
|
package/dist/module.json
ADDED
package/dist/module.mjs
ADDED
|
@@ -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>
|