mock-fried 1.1.0 → 1.1.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.
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.0.0"
6
6
  },
7
- "version": "1.1.0",
7
+ "version": "1.1.1",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
@@ -88,6 +88,8 @@
88
88
  </template>
89
89
 
90
90
  <script setup>
91
+ import { ref, onMounted } from "vue";
92
+ import { useNuxtApp } from "#imports";
91
93
  import EndpointCard from "./EndpointCard.vue";
92
94
  import RpcMethodCard from "./RpcMethodCard.vue";
93
95
  import ResponseViewer from "./ResponseViewer.vue";
@@ -112,6 +112,7 @@
112
112
  </template>
113
113
 
114
114
  <script setup>
115
+ import { ref, computed } from "vue";
115
116
  const props = defineProps({
116
117
  endpoint: { type: Object, required: true },
117
118
  type: { type: String, required: true }
@@ -46,6 +46,7 @@
46
46
  </template>
47
47
 
48
48
  <script setup>
49
+ import { ref, computed } from "vue";
49
50
  const props = defineProps({
50
51
  response: { type: Object, required: true }
51
52
  });
@@ -64,6 +64,7 @@
64
64
  </template>
65
65
 
66
66
  <script setup>
67
+ import { ref, watch } from "vue";
67
68
  const props = defineProps({
68
69
  service: { type: String, required: true },
69
70
  method: { type: Object, required: true }
@@ -86,6 +86,7 @@ export function clearProtoCache() {
86
86
  protoPageManager = null;
87
87
  }
88
88
  const PROTO_TYPE_MAP = {
89
+ // 숫자 키 (legacy/fallback)
89
90
  1: "double",
90
91
  2: "float",
91
92
  3: "int64",
@@ -97,25 +98,57 @@ const PROTO_TYPE_MAP = {
97
98
  9: "string",
98
99
  10: "group",
99
100
  11: "message",
100
- // 중첩 메시지
101
101
  12: "bytes",
102
102
  13: "uint32",
103
103
  14: "enum",
104
104
  15: "sfixed32",
105
105
  16: "sfixed64",
106
106
  17: "sint32",
107
- 18: "sint64"
107
+ 18: "sint64",
108
+ // 문자열 키 (proto-loader 실제 반환값)
109
+ TYPE_DOUBLE: "double",
110
+ TYPE_FLOAT: "float",
111
+ TYPE_INT64: "int64",
112
+ TYPE_UINT64: "uint64",
113
+ TYPE_INT32: "int32",
114
+ TYPE_FIXED64: "fixed64",
115
+ TYPE_FIXED32: "fixed32",
116
+ TYPE_BOOL: "bool",
117
+ TYPE_STRING: "string",
118
+ TYPE_GROUP: "group",
119
+ TYPE_MESSAGE: "message",
120
+ TYPE_BYTES: "bytes",
121
+ TYPE_UINT32: "uint32",
122
+ TYPE_ENUM: "enum",
123
+ TYPE_SFIXED32: "sfixed32",
124
+ TYPE_SFIXED64: "sfixed64",
125
+ TYPE_SINT32: "sint32",
126
+ TYPE_SINT64: "sint64"
108
127
  };
109
128
  function findTypeInPackageDefinition(typeName, packageDefinition) {
110
129
  const cleanName = typeName.startsWith(".") ? typeName.slice(1) : typeName;
111
- const typeInfo = packageDefinition[cleanName];
112
- return typeInfo?.type || null;
130
+ const directMatch = packageDefinition[cleanName];
131
+ if (directMatch?.type) {
132
+ return directMatch.type;
133
+ }
134
+ if (!cleanName.includes(".")) {
135
+ for (const key of Object.keys(packageDefinition)) {
136
+ if (key.endsWith(`.${cleanName}`)) {
137
+ const matchedType = packageDefinition[key];
138
+ if (matchedType?.type) {
139
+ return matchedType.type;
140
+ }
141
+ }
142
+ }
143
+ }
144
+ return null;
113
145
  }
114
146
  function convertFieldDescriptor(fieldDesc, protoType, context) {
115
147
  const typeName = PROTO_TYPE_MAP[fieldDesc.type] || "string";
148
+ const isRepeated = fieldDesc.label === 3 || fieldDesc.label === "LABEL_REPEATED";
116
149
  const result = {
117
150
  type: typeName,
118
- rule: fieldDesc.label === 3 ? "repeated" : void 0
151
+ rule: isRepeated ? "repeated" : void 0
119
152
  };
120
153
  if (fieldDesc.typeName) {
121
154
  const shortName = fieldDesc.typeName.split(".").pop() || fieldDesc.typeName;
@@ -123,10 +156,6 @@ function convertFieldDescriptor(fieldDesc, protoType, context) {
123
156
  if (context.depth >= context.maxDepth) {
124
157
  return result;
125
158
  }
126
- if (context.visitedTypes.has(fieldDesc.typeName)) {
127
- result.resolvedType = { fields: {}, name: shortName };
128
- return result;
129
- }
130
159
  const enumType = protoType.enumType?.find(
131
160
  (e) => e.name === shortName
132
161
  );
@@ -185,9 +214,10 @@ function resolveProtoType(protoType, context) {
185
214
  };
186
215
  }
187
216
  function getResponseTypeInfo(methodDef, packageDefinition) {
188
- const responseType = methodDef.responseType;
217
+ const methodDefWithResponseType = methodDef;
218
+ const responseType = methodDefWithResponseType.responseType;
189
219
  if (!responseType?.type?.field) {
190
- return {};
220
+ return { name: "unknown" };
191
221
  }
192
222
  const context = {
193
223
  packageDefinition,
@@ -254,7 +284,8 @@ export default defineEventHandler(async (event) => {
254
284
  requestBody = {};
255
285
  }
256
286
  const responseTypeInfo = getResponseTypeInfo(methodDef, cache.packageDefinition);
257
- const seed = deriveSeedFromRequest(requestBody);
287
+ const seedNum = deriveSeedFromRequest(requestBody);
288
+ const seed = String(seedNum);
258
289
  const paginationInfo = analyzeProtoPagination(responseTypeInfo);
259
290
  let mockResponse;
260
291
  if (paginationInfo) {
@@ -323,7 +354,7 @@ export default defineEventHandler(async (event) => {
323
354
  mockResponse = responseData;
324
355
  }
325
356
  } else {
326
- mockResponse = generateMockMessage(responseTypeInfo, seed);
357
+ mockResponse = generateMockMessage(responseTypeInfo, seedNum);
327
358
  }
328
359
  return {
329
360
  success: true,
@@ -12,19 +12,22 @@ function parseApiFile(filePath, fileName) {
12
12
  while ((match = methodBodyRegex.exec(content)) !== null) {
13
13
  const methodName = match[1];
14
14
  const body = match[2];
15
- methodBodies.set(methodName, body);
15
+ if (methodName && body) {
16
+ methodBodies.set(methodName, body);
17
+ }
16
18
  }
17
19
  rawMethodRegex.lastIndex = 0;
18
20
  while ((match = rawMethodRegex.exec(content)) !== null) {
19
21
  const jsdocContent = match[1];
20
22
  const operationId = match[2];
21
23
  const responseType = match[3];
24
+ if (!jsdocContent || !operationId || !responseType) continue;
22
25
  const jsdocLines = jsdocContent.split("\n").map((line) => line.replace(/^\s*\*\s?/, "").trim()).filter((line) => line.length > 0 && !line.startsWith("@"));
23
26
  const summary = jsdocLines[jsdocLines.length - 1] || jsdocLines[0] || operationId;
24
27
  const body = methodBodies.get(operationId);
25
28
  if (!body) continue;
26
29
  const pathMatch = body.match(/let\s+urlPath\s*=\s*`([^`]+)`/);
27
- if (!pathMatch) continue;
30
+ if (!pathMatch?.[1]) continue;
28
31
  let path = pathMatch[1];
29
32
  path = path.replace(/\$\{[^}]+\}/g, (match2) => {
30
33
  const paramName = match2.match(/requestParameters(?:\.(\w+)|\["?(\w+)"?\])/)?.[1] || match2.match(/requestParameters(?:\.(\w+)|\["?(\w+)"?\])/)?.[2];
@@ -38,25 +41,31 @@ function parseApiFile(filePath, fileName) {
38
41
  const pathParams = [];
39
42
  const pathParamMatches = path.matchAll(/\{(\w+)\}/g);
40
43
  for (const pm of pathParamMatches) {
41
- pathParams.push({
42
- name: pm[1],
43
- type: "string",
44
- required: true
45
- });
44
+ const paramName = pm[1];
45
+ if (paramName) {
46
+ pathParams.push({
47
+ name: paramName,
48
+ type: "string",
49
+ required: true
50
+ });
51
+ }
46
52
  }
47
53
  const queryParams = [];
48
54
  const queryParamRegex = /if\s*\(requestParameters\[?['"]?(\w+)['"]?\]?\s*!==?\s*(?:undefined|null)\)\s*\{\s*queryParameters\[['"](\w+)['"]\]/g;
49
55
  let qpm;
50
56
  while ((qpm = queryParamRegex.exec(body)) !== null) {
51
- queryParams.push({
52
- name: qpm[2],
53
- type: "string",
54
- required: false
55
- });
57
+ const paramName = qpm[2];
58
+ if (paramName) {
59
+ queryParams.push({
60
+ name: paramName,
61
+ type: "string",
62
+ required: false
63
+ });
64
+ }
56
65
  }
57
66
  let requestBodyType;
58
67
  const bodyMatch = body.match(/body:\s*(\w+)ToJSON\(requestParameters\.(\w+)\)/);
59
- if (bodyMatch) {
68
+ if (bodyMatch?.[1]) {
60
69
  requestBodyType = bodyMatch[1];
61
70
  }
62
71
  endpoints.push({
@@ -79,7 +88,7 @@ function analyzeType(rawType) {
79
88
  let type = rawType;
80
89
  if (isArray) {
81
90
  const arrayTypeMatch = rawType.match(/Array<(.+)>/) || rawType.match(/(.+)\[\]/);
82
- if (arrayTypeMatch) {
91
+ if (arrayTypeMatch?.[1]) {
83
92
  type = arrayTypeMatch[1].trim();
84
93
  }
85
94
  }
@@ -96,11 +105,13 @@ function parseModelFile(filePath, fileName) {
96
105
  if (!hasInterface) {
97
106
  const enumRegex = new RegExp(`export\\s+const\\s+(${modelName})\\s*=\\s*\\{([^}]+)\\}\\s*as\\s*const`);
98
107
  const enumMatch = content.match(enumRegex);
99
- if (enumMatch) {
108
+ if (enumMatch?.[2]) {
100
109
  const enumValues = [];
101
110
  const valueMatches = enumMatch[2].matchAll(/(\w+):\s*['"]([^'"]+)['"]/g);
102
111
  for (const vm of valueMatches) {
103
- enumValues.push(vm[2]);
112
+ if (vm[2]) {
113
+ enumValues.push(vm[2]);
114
+ }
104
115
  }
105
116
  if (enumValues.length > 0) {
106
117
  return {
@@ -114,11 +125,12 @@ function parseModelFile(filePath, fileName) {
114
125
  const fields = [];
115
126
  const jsonKeyMap = /* @__PURE__ */ new Map();
116
127
  const toJsonMatch = content.match(/export\s+function\s+\w+ToJSON[^{]*\{[\s\S]*?return\s*\{([\s\S]*?)\};?\s*\}/);
117
- if (toJsonMatch) {
128
+ if (toJsonMatch?.[1]) {
118
129
  const returnBody = toJsonMatch[1];
119
130
  const fieldMatches = returnBody.matchAll(/['"](\w+)['"]\s*:\s*(?:value\.(\w+)|[^,\n]*?value\.(\w+))/g);
120
131
  for (const fm of fieldMatches) {
121
132
  const jsonKey = fm[1];
133
+ if (!jsonKey) continue;
122
134
  const propertyName = fm[2] || fm[3];
123
135
  if (!propertyName) continue;
124
136
  jsonKeyMap.set(propertyName, jsonKey);
@@ -134,29 +146,31 @@ function parseModelFile(filePath, fileName) {
134
146
  }
135
147
  }
136
148
  const fromJsonMatch = content.match(/export\s+function\s+\w+FromJSONTyped[^{]*\{[\s\S]*?return\s*\{([\s\S]*?)\};?\s*\}/);
137
- if (fromJsonMatch) {
149
+ if (fromJsonMatch?.[1]) {
138
150
  const returnBody = fromJsonMatch[1];
139
151
  const dateFields = /* @__PURE__ */ new Set();
140
152
  const dateMatches = returnBody.matchAll(/['"](\w+)['"]\s*:.*?new\s+Date\(/g);
141
153
  for (const dm of dateMatches) {
142
- dateFields.add(dm[1]);
154
+ if (dm[1]) dateFields.add(dm[1]);
143
155
  }
144
156
  const arrayFields = /* @__PURE__ */ new Set();
145
157
  const arrayMatches = returnBody.matchAll(/['"](\w+)['"]\s*:.*?\(json\[['"](\w+)['"]\]\s*as\s*Array/g);
146
158
  for (const am of arrayMatches) {
147
- arrayFields.add(am[1]);
159
+ if (am[1]) arrayFields.add(am[1]);
148
160
  }
149
161
  const refTypeMap = /* @__PURE__ */ new Map();
150
162
  const refMatches = returnBody.matchAll(/['"](\w+)['"]\s*:.*?(\w+)FromJSON\(json\[/g);
151
163
  for (const rm of refMatches) {
152
- if (!rm[2].includes("Array")) {
164
+ if (rm[1] && rm[2] && !rm[2].includes("Array")) {
153
165
  refTypeMap.set(rm[1], rm[2]);
154
166
  }
155
167
  }
156
168
  const arrayRefMatches = returnBody.matchAll(/['"](\w+)['"]\s*:.*?\.map\((\w+)FromJSON\)/g);
157
169
  for (const arm of arrayRefMatches) {
158
- refTypeMap.set(arm[1], arm[2]);
159
- arrayFields.add(arm[1]);
170
+ if (arm[1] && arm[2]) {
171
+ refTypeMap.set(arm[1], arm[2]);
172
+ arrayFields.add(arm[1]);
173
+ }
160
174
  }
161
175
  for (const field of fields) {
162
176
  if (dateFields.has(field.name)) {
@@ -173,14 +187,16 @@ function parseModelFile(filePath, fileName) {
173
187
  }
174
188
  }
175
189
  const interfaceMatch = content.match(/export\s+interface\s+(\w+)(?:\s+extends\s+[\w,\s]+)?\s*\{([\s\S]*?)\n\}/);
176
- if (interfaceMatch) {
190
+ if (interfaceMatch?.[2]) {
177
191
  const interfaceBody = interfaceMatch[2];
178
192
  const simpleFieldRegex = /^\s+(\w+)(\?)?:\s*([^;]+);/gm;
179
193
  let sfm;
180
194
  while ((sfm = simpleFieldRegex.exec(interfaceBody)) !== null) {
181
195
  const name = sfm[1];
196
+ const rawTypeStr = sfm[3];
197
+ if (!name || !rawTypeStr) continue;
182
198
  const optional = sfm[2] === "?";
183
- const rawType = sfm[3].trim();
199
+ const rawType = rawTypeStr.trim();
184
200
  const { type, isArray, refType } = analyzeType(rawType);
185
201
  const existingField = fields.find((f) => f.name === name);
186
202
  if (existingField) {
@@ -90,6 +90,9 @@ export function analyzeProtoPagination(messageType) {
90
90
  const foundCursorFields = cursorFieldPatterns.filter((p) => fieldNames.includes(p.toLowerCase()));
91
91
  const isPageBased = foundPageFields.length >= 2;
92
92
  const isCursorBased = foundCursorFields.length >= 1;
93
+ if (!isPageBased && !isCursorBased) {
94
+ return null;
95
+ }
93
96
  return {
94
97
  itemsFieldName,
95
98
  itemMessageType,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mock-fried",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Nuxt3 Mock API Module - OpenAPI & Protobuf RPC Mock Server",
5
5
  "repository": {
6
6
  "type": "git",
@@ -59,7 +59,7 @@
59
59
  "test:e2e:openapi-client": "vitest run test/e2e/playground-openapi-client.e2e.test.ts",
60
60
  "test:e2e:proto": "vitest run test/e2e/playground-proto.e2e.test.ts test/e2e/playground-proto-advanced.e2e.test.ts",
61
61
  "test:e2e:proto-advanced": "vitest run test/e2e/playground-proto-advanced.e2e.test.ts",
62
- "test:types": "vue-tsc --noEmit && cd playground-openapi && vue-tsc --noEmit"
62
+ "test:types": "vue-tsc --noEmit"
63
63
  },
64
64
  "dependencies": {
65
65
  "@grpc/grpc-js": "^1.12.0",