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
|
@@ -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,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
|
+
});
|