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,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,24 @@
1
+ import type { OpenApiPathItem } from '../../types.js';
2
+ type __VLS_Props = {
3
+ endpoint: OpenApiPathItem;
4
+ type: 'rest' | 'rpc';
5
+ };
6
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
7
+ execute: (params: {
8
+ path: string;
9
+ method: string;
10
+ pathParams?: Record<string, string>;
11
+ queryParams?: Record<string, string>;
12
+ body?: unknown;
13
+ }) => any;
14
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
15
+ onExecute?: ((params: {
16
+ path: string;
17
+ method: string;
18
+ pathParams?: Record<string, string>;
19
+ queryParams?: Record<string, string>;
20
+ body?: unknown;
21
+ }) => any) | undefined;
22
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
23
+ declare const _default: typeof __VLS_export;
24
+ export default _default;
@@ -0,0 +1,173 @@
1
+ <template>
2
+ <div
3
+ class="endpoint-card"
4
+ :class="{ expanded }"
5
+ >
6
+ <div
7
+ class="card-header"
8
+ @click="expanded = !expanded"
9
+ >
10
+ <span
11
+ class="method"
12
+ :class="methodClass"
13
+ >
14
+ {{ endpoint.method }}
15
+ </span>
16
+ <span class="path">{{ endpoint.path }}</span>
17
+ <span
18
+ v-if="endpoint.summary"
19
+ class="summary"
20
+ >
21
+ {{ endpoint.summary }}
22
+ </span>
23
+ <span class="expand-icon">{{ expanded ? "\u25BC" : "\u25B6" }}</span>
24
+ </div>
25
+
26
+ <div
27
+ v-if="expanded"
28
+ class="card-body"
29
+ >
30
+ <p
31
+ v-if="endpoint.description"
32
+ class="description"
33
+ >
34
+ {{ endpoint.description }}
35
+ </p>
36
+
37
+ <!-- Path Parameters -->
38
+ <div
39
+ v-if="pathParams.length > 0"
40
+ class="params-section"
41
+ >
42
+ <h4>Path Parameters</h4>
43
+ <div
44
+ v-for="param in pathParams"
45
+ :key="param.name"
46
+ class="param-row"
47
+ >
48
+ <label :for="`path-${param.name}`">
49
+ {{ param.name }}
50
+ <span
51
+ v-if="param.required"
52
+ class="required"
53
+ >*</span>
54
+ </label>
55
+ <input
56
+ :id="`path-${param.name}`"
57
+ v-model="pathParamValues[param.name]"
58
+ type="text"
59
+ :placeholder="param.description || param.name"
60
+ >
61
+ </div>
62
+ </div>
63
+
64
+ <!-- Query Parameters -->
65
+ <div
66
+ v-if="queryParams.length > 0"
67
+ class="params-section"
68
+ >
69
+ <h4>Query Parameters</h4>
70
+ <div
71
+ v-for="param in queryParams"
72
+ :key="param.name"
73
+ class="param-row"
74
+ >
75
+ <label :for="`query-${param.name}`">
76
+ {{ param.name }}
77
+ <span
78
+ v-if="param.required"
79
+ class="required"
80
+ >*</span>
81
+ </label>
82
+ <input
83
+ :id="`query-${param.name}`"
84
+ v-model="queryParamValues[param.name]"
85
+ type="text"
86
+ :placeholder="param.description || param.name"
87
+ >
88
+ </div>
89
+ </div>
90
+
91
+ <!-- Request Body -->
92
+ <div
93
+ v-if="hasRequestBody"
94
+ class="params-section"
95
+ >
96
+ <h4>Request Body</h4>
97
+ <textarea
98
+ v-model="bodyValue"
99
+ rows="5"
100
+ placeholder="{ }"
101
+ />
102
+ </div>
103
+
104
+ <button
105
+ class="execute-btn"
106
+ @click="execute"
107
+ >
108
+ Execute
109
+ </button>
110
+ </div>
111
+ </div>
112
+ </template>
113
+
114
+ <script setup>
115
+ const props = defineProps({
116
+ endpoint: { type: Object, required: true },
117
+ type: { type: String, required: true }
118
+ });
119
+ const emit = defineEmits(["execute"]);
120
+ const expanded = ref(false);
121
+ const pathParamValues = ref({});
122
+ const queryParamValues = ref({});
123
+ const bodyValue = ref("");
124
+ const methodClass = computed(() => {
125
+ const method = props.endpoint.method.toLowerCase();
126
+ return {
127
+ get: method === "get",
128
+ post: method === "post",
129
+ put: method === "put",
130
+ delete: method === "delete",
131
+ patch: method === "patch"
132
+ };
133
+ });
134
+ const pathParams = computed(() => {
135
+ return (props.endpoint.parameters || []).filter((p) => p.in === "path");
136
+ });
137
+ const queryParams = computed(() => {
138
+ return (props.endpoint.parameters || []).filter((p) => p.in === "query");
139
+ });
140
+ const hasRequestBody = computed(() => {
141
+ return ["POST", "PUT", "PATCH"].includes(props.endpoint.method) && props.endpoint.requestBody;
142
+ });
143
+ function execute() {
144
+ const params = {
145
+ path: props.endpoint.path,
146
+ method: props.endpoint.method
147
+ };
148
+ if (Object.keys(pathParamValues.value).length > 0) {
149
+ params.pathParams = { ...pathParamValues.value };
150
+ }
151
+ if (Object.keys(queryParamValues.value).length > 0) {
152
+ const filtered = {};
153
+ for (const [k, v] of Object.entries(queryParamValues.value)) {
154
+ if (v) filtered[k] = v;
155
+ }
156
+ if (Object.keys(filtered).length > 0) {
157
+ params.queryParams = filtered;
158
+ }
159
+ }
160
+ if (bodyValue.value) {
161
+ try {
162
+ params.body = JSON.parse(bodyValue.value);
163
+ } catch {
164
+ params.body = bodyValue.value;
165
+ }
166
+ }
167
+ emit("execute", params);
168
+ }
169
+ </script>
170
+
171
+ <style scoped>
172
+ .endpoint-card{background:#fff;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden}.card-header{align-items:center;background:#fafafa;cursor:pointer;display:flex;gap:.75rem;padding:.75rem 1rem;transition:background .2s}.card-header:hover{background:#f0f0f0}.method{border-radius:4px;font-size:.75rem;font-weight:700;min-width:60px;padding:.25rem .5rem;text-align:center}.method.get{background:#61affe;color:#fff}.method.post{background:#49cc90;color:#fff}.method.put{background:#fca130;color:#fff}.method.delete{background:#f93e3e;color:#fff}.method.patch{background:#50e3c2;color:#1a1a1a}.path{color:#333;font-family:Fira Code,Monaco,monospace;font-size:.9rem}.summary{color:#666;flex:1;font-size:.85rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.expand-icon{color:#999;font-size:.75rem}.card-body{border-top:1px solid #e0e0e0;padding:1rem}.description{color:#666;font-size:.9rem;margin:0 0 1rem}.params-section{margin-bottom:1rem}.params-section h4{color:#444;font-size:.85rem;margin:0 0 .5rem}.param-row{align-items:center;display:flex;gap:.5rem;margin-bottom:.5rem}.param-row label{color:#555;font-size:.85rem;min-width:120px}.param-row .required{color:#f93e3e}.param-row input{border:1px solid #ddd;border-radius:4px;flex:1;font-size:.9rem;padding:.5rem}.param-row input:focus{border-color:#61affe;outline:none}textarea{border:1px solid #ddd;border-radius:4px;font-family:Fira Code,Monaco,monospace;font-size:.85rem;padding:.5rem;resize:vertical;width:100%}textarea:focus{border-color:#61affe;outline:none}.execute-btn{background:#4990e2;border:none;border-radius:4px;color:#fff;cursor:pointer;font-size:.9rem;padding:.5rem 1.5rem;transition:background .2s}.execute-btn:hover{background:#357abd}
173
+ </style>
@@ -0,0 +1,24 @@
1
+ import type { OpenApiPathItem } from '../../types.js';
2
+ type __VLS_Props = {
3
+ endpoint: OpenApiPathItem;
4
+ type: 'rest' | 'rpc';
5
+ };
6
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
7
+ execute: (params: {
8
+ path: string;
9
+ method: string;
10
+ pathParams?: Record<string, string>;
11
+ queryParams?: Record<string, string>;
12
+ body?: unknown;
13
+ }) => any;
14
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
15
+ onExecute?: ((params: {
16
+ path: string;
17
+ method: string;
18
+ pathParams?: Record<string, string>;
19
+ queryParams?: Record<string, string>;
20
+ body?: unknown;
21
+ }) => any) | undefined;
22
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
23
+ declare const _default: typeof __VLS_export;
24
+ export default _default;
@@ -0,0 +1,16 @@
1
+ type __VLS_Props = {
2
+ response: {
3
+ success: boolean;
4
+ data: unknown;
5
+ time: number;
6
+ type: 'rest' | 'rpc';
7
+ info: string;
8
+ };
9
+ };
10
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
+ close: () => any;
12
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
13
+ onClose?: (() => any) | undefined;
14
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
15
+ declare const _default: typeof __VLS_export;
16
+ export default _default;
@@ -0,0 +1,78 @@
1
+ <template>
2
+ <div
3
+ class="response-overlay"
4
+ @click.self="$emit('close')"
5
+ >
6
+ <div class="response-modal">
7
+ <header class="modal-header">
8
+ <div class="header-info">
9
+ <span
10
+ class="status"
11
+ :class="{ success: response.success, error: !response.success }"
12
+ >
13
+ {{ response.success ? "SUCCESS" : "ERROR" }}
14
+ </span>
15
+ <span class="info">{{ response.info }}</span>
16
+ <span class="time">{{ response.time }}ms</span>
17
+ </div>
18
+ <button
19
+ class="close-btn"
20
+ @click="$emit('close')"
21
+ >
22
+ ×
23
+ </button>
24
+ </header>
25
+
26
+ <div class="modal-body">
27
+ <div class="response-actions">
28
+ <button
29
+ class="action-btn"
30
+ @click="copyToClipboard"
31
+ >
32
+ {{ copied ? "Copied!" : "Copy" }}
33
+ </button>
34
+ <button
35
+ class="action-btn"
36
+ @click="toggleFormat"
37
+ >
38
+ {{ formatted ? "Raw" : "Format" }}
39
+ </button>
40
+ </div>
41
+
42
+ <pre class="response-content">{{ displayContent }}</pre>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ </template>
47
+
48
+ <script setup>
49
+ const props = defineProps({
50
+ response: { type: Object, required: true }
51
+ });
52
+ defineEmits(["close"]);
53
+ const formatted = ref(true);
54
+ const copied = ref(false);
55
+ const displayContent = computed(() => {
56
+ if (formatted.value) {
57
+ return JSON.stringify(props.response.data, null, 2);
58
+ }
59
+ return JSON.stringify(props.response.data);
60
+ });
61
+ function toggleFormat() {
62
+ formatted.value = !formatted.value;
63
+ }
64
+ async function copyToClipboard() {
65
+ try {
66
+ await navigator.clipboard.writeText(displayContent.value);
67
+ copied.value = true;
68
+ setTimeout(() => {
69
+ copied.value = false;
70
+ }, 2e3);
71
+ } catch {
72
+ }
73
+ }
74
+ </script>
75
+
76
+ <style scoped>
77
+ .response-overlay{align-items:center;background:rgba(0,0,0,.5);bottom:0;display:flex;justify-content:center;left:0;position:fixed;right:0;top:0;z-index:1000}.response-modal{background:#fff;border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,.3);display:flex;flex-direction:column;max-height:80vh;max-width:800px;overflow:hidden;width:90%}.modal-header{background:#1a1a1a;color:#fff;justify-content:space-between;padding:1rem 1.5rem}.header-info,.modal-header{align-items:center;display:flex}.header-info{gap:1rem}.status{border-radius:4px;font-size:.75rem;font-weight:700;padding:.25rem .75rem}.status.success{background:#49cc90;color:#fff}.status.error{background:#f93e3e;color:#fff}.info{font-family:Fira Code,Monaco,monospace;font-size:.9rem}.time{color:#888;font-size:.85rem}.close-btn{background:none;border:none;color:#fff;cursor:pointer;font-size:1.5rem;line-height:1;opacity:.7;padding:0;transition:opacity .2s}.close-btn:hover{opacity:1}.modal-body{flex:1;overflow:auto;padding:1rem 1.5rem}.response-actions{display:flex;gap:.5rem;margin-bottom:1rem}.action-btn{background:#f0f0f0;border:1px solid #ddd;border-radius:4px;cursor:pointer;font-size:.8rem;padding:.4rem .8rem;transition:background .2s}.action-btn:hover{background:#e0e0e0}.response-content{background:#1a1a1a;border-radius:8px;color:#00dc82;font-family:Fira Code,Monaco,monospace;font-size:.85rem;line-height:1.5;margin:0;overflow-x:auto;padding:1rem;white-space:pre-wrap;word-break:break-all}
78
+ </style>
@@ -0,0 +1,16 @@
1
+ type __VLS_Props = {
2
+ response: {
3
+ success: boolean;
4
+ data: unknown;
5
+ time: number;
6
+ type: 'rest' | 'rpc';
7
+ info: string;
8
+ };
9
+ };
10
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
+ close: () => any;
12
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
13
+ onClose?: (() => any) | undefined;
14
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
15
+ declare const _default: typeof __VLS_export;
16
+ export default _default;
@@ -0,0 +1,20 @@
1
+ import type { RpcMethodSchema } from '../../types.js';
2
+ type __VLS_Props = {
3
+ service: string;
4
+ method: RpcMethodSchema;
5
+ };
6
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
7
+ execute: (params: {
8
+ service: string;
9
+ method: string;
10
+ body?: Record<string, unknown>;
11
+ }) => any;
12
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
13
+ onExecute?: ((params: {
14
+ service: string;
15
+ method: string;
16
+ body?: Record<string, unknown>;
17
+ }) => any) | undefined;
18
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
19
+ declare const _default: typeof __VLS_export;
20
+ export default _default;
@@ -0,0 +1,129 @@
1
+ <template>
2
+ <div
3
+ class="rpc-card"
4
+ :class="{ expanded }"
5
+ >
6
+ <div
7
+ class="card-header"
8
+ @click="expanded = !expanded"
9
+ >
10
+ <span class="method-badge">RPC</span>
11
+ <span class="method-name">{{ method.name }}</span>
12
+ <span class="types">
13
+ {{ method.requestType }} → {{ method.responseType }}
14
+ </span>
15
+ <span class="expand-icon">{{ expanded ? "\u25BC" : "\u25B6" }}</span>
16
+ </div>
17
+
18
+ <div
19
+ v-if="expanded"
20
+ class="card-body"
21
+ >
22
+ <!-- Request Fields -->
23
+ <div
24
+ v-if="method.requestFields && method.requestFields.length > 0"
25
+ class="fields-section"
26
+ >
27
+ <h4>Request Fields ({{ method.requestType }})</h4>
28
+ <div
29
+ v-for="field in method.requestFields"
30
+ :key="field.name"
31
+ class="field-row"
32
+ >
33
+ <label :for="`field-${field.name}`">
34
+ {{ field.name }}
35
+ <span class="field-type">{{ formatFieldType(field) }}</span>
36
+ </label>
37
+ <input
38
+ :id="`field-${field.name}`"
39
+ v-model="fieldValues[field.name]"
40
+ type="text"
41
+ :placeholder="getPlaceholder(field)"
42
+ >
43
+ </div>
44
+ </div>
45
+
46
+ <!-- Raw JSON Input -->
47
+ <div class="json-section">
48
+ <h4>Request JSON</h4>
49
+ <textarea
50
+ v-model="jsonValue"
51
+ rows="4"
52
+ placeholder="{ }"
53
+ />
54
+ </div>
55
+
56
+ <button
57
+ class="execute-btn"
58
+ @click="execute"
59
+ >
60
+ Execute
61
+ </button>
62
+ </div>
63
+ </div>
64
+ </template>
65
+
66
+ <script setup>
67
+ const props = defineProps({
68
+ service: { type: String, required: true },
69
+ method: { type: Object, required: true }
70
+ });
71
+ const emit = defineEmits(["execute"]);
72
+ const expanded = ref(false);
73
+ const fieldValues = ref({});
74
+ const jsonValue = ref("");
75
+ watch(fieldValues, (values) => {
76
+ const obj = {};
77
+ for (const [key, value] of Object.entries(values)) {
78
+ if (value) {
79
+ const num = Number(value);
80
+ obj[key] = Number.isNaN(num) ? value : num;
81
+ }
82
+ }
83
+ if (Object.keys(obj).length > 0) {
84
+ jsonValue.value = JSON.stringify(obj, null, 2);
85
+ }
86
+ }, { deep: true });
87
+ function formatFieldType(field) {
88
+ let type = field.type;
89
+ if (field.repeated) type = `${type}[]`;
90
+ if (field.optional) type = `${type}?`;
91
+ return type;
92
+ }
93
+ function getPlaceholder(field) {
94
+ const typeHints = {
95
+ string: '"text"',
96
+ int32: "123",
97
+ int64: "123",
98
+ float: "1.23",
99
+ double: "1.23",
100
+ bool: "true/false"
101
+ };
102
+ return typeHints[field.type] || field.type;
103
+ }
104
+ function execute() {
105
+ let body;
106
+ if (jsonValue.value) {
107
+ try {
108
+ body = JSON.parse(jsonValue.value);
109
+ } catch {
110
+ body = {};
111
+ for (const [key, value] of Object.entries(fieldValues.value)) {
112
+ if (value) {
113
+ const num = Number(value);
114
+ body[key] = Number.isNaN(num) ? value : num;
115
+ }
116
+ }
117
+ }
118
+ }
119
+ emit("execute", {
120
+ service: props.service,
121
+ method: props.method.name,
122
+ body
123
+ });
124
+ }
125
+ </script>
126
+
127
+ <style scoped>
128
+ .rpc-card{background:#fff;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden}.card-header{align-items:center;background:#fafafa;cursor:pointer;display:flex;gap:.75rem;padding:.75rem 1rem;transition:background .2s}.card-header:hover{background:#f0f0f0}.method-badge{background:#7c3aed;border-radius:4px;color:#fff;font-size:.7rem;font-weight:700;padding:.25rem .5rem}.method-name{color:#333;font-size:.9rem;font-weight:600}.method-name,.types{font-family:Fira Code,Monaco,monospace}.types{color:#888;flex:1;font-size:.8rem}.expand-icon{color:#999;font-size:.75rem}.card-body{border-top:1px solid #e0e0e0;padding:1rem}.fields-section,.json-section{margin-bottom:1rem}.fields-section h4,.json-section h4{color:#444;font-size:.85rem;margin:0 0 .5rem}.field-row{align-items:center;display:flex;gap:.5rem;margin-bottom:.5rem}.field-row label{color:#555;font-size:.85rem;min-width:140px}.field-type{color:#888;font-family:Fira Code,Monaco,monospace;font-size:.75rem}.field-row input{border:1px solid #ddd;border-radius:4px;flex:1;font-size:.9rem;padding:.5rem}.field-row input:focus{border-color:#7c3aed;outline:none}textarea{border:1px solid #ddd;border-radius:4px;font-family:Fira Code,Monaco,monospace;font-size:.85rem;padding:.5rem;resize:vertical;width:100%}textarea:focus{border-color:#7c3aed;outline:none}.execute-btn{background:#7c3aed;border:none;border-radius:4px;color:#fff;cursor:pointer;font-size:.9rem;padding:.5rem 1.5rem;transition:background .2s}.execute-btn:hover{background:#6d28d9}
129
+ </style>
@@ -0,0 +1,20 @@
1
+ import type { RpcMethodSchema } from '../../types.js';
2
+ type __VLS_Props = {
3
+ service: string;
4
+ method: RpcMethodSchema;
5
+ };
6
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
7
+ execute: (params: {
8
+ service: string;
9
+ method: string;
10
+ body?: Record<string, unknown>;
11
+ }) => any;
12
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
13
+ onExecute?: ((params: {
14
+ service: string;
15
+ method: string;
16
+ body?: Record<string, unknown>;
17
+ }) => any) | undefined;
18
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
19
+ declare const _default: typeof __VLS_export;
20
+ export default _default;
@@ -0,0 +1 @@
1
+ export { useApi } from './useApi.js';
@@ -0,0 +1 @@
1
+ export { useApi } from "./useApi.js";
@@ -0,0 +1,19 @@
1
+ import type { ApiClient } from '../../types.js';
2
+ /**
3
+ * Mock API 클라이언트 composable
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * const api = useApi()
8
+ *
9
+ * // REST 호출
10
+ * const users = await api.rest('/users')
11
+ *
12
+ * // RPC 호출
13
+ * const user = await api.rpc('UserService', 'GetUser', { id: 1 })
14
+ *
15
+ * // 동적 서비스 접근
16
+ * const user = await api.UserService.GetUser({ id: 1 })
17
+ * ```
18
+ */
19
+ export declare function useApi(): ApiClient;
@@ -0,0 +1,5 @@
1
+ import { useNuxtApp } from "#app";
2
+ export function useApi() {
3
+ const nuxtApp = useNuxtApp();
4
+ return nuxtApp.$api;
5
+ }
@@ -0,0 +1,7 @@
1
+ import type { ApiClient } from '../types.js';
2
+ declare const _default: import("#app").Plugin<{
3
+ api: ApiClient;
4
+ }> & import("#app").ObjectPlugin<{
5
+ api: ApiClient;
6
+ }>;
7
+ export default _default;
@@ -0,0 +1,75 @@
1
+ import { defineNuxtPlugin, useRuntimeConfig } from "#app";
2
+ export default defineNuxtPlugin(() => {
3
+ const config = useRuntimeConfig();
4
+ const mockConfig = config.public?.mock;
5
+ if (!mockConfig?.enable) {
6
+ return {
7
+ provide: {
8
+ api: {}
9
+ }
10
+ };
11
+ }
12
+ const prefix = mockConfig.prefix || "/mock";
13
+ const rest = async (path, options) => {
14
+ const url = `${prefix}${path.startsWith("/") ? path : "/" + path}`;
15
+ const fetchOptions = {
16
+ method: options?.method || "GET"
17
+ };
18
+ if (options?.body && fetchOptions.method !== "GET") {
19
+ fetchOptions.body = JSON.stringify(options.body);
20
+ }
21
+ if (options?.headers) {
22
+ fetchOptions.headers = options.headers;
23
+ }
24
+ let finalUrl = url;
25
+ if (options?.params) {
26
+ const searchParams = new URLSearchParams();
27
+ for (const [key, value] of Object.entries(options.params)) {
28
+ searchParams.append(key, String(value));
29
+ }
30
+ finalUrl = `${url}?${searchParams.toString()}`;
31
+ }
32
+ const response = await $fetch(finalUrl, fetchOptions);
33
+ return response;
34
+ };
35
+ const rpc = async (service, method, params) => {
36
+ const url = `${prefix}/rpc/${service}/${method}`;
37
+ const response = await $fetch(url, {
38
+ method: "POST",
39
+ body: params
40
+ });
41
+ return response.data;
42
+ };
43
+ let cachedSchema = null;
44
+ const getSchema = async () => {
45
+ if (cachedSchema) {
46
+ return cachedSchema;
47
+ }
48
+ const url = `${prefix}/__schema`;
49
+ const schema = await $fetch(url);
50
+ cachedSchema = schema;
51
+ return schema;
52
+ };
53
+ const baseApi = {
54
+ rest,
55
+ rpc,
56
+ getSchema
57
+ };
58
+ const apiProxy = new Proxy(baseApi, {
59
+ get(target, prop) {
60
+ if (prop === "rest" || prop === "rpc" || prop === "getSchema") {
61
+ return target[prop];
62
+ }
63
+ return new Proxy({}, {
64
+ get(_, methodName) {
65
+ return (params) => rpc(prop, methodName, params);
66
+ }
67
+ });
68
+ }
69
+ });
70
+ return {
71
+ provide: {
72
+ api: apiProxy
73
+ }
74
+ };
75
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
2
+ export default _default;