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
@@ -0,0 +1,346 @@
1
+ import { defineEventHandler, getQuery, readBody, createError, getRequestURL } from "h3";
2
+ import { useRuntimeConfig } from "#imports";
3
+ import { readFileSync } from "node:fs";
4
+ import yaml from "js-yaml";
5
+ import {
6
+ generateMockFromSchema,
7
+ hashString,
8
+ SchemaMockGenerator,
9
+ extractDataModelName,
10
+ CursorPaginationManager,
11
+ PagePaginationManager
12
+ } from "../utils/mock/index.js";
13
+ import { getClientPackage } from "../utils/client-parser.js";
14
+ let apiInstance = null;
15
+ let cachedSpecPath = null;
16
+ function loadOpenAPISpec(specPath) {
17
+ const content = readFileSync(specPath, "utf-8");
18
+ if (specPath.endsWith(".yaml") || specPath.endsWith(".yml")) {
19
+ return yaml.load(content);
20
+ }
21
+ return JSON.parse(content);
22
+ }
23
+ async function getOpenAPIBackend(specPath) {
24
+ if (apiInstance && cachedSpecPath === specPath) {
25
+ return apiInstance;
26
+ }
27
+ const { OpenAPIBackend } = await import("openapi-backend");
28
+ const definition = loadOpenAPISpec(specPath);
29
+ apiInstance = new OpenAPIBackend({
30
+ definition,
31
+ quick: true
32
+ });
33
+ apiInstance.register({
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
+ notFound: async (_c) => {
36
+ return {
37
+ statusCode: 404,
38
+ body: { error: "Not found", message: "No matching operation found" }
39
+ };
40
+ },
41
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
42
+ notImplemented: async (c) => {
43
+ const operationId = c.operation?.operationId || "unknown";
44
+ const responses = c.operation?.responses;
45
+ const successResponse = responses?.["200"] || responses?.["201"] || Object.values(responses || {})[0];
46
+ const content = successResponse?.content;
47
+ const jsonContent = content?.["application/json"];
48
+ let mockData = null;
49
+ if (jsonContent?.example) {
50
+ mockData = jsonContent.example;
51
+ } else if (jsonContent?.schema) {
52
+ const seed = hashString(operationId + JSON.stringify(c.request?.params || {}));
53
+ mockData = generateMockFromSchema(jsonContent.schema, seed);
54
+ }
55
+ return {
56
+ statusCode: 200,
57
+ body: mockData,
58
+ meta: { operationId }
59
+ };
60
+ },
61
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
62
+ validationFail: async (c) => {
63
+ return {
64
+ statusCode: 400,
65
+ body: {
66
+ error: "Validation failed",
67
+ details: c.validation?.errors || []
68
+ }
69
+ };
70
+ }
71
+ });
72
+ await apiInstance.init();
73
+ cachedSpecPath = specPath;
74
+ return apiInstance;
75
+ }
76
+ let cachedClientPackage = null;
77
+ let cachedClientPath = null;
78
+ let mockGenerator = null;
79
+ let cursorPaginationManager = null;
80
+ let pagePaginationManager = null;
81
+ function getClientPackageData(packagePath, config, paginationConfig, cursorConfig) {
82
+ if (cachedClientPackage && cachedClientPath === packagePath && mockGenerator && cursorPaginationManager && pagePaginationManager) {
83
+ return {
84
+ package: cachedClientPackage,
85
+ generator: mockGenerator,
86
+ cursorManager: cursorPaginationManager,
87
+ pageManager: pagePaginationManager
88
+ };
89
+ }
90
+ cachedClientPackage = getClientPackage(packagePath, config);
91
+ cachedClientPath = packagePath;
92
+ mockGenerator = new SchemaMockGenerator(cachedClientPackage.models);
93
+ cursorPaginationManager = new CursorPaginationManager(mockGenerator, {
94
+ cursorConfig
95
+ });
96
+ pagePaginationManager = new PagePaginationManager(mockGenerator, {
97
+ config: paginationConfig
98
+ });
99
+ return {
100
+ package: cachedClientPackage,
101
+ generator: mockGenerator,
102
+ cursorManager: cursorPaginationManager,
103
+ pageManager: pagePaginationManager
104
+ };
105
+ }
106
+ function getPathSpecificity(path) {
107
+ const segments = path.split("/").filter(Boolean);
108
+ const paramCount = (path.match(/\{(\w+)\}/g) || []).length;
109
+ return segments.length * 100 - paramCount * 10;
110
+ }
111
+ function findMatchingEndpoint(endpoints, path, method) {
112
+ const normalizedMethod = method.toUpperCase();
113
+ const sortedEndpoints = [...endpoints].filter((e) => e.method === normalizedMethod).sort((a, b) => getPathSpecificity(b.path) - getPathSpecificity(a.path));
114
+ for (const endpoint of sortedEndpoints) {
115
+ const pattern = endpoint.path.replace(/\{(\w+)\}/g, "([^/]+)");
116
+ const regex = new RegExp(`^${pattern}$`);
117
+ const match = path.match(regex);
118
+ if (match) {
119
+ const pathParams = {};
120
+ const paramNames = endpoint.path.match(/\{(\w+)\}/g) || [];
121
+ paramNames.forEach((param, index) => {
122
+ const paramName = param.slice(1, -1);
123
+ pathParams[paramName] = match[index + 1] || "";
124
+ });
125
+ return { endpoint, pathParams };
126
+ }
127
+ }
128
+ return null;
129
+ }
130
+ function handleClientPackageRequest(pkg, generator, cursorManager, pageManager, path, method, query, _responseFormat = "auto") {
131
+ const match = findMatchingEndpoint(pkg.endpoints, path, method);
132
+ if (!match) {
133
+ return {
134
+ statusCode: 404,
135
+ body: { error: "Not found", message: `No matching endpoint for ${method} ${path}` }
136
+ };
137
+ }
138
+ const { endpoint, pathParams } = match;
139
+ const primitiveTypes = ["object", "string", "number", "boolean", "void", "any", "unknown"];
140
+ if (primitiveTypes.includes(endpoint.responseType.toLowerCase())) {
141
+ const pathLower = path.toLowerCase();
142
+ let primitiveResponse = {};
143
+ if (pathLower.includes("health")) {
144
+ primitiveResponse = { status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() };
145
+ } else if (pathLower.includes("ping")) {
146
+ primitiveResponse = { pong: true };
147
+ } else if (endpoint.responseType === "string") {
148
+ primitiveResponse = "success";
149
+ } else if (endpoint.responseType === "number") {
150
+ primitiveResponse = 0;
151
+ } else if (endpoint.responseType === "boolean") {
152
+ primitiveResponse = true;
153
+ }
154
+ return {
155
+ statusCode: 200,
156
+ body: primitiveResponse,
157
+ meta: {
158
+ operationId: endpoint.operationId,
159
+ apiClass: endpoint.apiClassName,
160
+ responseType: endpoint.responseType
161
+ }
162
+ };
163
+ }
164
+ const typeInfo = extractDataModelName(endpoint.responseType, pkg.models);
165
+ const { modelName, isList, listFieldName, wrapperType } = typeInfo;
166
+ const page = Number(query.page) || 1;
167
+ const limit = Number(query.limit) || Number(query.size) || 20;
168
+ const cursor = query.cursor;
169
+ const wrapperSchema = wrapperType ? pkg.models.get(wrapperType) : null;
170
+ const hasItemsField = listFieldName === "items";
171
+ const hasPaginationFields = wrapperSchema?.fields.some(
172
+ (f) => ["page", "totalPages", "total", "totalItems", "pagination"].includes(f.name)
173
+ );
174
+ let responseData;
175
+ if (isList) {
176
+ const seed = `${endpoint.path}-${JSON.stringify(pathParams)}`;
177
+ if (hasItemsField && hasPaginationFields) {
178
+ if (cursor) {
179
+ const result = cursorManager.getCursorPage(modelName, {
180
+ cursor,
181
+ limit,
182
+ total: 100,
183
+ seed
184
+ });
185
+ const { _snapshotId: _, ...responseWithoutSnapshotId } = result;
186
+ responseData = responseWithoutSnapshotId;
187
+ } else {
188
+ const result = pageManager.getPagedResponse(modelName, {
189
+ page,
190
+ limit,
191
+ total: 100,
192
+ seed
193
+ });
194
+ const { _snapshotId: _, ...responseWithoutSnapshotId } = result;
195
+ responseData = responseWithoutSnapshotId;
196
+ }
197
+ } else {
198
+ if (cursor) {
199
+ const result = cursorManager.getCursorPage(modelName, {
200
+ cursor,
201
+ limit,
202
+ total: 100,
203
+ seed
204
+ });
205
+ if (listFieldName) {
206
+ const listField = wrapperSchema?.fields.find((f) => f.name === listFieldName);
207
+ const listJsonKey = listField?.jsonKey || listFieldName;
208
+ const otherFields = {};
209
+ if (wrapperSchema) {
210
+ for (const field of wrapperSchema.fields) {
211
+ if (field.name !== listFieldName) {
212
+ const outputKey = field.jsonKey || field.name;
213
+ if (field.name === "nextCursor" || field.name === "cursor") {
214
+ otherFields[outputKey] = result.nextCursor;
215
+ } else if (field.name === "prevCursor") {
216
+ otherFields[outputKey] = result.prevCursor;
217
+ } else if (field.name === "hasMore") {
218
+ otherFields[outputKey] = result.hasMore;
219
+ } else if (field.name === "total" || field.name === "totalItems") {
220
+ otherFields[outputKey] = 100;
221
+ }
222
+ }
223
+ }
224
+ }
225
+ responseData = {
226
+ [listJsonKey]: result.items,
227
+ ...otherFields
228
+ };
229
+ } else {
230
+ responseData = result.items;
231
+ }
232
+ } else {
233
+ const result = cursorManager.getCursorPage(modelName, {
234
+ limit,
235
+ total: 100,
236
+ seed
237
+ });
238
+ if (listFieldName) {
239
+ const listField = wrapperSchema?.fields.find((f) => f.name === listFieldName);
240
+ const listJsonKey = listField?.jsonKey || listFieldName;
241
+ const otherFields = {};
242
+ if (wrapperSchema) {
243
+ for (const field of wrapperSchema.fields) {
244
+ if (field.name !== listFieldName) {
245
+ const outputKey = field.jsonKey || field.name;
246
+ if (field.name === "nextCursor" || field.name === "cursor") {
247
+ otherFields[outputKey] = result.nextCursor;
248
+ } else if (field.name === "prevCursor") {
249
+ otherFields[outputKey] = result.prevCursor;
250
+ } else if (field.name === "hasMore") {
251
+ otherFields[outputKey] = result.hasMore;
252
+ } else if (field.name === "total" || field.name === "totalItems") {
253
+ otherFields[outputKey] = 100;
254
+ }
255
+ }
256
+ }
257
+ }
258
+ responseData = {
259
+ [listJsonKey]: result.items,
260
+ ...otherFields
261
+ };
262
+ } else {
263
+ responseData = result.items;
264
+ }
265
+ }
266
+ }
267
+ } else {
268
+ const seed = `${endpoint.operationId}-${JSON.stringify(pathParams)}`;
269
+ responseData = generator.generateOne(endpoint.responseType, seed);
270
+ }
271
+ return {
272
+ statusCode: 200,
273
+ body: responseData,
274
+ meta: {
275
+ operationId: endpoint.operationId,
276
+ apiClass: endpoint.apiClassName,
277
+ responseType: endpoint.responseType
278
+ }
279
+ };
280
+ }
281
+ export default defineEventHandler(async (event) => {
282
+ const config = useRuntimeConfig(event);
283
+ const mockConfig = config.mock;
284
+ const requestUrl = getRequestURL(event);
285
+ const prefix = mockConfig?.prefix || "/mock";
286
+ let path = requestUrl.pathname;
287
+ if (path.startsWith(prefix)) {
288
+ path = path.substring(prefix.length) || "/";
289
+ }
290
+ const query = getQuery(event);
291
+ let body;
292
+ if (event.method !== "GET" && event.method !== "HEAD") {
293
+ try {
294
+ body = await readBody(event);
295
+ } catch {
296
+ body = void 0;
297
+ }
298
+ }
299
+ if (mockConfig?.clientPackagePath) {
300
+ const { package: pkg, generator, cursorManager, pageManager } = getClientPackageData(
301
+ mockConfig.clientPackagePath,
302
+ mockConfig.clientPackageConfig,
303
+ mockConfig.pagination,
304
+ mockConfig.cursor
305
+ );
306
+ const result = handleClientPackageRequest(
307
+ pkg,
308
+ generator,
309
+ cursorManager,
310
+ pageManager,
311
+ path,
312
+ event.method,
313
+ query,
314
+ mockConfig.responseFormat ?? "auto"
315
+ );
316
+ if (result.statusCode) {
317
+ event.node.res.statusCode = result.statusCode;
318
+ }
319
+ return result.body;
320
+ }
321
+ if (mockConfig?.openapiPath) {
322
+ const backend = await getOpenAPIBackend(mockConfig.openapiPath);
323
+ const headers = {};
324
+ const rawHeaders = event.headers;
325
+ if (rawHeaders) {
326
+ for (const [key, value] of Object.entries(rawHeaders)) {
327
+ if (value) headers[key] = String(value);
328
+ }
329
+ }
330
+ const result = await backend.handleRequest({
331
+ method: event.method,
332
+ path,
333
+ query,
334
+ body,
335
+ headers
336
+ });
337
+ if (result?.statusCode) {
338
+ event.node.res.statusCode = result.statusCode;
339
+ }
340
+ return result?.body ?? result;
341
+ }
342
+ throw createError({
343
+ statusCode: 500,
344
+ message: "OpenAPI configuration not found. Set openapi path or client package."
345
+ });
346
+ });
@@ -0,0 +1,7 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
+ success: boolean;
3
+ service: string;
4
+ method: string;
5
+ data: Record<string, unknown>;
6
+ }>>;
7
+ export default _default;
@@ -0,0 +1,140 @@
1
+ import { defineEventHandler, readBody, getRouterParams, createError } from "h3";
2
+ import { useRuntimeConfig } from "#imports";
3
+ import * as protoLoader from "@grpc/proto-loader";
4
+ import * as grpc from "@grpc/grpc-js";
5
+ import { readdirSync, statSync } from "node:fs";
6
+ import { join, extname } from "pathe";
7
+ import { generateMockMessage, deriveSeedFromRequest } from "../utils/mock/index.js";
8
+ let protoCache = null;
9
+ let cachedProtoPath = null;
10
+ function findProtoFiles(dirPath) {
11
+ const files = [];
12
+ const stat = statSync(dirPath);
13
+ if (stat.isFile() && extname(dirPath) === ".proto") {
14
+ return [dirPath];
15
+ }
16
+ if (stat.isDirectory()) {
17
+ const entries = readdirSync(dirPath);
18
+ for (const entry of entries) {
19
+ const fullPath = join(dirPath, entry);
20
+ const entryStat = statSync(fullPath);
21
+ if (entryStat.isFile() && extname(entry) === ".proto") {
22
+ files.push(fullPath);
23
+ } else if (entryStat.isDirectory()) {
24
+ files.push(...findProtoFiles(fullPath));
25
+ }
26
+ }
27
+ }
28
+ return files;
29
+ }
30
+ function extractServices(obj, services, prefix = "") {
31
+ for (const [key, value] of Object.entries(obj)) {
32
+ const fullName = prefix ? `${prefix}.${key}` : key;
33
+ if (typeof value === "function" && "service" in value) {
34
+ const serviceConstructor = value;
35
+ services.set(key, serviceConstructor.service);
36
+ services.set(fullName, serviceConstructor.service);
37
+ } else if (typeof value === "object" && value !== null) {
38
+ extractServices(value, services, fullName);
39
+ }
40
+ }
41
+ }
42
+ async function loadProto(protoPath) {
43
+ if (protoCache && cachedProtoPath === protoPath) {
44
+ return protoCache;
45
+ }
46
+ const protoFiles = findProtoFiles(protoPath);
47
+ if (protoFiles.length === 0) {
48
+ throw new Error(`No .proto files found in ${protoPath}`);
49
+ }
50
+ const packageDefinition = await protoLoader.load(protoFiles, {
51
+ keepCase: true,
52
+ longs: String,
53
+ enums: String,
54
+ defaults: true,
55
+ oneofs: true,
56
+ includeDirs: [protoPath]
57
+ });
58
+ const grpcObject = grpc.loadPackageDefinition(packageDefinition);
59
+ const services = /* @__PURE__ */ new Map();
60
+ extractServices(grpcObject, services);
61
+ protoCache = {
62
+ packageDefinition,
63
+ grpcObject,
64
+ services
65
+ };
66
+ cachedProtoPath = protoPath;
67
+ return protoCache;
68
+ }
69
+ function getResponseTypeInfo(methodDef) {
70
+ const responseType = methodDef.responseType;
71
+ if (responseType?.type) {
72
+ return responseType.type;
73
+ }
74
+ return {};
75
+ }
76
+ export default defineEventHandler(async (event) => {
77
+ const config = useRuntimeConfig(event);
78
+ const mockConfig = config.mock;
79
+ if (!mockConfig?.protoPath) {
80
+ throw createError({
81
+ statusCode: 500,
82
+ message: "Proto path not configured"
83
+ });
84
+ }
85
+ const params = getRouterParams(event);
86
+ const serviceName = params.service;
87
+ const methodName = params.method;
88
+ if (!serviceName || !methodName) {
89
+ throw createError({
90
+ statusCode: 400,
91
+ message: "Service and method are required"
92
+ });
93
+ }
94
+ let cache;
95
+ try {
96
+ cache = await loadProto(mockConfig.protoPath);
97
+ } catch (error) {
98
+ throw createError({
99
+ statusCode: 500,
100
+ message: `Failed to load proto files: ${error instanceof Error ? error.message : "Unknown error"}`
101
+ });
102
+ }
103
+ const serviceDefinition = cache.services.get(serviceName);
104
+ if (!serviceDefinition) {
105
+ const availableServices = Array.from(cache.services.keys()).filter((k) => !k.includes("."));
106
+ throw createError({
107
+ statusCode: 404,
108
+ message: `Service '${serviceName}' not found. Available services: ${availableServices.join(", ")}`
109
+ });
110
+ }
111
+ const methodDef = serviceDefinition[methodName];
112
+ if (!methodDef) {
113
+ const availableMethods = Object.keys(serviceDefinition);
114
+ throw createError({
115
+ statusCode: 404,
116
+ message: `Method '${methodName}' not found in service '${serviceName}'. Available methods: ${availableMethods.join(", ")}`
117
+ });
118
+ }
119
+ if (methodDef.requestStream || methodDef.responseStream) {
120
+ throw createError({
121
+ statusCode: 501,
122
+ message: "Streaming methods are not supported. Only unary RPC is available."
123
+ });
124
+ }
125
+ let requestBody;
126
+ try {
127
+ requestBody = await readBody(event);
128
+ } catch {
129
+ requestBody = {};
130
+ }
131
+ const responseTypeInfo = getResponseTypeInfo(methodDef);
132
+ const seed = deriveSeedFromRequest(requestBody);
133
+ const mockResponse = generateMockMessage(responseTypeInfo, seed);
134
+ return {
135
+ success: true,
136
+ service: serviceName,
137
+ method: methodName,
138
+ data: mockResponse
139
+ };
140
+ });
@@ -0,0 +1,7 @@
1
+ import type { ApiSchema } from '../../../types.js';
2
+ /**
3
+ * 스키마 핸들러
4
+ * GET /mock/__schema
5
+ */
6
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<ApiSchema>>;
7
+ export default _default;
@@ -0,0 +1,190 @@
1
+ import { defineEventHandler, createError } from "h3";
2
+ import { useRuntimeConfig } from "#imports";
3
+ import { readFileSync, existsSync } from "node:fs";
4
+ let cachedSchema = null;
5
+ function parseOpenApiSpec(specPath) {
6
+ if (!existsSync(specPath)) {
7
+ return void 0;
8
+ }
9
+ try {
10
+ const content = readFileSync(specPath, "utf-8");
11
+ let spec;
12
+ if (specPath.endsWith(".yaml") || specPath.endsWith(".yml")) {
13
+ const yaml = require("js-yaml");
14
+ spec = yaml.load(content);
15
+ } else {
16
+ spec = JSON.parse(content);
17
+ }
18
+ const info = spec.info || {};
19
+ const paths = spec.paths || {};
20
+ const pathItems = [];
21
+ for (const [path, methods] of Object.entries(paths)) {
22
+ for (const [method, operation] of Object.entries(methods)) {
23
+ if (["get", "post", "put", "delete", "patch"].includes(method.toLowerCase())) {
24
+ const op = operation;
25
+ const parameters = [];
26
+ if (Array.isArray(op.parameters)) {
27
+ for (const param of op.parameters) {
28
+ const p = param;
29
+ parameters.push({
30
+ name: p.name,
31
+ in: p.in,
32
+ required: p.required,
33
+ description: p.description,
34
+ schema: p.schema
35
+ });
36
+ }
37
+ }
38
+ pathItems.push({
39
+ path,
40
+ method: method.toUpperCase(),
41
+ operationId: op.operationId,
42
+ summary: op.summary,
43
+ description: op.description,
44
+ tags: op.tags,
45
+ parameters: parameters.length > 0 ? parameters : void 0,
46
+ requestBody: op.requestBody,
47
+ responses: op.responses
48
+ });
49
+ }
50
+ }
51
+ }
52
+ return {
53
+ info: {
54
+ title: info.title || "Unknown API",
55
+ version: info.version || "1.0.0",
56
+ description: info.description
57
+ },
58
+ paths: pathItems
59
+ };
60
+ } catch {
61
+ return void 0;
62
+ }
63
+ }
64
+ async function parseProtoSpec(protoPath) {
65
+ if (!existsSync(protoPath)) {
66
+ return void 0;
67
+ }
68
+ try {
69
+ const protoLoader = await import("@grpc/proto-loader");
70
+ const packageDefinition = await protoLoader.load(protoPath, {
71
+ keepCase: false,
72
+ longs: String,
73
+ enums: String,
74
+ defaults: true,
75
+ oneofs: true
76
+ });
77
+ const services = [];
78
+ let packageName;
79
+ for (const [fullName, def] of Object.entries(packageDefinition)) {
80
+ const definition = def;
81
+ if (!packageName && fullName.includes(".")) {
82
+ packageName = fullName.split(".").slice(0, -1).join(".");
83
+ }
84
+ if (!definition.format && typeof definition === "object") {
85
+ const methods = [];
86
+ for (const [methodName, methodDef] of Object.entries(definition)) {
87
+ const method = methodDef;
88
+ if (method.requestType && method.responseType) {
89
+ const reqType = method.requestType;
90
+ const resType = method.responseType;
91
+ const requestFields = extractFields(reqType);
92
+ const responseFields = extractFields(resType);
93
+ methods.push({
94
+ name: methodName,
95
+ requestType: reqType.name || "Unknown",
96
+ responseType: resType.name || "Unknown",
97
+ requestFields: requestFields.length > 0 ? requestFields : void 0,
98
+ responseFields: responseFields.length > 0 ? responseFields : void 0
99
+ });
100
+ }
101
+ }
102
+ if (methods.length > 0) {
103
+ const serviceName = fullName.split(".").pop() || fullName;
104
+ services.push({
105
+ name: serviceName,
106
+ methods
107
+ });
108
+ }
109
+ }
110
+ }
111
+ return {
112
+ package: packageName,
113
+ services
114
+ };
115
+ } catch {
116
+ return void 0;
117
+ }
118
+ }
119
+ function extractFields(messageType) {
120
+ const fields = [];
121
+ const typeObj = messageType.type;
122
+ if (typeObj && typeObj.field && Array.isArray(typeObj.field)) {
123
+ for (const field of typeObj.field) {
124
+ const f = field;
125
+ fields.push({
126
+ name: f.name,
127
+ type: getProtoTypeName(f.type, f.typeName),
128
+ repeated: f.label === 3,
129
+ // LABEL_REPEATED = 3
130
+ optional: f.label === 1
131
+ // LABEL_OPTIONAL = 1
132
+ });
133
+ }
134
+ }
135
+ return fields;
136
+ }
137
+ function getProtoTypeName(typeNum, typeName) {
138
+ const typeMap = {
139
+ 1: "double",
140
+ 2: "float",
141
+ 3: "int64",
142
+ 4: "uint64",
143
+ 5: "int32",
144
+ 6: "fixed64",
145
+ 7: "fixed32",
146
+ 8: "bool",
147
+ 9: "string",
148
+ 10: "group",
149
+ 11: "message",
150
+ 12: "bytes",
151
+ 13: "uint32",
152
+ 14: "enum",
153
+ 15: "sfixed32",
154
+ 16: "sfixed64",
155
+ 17: "sint32",
156
+ 18: "sint64"
157
+ };
158
+ if (typeName) {
159
+ return typeName.split(".").pop() || typeName;
160
+ }
161
+ return typeMap[typeNum] || "unknown";
162
+ }
163
+ export default defineEventHandler(async () => {
164
+ const config = useRuntimeConfig();
165
+ const mockConfig = config.mock;
166
+ if (cachedSchema) {
167
+ return cachedSchema;
168
+ }
169
+ const schema = {};
170
+ if (mockConfig.openapiPath) {
171
+ const openapi = parseOpenApiSpec(mockConfig.openapiPath);
172
+ if (openapi) {
173
+ schema.openapi = openapi;
174
+ }
175
+ }
176
+ if (mockConfig.protoPath) {
177
+ const rpc = await parseProtoSpec(mockConfig.protoPath);
178
+ if (rpc) {
179
+ schema.rpc = rpc;
180
+ }
181
+ }
182
+ if (!schema.openapi && !schema.rpc) {
183
+ throw createError({
184
+ statusCode: 404,
185
+ message: "No API schema available"
186
+ });
187
+ }
188
+ cachedSchema = schema;
189
+ return schema;
190
+ });
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "../../../.nuxt/tsconfig.server.json",
3
+ }