@zachacious/protoc-gen-connect-vue 1.0.5 → 1.0.7
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 +107 -142
- package/dist/generator.js +21 -3
- package/package.json +1 -1
- package/templates/api.ts.mustache +41 -9
- package/templates/client.ts.mustache +26 -9
- package/templates/rpc.ts.mustache +57 -48
package/README.md
CHANGED
|
@@ -1,210 +1,175 @@
|
|
|
1
|
-
#
|
|
1
|
+
# **protoc-gen-connect-vue**
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
protoc-gen-connect-vue is a code generation plugin for [ConnectRPC](https://connectrpc.com/) tailored specifically for Vue.js. It generates a type-safe SDK that combines the power of ConnectRPC with the reactivity of the Vue Composition API and the caching capabilities of TanStack Query (Vue).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Unlike the official @connectrpc/connect-query which is architected for React, this plugin is built from the ground up for Vue developers, providing first-class support for ref, computed, and manual resource invalidation.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
> Note: this would made for personal and internal projects. If you find it useful, let me know.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## **Features**
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
- **Reactive Client:** Automatically re-initializes the transport when your base URL or auth tokens change.
|
|
12
|
+
- **TanStack Query Integration:** Generates useQuery, useMutation, and useInfiniteQuery hooks for every RPC.
|
|
13
|
+
- **Manual Wrappers:** Provides standard async wrappers for actions that don't fit the "query" pattern.
|
|
14
|
+
- **Message Initializers:** A createEmpty utility to generate default objects for any Protobuf message type.
|
|
15
|
+
- **Query Key Factory:** Centralized query keys to simplify manual cache invalidation.
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
## **🚀 Installation**
|
|
19
|
-
|
|
20
|
-
### **1. Plugin Installation**
|
|
21
|
-
|
|
22
|
-
You can install the plugin globally or as a dev dependency.
|
|
17
|
+
## **Installation**
|
|
23
18
|
|
|
24
|
-
```
|
|
25
|
-
#
|
|
26
|
-
npm install --save-dev @zachacious/protoc-gen-connect-vue
|
|
19
|
+
```bash
|
|
20
|
+
# install plugin either globally or locally
|
|
27
21
|
|
|
28
|
-
# For use across non-Node projects (Go/Rust/etc)
|
|
29
22
|
npm install -g @zachacious/protoc-gen-connect-vue
|
|
30
23
|
```
|
|
31
24
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
The generated SDK requires these specific packages to be installed in your Vue project:
|
|
25
|
+
```Bash
|
|
26
|
+
# install dependencies
|
|
35
27
|
|
|
36
|
-
|
|
37
|
-
npm install @connectrpc/connect @connectrpc/connect-web @connectrpc/connect-query @tanstack/vue-query @bufbuild/protobuf
|
|
28
|
+
npm install protoc-gen-connect-vue @tanstack/vue-query @connectrpc/connect @connectrpc/connect-web @bufbuild/protobuf
|
|
38
29
|
```
|
|
39
30
|
|
|
40
|
-
|
|
31
|
+
## **Generation**
|
|
41
32
|
|
|
42
|
-
|
|
33
|
+
Add the plugin to your buf.gen.yaml:
|
|
43
34
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
The plugin **must** run after protoc-gen-es and protoc-gen-connect-query.
|
|
47
|
-
|
|
48
|
-
```yaml
|
|
49
|
-
version: v2
|
|
50
|
-
managed:
|
|
51
|
-
enabled: true
|
|
35
|
+
```YAML
|
|
52
36
|
plugins:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
-
|
|
63
|
-
|
|
37
|
+
# ...
|
|
38
|
+
- remote: buf.build/bufbuild/es
|
|
39
|
+
out: web/src/api/gen
|
|
40
|
+
opt:
|
|
41
|
+
- target=ts
|
|
42
|
+
|
|
43
|
+
# Make sure this plugin goes after protoc-gen-es
|
|
44
|
+
# It should probably go last
|
|
45
|
+
|
|
46
|
+
# npm install -g @zachacious/protoc-gen-connect-vue
|
|
47
|
+
# https://www.npmjs.com/package/@zachacious/protoc-gen-connect-vue
|
|
48
|
+
- local: protoc-gen-connect-vue
|
|
49
|
+
out: web/src/api
|
|
50
|
+
opt: target=ts
|
|
64
51
|
```
|
|
65
52
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
**🛠 Integration & Setup**
|
|
53
|
+
## **Setup**
|
|
69
54
|
|
|
70
|
-
### **1.
|
|
55
|
+
### **1. Configure the Provider**
|
|
71
56
|
|
|
72
|
-
|
|
57
|
+
In your main.ts, provide the QueryClient to your Vue application.
|
|
73
58
|
|
|
74
59
|
```TypeScript
|
|
75
60
|
|
|
76
61
|
import { createApp } from 'vue';
|
|
77
|
-
import { VueQueryPlugin } from '@tanstack/vue-query';
|
|
78
|
-
import {
|
|
79
|
-
import
|
|
62
|
+
import { QueryClient, VueQueryPlugin } from '@tanstack/vue-query';
|
|
63
|
+
import { globalQueryConfig } from './api/generated/client';
|
|
64
|
+
import App from './App.vue';
|
|
80
65
|
|
|
81
66
|
const app = createApp(App);
|
|
67
|
+
const queryClient = new QueryClient(globalQueryConfig);
|
|
82
68
|
|
|
83
|
-
|
|
84
|
-
app.
|
|
85
|
-
|
|
86
|
-
// 2. Configure Endpoint
|
|
87
|
-
setBaseUrl(import.meta.env.VITE_API_URL);
|
|
69
|
+
app.use(VueQueryPlugin, { queryClient });
|
|
70
|
+
app.mount('#app');
|
|
71
|
+
```
|
|
88
72
|
|
|
89
|
-
|
|
90
|
-
const auth = useAuthStore();
|
|
91
|
-
setAuthResolver(async () => {
|
|
92
|
-
return auth.token; // Header 'x-api-key' added if exists
|
|
93
|
-
});
|
|
73
|
+
### **2. Runtime Configuration**
|
|
94
74
|
|
|
95
|
-
|
|
96
|
-
setSDKErrorCallback((err, url) => {
|
|
97
|
-
if (err.code === 16) { // Unauthenticated
|
|
98
|
-
auth.logout();
|
|
99
|
-
window.location.href = '/login';
|
|
100
|
-
}
|
|
101
|
-
});
|
|
75
|
+
Set up your authentication and base URL, typically in an identity store or main entry point.
|
|
102
76
|
|
|
103
|
-
|
|
104
|
-
```
|
|
77
|
+
```TypeScript
|
|
105
78
|
|
|
106
|
-
|
|
79
|
+
import { setBaseUrl, setAuthResolver, setSDKErrorCallback } from '@/api/generated';
|
|
107
80
|
|
|
108
|
-
|
|
81
|
+
setBaseUrl('https://api.example.com');
|
|
109
82
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
83
|
+
// Supports async token resolution
|
|
84
|
+
setAuthResolver(async () => {
|
|
85
|
+
const token = localStorage.getItem('token');
|
|
86
|
+
return token ? token : null;
|
|
87
|
+
});
|
|
115
88
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
</TransportProvider>
|
|
120
|
-
</template>
|
|
89
|
+
setSDKErrorCallback((err, url) => {
|
|
90
|
+
console.error(`API Error at ${url}: ${err.message}`);
|
|
91
|
+
});
|
|
121
92
|
```
|
|
122
93
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
## **📖 Usage Examples**
|
|
94
|
+
## **Usage**
|
|
126
95
|
|
|
127
|
-
### **
|
|
96
|
+
### **Using Hooks and Manual Wrappers**
|
|
128
97
|
|
|
129
|
-
|
|
98
|
+
The generated SDK provides both automated hooks and manual async functions.
|
|
130
99
|
|
|
131
100
|
```html
|
|
132
101
|
<script setup lang="ts">
|
|
133
|
-
import {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const {
|
|
102
|
+
import { ref } from "vue";
|
|
103
|
+
import { useApi } from "@/api/generated";
|
|
104
|
+
|
|
105
|
+
const { getUser, useGetUser } = useApi();
|
|
106
|
+
|
|
107
|
+
// 1. Reactive Hook (Auto-fetches)
|
|
108
|
+
const userId = ref("123");
|
|
109
|
+
const { data, isLoading, error } = useGetUser(userId);
|
|
110
|
+
|
|
111
|
+
// 2. Manual Action
|
|
112
|
+
const handleUpdate = async () => {
|
|
113
|
+
const { data, error } = await updateUser({ id: "123", name: "New Name" });
|
|
114
|
+
if (!error) {
|
|
115
|
+
// Note: The SDK automatically invalidates related queries on success
|
|
116
|
+
console.log("Update successful");
|
|
117
|
+
}
|
|
118
|
+
};
|
|
137
119
|
</script>
|
|
138
120
|
|
|
139
121
|
<template>
|
|
140
122
|
<div v-if="isLoading">Loading...</div>
|
|
141
|
-
<div v-else>{{
|
|
123
|
+
<div v-else-if="error">{{ error }}</div>
|
|
124
|
+
<div v-else>
|
|
125
|
+
<h1>{{ data?.name }}</h1>
|
|
126
|
+
<button @click="handleUpdate">Update Profile</button>
|
|
127
|
+
</div>
|
|
142
128
|
</template>
|
|
143
129
|
```
|
|
144
130
|
|
|
145
|
-
### **
|
|
146
|
-
|
|
147
|
-
The SDK uses resource-name stripping (e.g., UpdateTicket -> Ticket) to invalidate active lists automatically.
|
|
131
|
+
### **Initializing New Data (createEmpty)**
|
|
148
132
|
|
|
149
|
-
|
|
150
|
-
<script setup lang="ts">
|
|
151
|
-
const { mutate, isPending } = api.useUpdateTicket({
|
|
152
|
-
onSuccess: () => console.log("List refreshed by SDK!"),
|
|
153
|
-
});
|
|
133
|
+
Protobuf messages often require specific default values (e.g., empty strings instead of undefined). The createEmpty utility ensures your local state matches the expected Protobuf structure.
|
|
154
134
|
|
|
155
|
-
|
|
156
|
-
</script>
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### **Infinite Scrolling (Pagination)**
|
|
135
|
+
```TypeScript
|
|
160
136
|
|
|
161
|
-
|
|
137
|
+
import { useApi } from '@/api/generated';
|
|
162
138
|
|
|
163
|
-
|
|
164
|
-
<script setup lang="ts">
|
|
165
|
-
const { data, fetchNextPage, hasNextPage } = api.useListTickets({
|
|
166
|
-
filter: "open",
|
|
167
|
-
});
|
|
168
|
-
</script>
|
|
139
|
+
const { createEmpty } = useApi();
|
|
169
140
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
</div>
|
|
174
|
-
<button v-if="hasNextPage" @click="fetchNextPage">Load More</button>
|
|
175
|
-
</template>
|
|
141
|
+
// Create an empty Customer object with all Protobuf defaults
|
|
142
|
+
// Useful when you need to create and empty instance instead of using a nullable object
|
|
143
|
+
const newCustomer = ref(createEmpty.Customer({ name: "Initial Name" }));
|
|
176
144
|
```
|
|
177
145
|
|
|
178
|
-
|
|
146
|
+
### **Manual Cache Invalidation (queryKeys)**
|
|
179
147
|
|
|
180
|
-
|
|
148
|
+
If you need to manually refresh or invalidate a specific query, use the queryKeys factory to ensure the key matches the one used by the generated hooks.
|
|
181
149
|
|
|
182
|
-
|
|
150
|
+
```TypeScript
|
|
183
151
|
|
|
184
|
-
|
|
152
|
+
import { useQueryClient } from '@tanstack/vue-query';
|
|
153
|
+
import { queryKeys } from '@/api/generated';
|
|
185
154
|
|
|
186
|
-
|
|
187
|
-
<script setup lang="ts">
|
|
188
|
-
const { isGlobalLoading } = useApi();
|
|
189
|
-
</script>
|
|
155
|
+
const queryClient = useQueryClient();
|
|
190
156
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
157
|
+
const refreshUser = (id: string) => {
|
|
158
|
+
queryClient.invalidateQueries({
|
|
159
|
+
queryKey: queryKeys.getUser(id)
|
|
160
|
+
});
|
|
161
|
+
};
|
|
194
162
|
```
|
|
195
163
|
|
|
196
|
-
|
|
164
|
+
## **Global Loading State**
|
|
197
165
|
|
|
198
|
-
|
|
166
|
+
The SDK exports a reactive isGlobalLoading computed property that tracks if any RPC (query or mutation) is currently in flight.
|
|
199
167
|
|
|
200
|
-
|
|
201
|
-
| :------------------- | :--------------------------------------------------------------- |
|
|
202
|
-
| @wrapper:auth | Marks the endpoint as expecting authentication in documentation. |
|
|
203
|
-
| @sdk:signature=(...) | Overrides the generated TypeScript function signature. |
|
|
204
|
-
| @sdk:data=res.item | Overrides the default data extractor for async wrappers. |
|
|
168
|
+
```TypeScript
|
|
205
169
|
|
|
206
|
-
|
|
170
|
+
const { isGlobalLoading } = useApi();
|
|
171
|
+
```
|
|
207
172
|
|
|
208
|
-
|
|
173
|
+
---
|
|
209
174
|
|
|
210
|
-
MIT
|
|
175
|
+
License MIT
|
package/dist/generator.js
CHANGED
|
@@ -336845,14 +336845,28 @@ function processType(typeDesc, serviceFile, wktImports, localImports, externalIm
|
|
|
336845
336845
|
externalImports.get(importPath).add(baseName);
|
|
336846
336846
|
return baseName;
|
|
336847
336847
|
}
|
|
336848
|
+
function collectAllMessages(message, serviceFile, wktImports, localImports, externalImports, seen = new Set) {
|
|
336849
|
+
if (seen.has(message.typeName))
|
|
336850
|
+
return;
|
|
336851
|
+
seen.add(message.typeName);
|
|
336852
|
+
processType(message, serviceFile, wktImports, localImports, externalImports);
|
|
336853
|
+
for (const field of message.fields) {
|
|
336854
|
+
if (field.fieldKind === "message") {
|
|
336855
|
+
collectAllMessages(field.message, serviceFile, wktImports, localImports, externalImports, seen);
|
|
336856
|
+
}
|
|
336857
|
+
}
|
|
336858
|
+
}
|
|
336848
336859
|
function processService(service, protoPbFile, connectQueryFile) {
|
|
336849
336860
|
const rpcs = [];
|
|
336850
336861
|
const wktImports = new Set;
|
|
336851
336862
|
const localImports = new Set;
|
|
336852
336863
|
const externalImports = new Map;
|
|
336864
|
+
const allSeenMessages = new Set;
|
|
336853
336865
|
for (const method of service.methods) {
|
|
336854
|
-
|
|
336855
|
-
|
|
336866
|
+
collectAllMessages(method.input, service.file, wktImports, localImports, externalImports, allSeenMessages);
|
|
336867
|
+
collectAllMessages(method.output, service.file, wktImports, localImports, externalImports, allSeenMessages);
|
|
336868
|
+
const inputBaseName = method.input.name;
|
|
336869
|
+
const outputBaseName = method.output.name;
|
|
336856
336870
|
const camelName = method.name.charAt(0).toLowerCase() + method.name.slice(1);
|
|
336857
336871
|
const mutationVerbs = [
|
|
336858
336872
|
"Create",
|
|
@@ -336884,11 +336898,15 @@ function processService(service, protoPbFile, connectQueryFile) {
|
|
|
336884
336898
|
isPaginated: isPaginatedDeep(method.input) && isUnary && !isMutation
|
|
336885
336899
|
});
|
|
336886
336900
|
}
|
|
336901
|
+
const messageNames = Array.from(allSeenMessages).map((fullPath) => {
|
|
336902
|
+
return fullPath.split(".").pop();
|
|
336903
|
+
});
|
|
336887
336904
|
return {
|
|
336888
336905
|
serviceName: service.name,
|
|
336889
336906
|
protoPbFile,
|
|
336890
336907
|
connectQueryFile,
|
|
336891
336908
|
rpcs,
|
|
336909
|
+
messageNames,
|
|
336892
336910
|
wktImports: Array.from(wktImports),
|
|
336893
336911
|
localImports: Array.from(localImports),
|
|
336894
336912
|
externalImports: Array.from(externalImports.entries()).map(([path2, types2]) => ({
|
|
@@ -336899,7 +336917,7 @@ function processService(service, protoPbFile, connectQueryFile) {
|
|
|
336899
336917
|
}
|
|
336900
336918
|
var plugin = createEcmaScriptPlugin({
|
|
336901
336919
|
name: "protoc-gen-connect-vue",
|
|
336902
|
-
version: "v1.0.
|
|
336920
|
+
version: "v1.0.3",
|
|
336903
336921
|
generateTs: (schema) => {
|
|
336904
336922
|
let firstService = schema.files.flatMap((f) => f.services)[0];
|
|
336905
336923
|
if (!firstService)
|
package/package.json
CHANGED
|
@@ -1,17 +1,28 @@
|
|
|
1
|
-
import { computed } from "vue";
|
|
2
|
-
import {
|
|
1
|
+
import { computed, unref } from "vue";
|
|
2
|
+
import {
|
|
3
|
+
useQuery,
|
|
4
|
+
useMutation,
|
|
5
|
+
useInfiniteQuery,
|
|
6
|
+
useQueryClient,
|
|
7
|
+
useIsFetching,
|
|
8
|
+
useIsMutating
|
|
9
|
+
} from "@tanstack/vue-query";
|
|
3
10
|
import { ConnectError } from "@connectrpc/connect";
|
|
11
|
+
import { create } from "@bufbuild/protobuf";
|
|
4
12
|
import { useGrpcClient } from "./client";
|
|
5
13
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
14
|
+
{{#wktImports}}
|
|
15
|
+
import { {{.}}Schema } from "@bufbuild/protobuf";
|
|
16
|
+
import type { {{.}} } from "@bufbuild/protobuf";
|
|
17
|
+
{{/wktImports}}
|
|
18
|
+
|
|
19
|
+
{{#localImports}}
|
|
20
|
+
import { {{.}}Schema } from "./gen/{{{protoPbFile}}}";
|
|
21
|
+
import type { {{.}} } from "./gen/{{{protoPbFile}}}";
|
|
22
|
+
{{/localImports}}
|
|
11
23
|
|
|
12
|
-
{{#wktImports}}import type { {{.}} } from "@bufbuild/protobuf";{{/wktImports}}
|
|
13
|
-
{{#localImports}}import type { {{.}} } from "./gen/{{{protoPbFile}}}";{{/localImports}}
|
|
14
24
|
{{#externalImports}}
|
|
25
|
+
import { {{#types}}{{.}}Schema, {{/types}} } from "{{{path}}}";
|
|
15
26
|
import type { {{#types}}{{.}}, {{/types}} } from "{{{path}}}";
|
|
16
27
|
{{/externalImports}}
|
|
17
28
|
|
|
@@ -33,6 +44,25 @@ async function callConnect<ReqT, ResT, DataT>(
|
|
|
33
44
|
}
|
|
34
45
|
}
|
|
35
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Query Keys for manual cache management
|
|
49
|
+
*/
|
|
50
|
+
export const queryKeys = {
|
|
51
|
+
{{#rpcs}}
|
|
52
|
+
{{functionName}}: (input?: any) => ["{{resource}}", "{{functionName}}", input] as const,
|
|
53
|
+
{{/rpcs}}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Utility to create default/empty versions of Protobuf messages
|
|
58
|
+
* ensuring all fields (even nested ones) are initialized per schema.
|
|
59
|
+
*/
|
|
60
|
+
export const createEmpty = {
|
|
61
|
+
{{#messageNames}}
|
|
62
|
+
{{.}}: (data?: Partial<{{.}}>) => create({{.}}Schema, data),
|
|
63
|
+
{{/messageNames}}
|
|
64
|
+
};
|
|
65
|
+
|
|
36
66
|
export const useApi = () => {
|
|
37
67
|
const { client } = useGrpcClient();
|
|
38
68
|
const queryClient = useQueryClient();
|
|
@@ -52,5 +82,7 @@ export const useApi = () => {
|
|
|
52
82
|
{{hookName}},
|
|
53
83
|
{{/rpcs}}
|
|
54
84
|
isGlobalLoading,
|
|
85
|
+
queryKeys,
|
|
86
|
+
createEmpty
|
|
55
87
|
};
|
|
56
88
|
};
|
|
@@ -1,14 +1,25 @@
|
|
|
1
|
+
import { ref, computed } from "vue";
|
|
1
2
|
import { {{serviceName}} } from './gen/{{{protoPbFile}}}';
|
|
2
3
|
import { createClient, ConnectError, type Interceptor } from "@connectrpc/connect";
|
|
3
4
|
import { createConnectTransport } from "@connectrpc/connect-web";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
// This workaround insures that bigints returned from the backend
|
|
7
|
+
// can be serialized - otherwise you will run into some hard to pin down bugs
|
|
8
|
+
// dealing with bigint serialization
|
|
9
|
+
if (!(BigInt.prototype as any).toJSON) {
|
|
10
|
+
(BigInt.prototype as any).toJSON = function () {
|
|
11
|
+
return this.toString();
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// --- REACTIVE STATE ---
|
|
16
|
+
const baseUrl = ref('http://localhost:3000');
|
|
6
17
|
|
|
7
18
|
/**
|
|
8
19
|
* Configure the API endpoint at runtime.
|
|
9
20
|
*/
|
|
10
21
|
export const setBaseUrl = (url: string) => {
|
|
11
|
-
baseUrl = url;
|
|
22
|
+
baseUrl.value = url;
|
|
12
23
|
};
|
|
13
24
|
|
|
14
25
|
// --- AUTH RESOLVER ---
|
|
@@ -35,12 +46,20 @@ const transportInterceptor: Interceptor = (next) => async (req) => {
|
|
|
35
46
|
}
|
|
36
47
|
};
|
|
37
48
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
49
|
+
/**
|
|
50
|
+
* useGrpcClient provides a reactive client.
|
|
51
|
+
* If baseUrl.value changes, the computed client updates automatically.
|
|
52
|
+
*/
|
|
53
|
+
export const useGrpcClient = () => {
|
|
54
|
+
const transport = computed(() => createConnectTransport({
|
|
55
|
+
baseUrl: baseUrl.value,
|
|
56
|
+
interceptors: [transportInterceptor],
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
const client = computed(() => createClient({{serviceName}}, transport.value));
|
|
42
60
|
|
|
43
|
-
|
|
61
|
+
return { client };
|
|
62
|
+
};
|
|
44
63
|
|
|
45
64
|
export const globalQueryConfig = {
|
|
46
65
|
defaultOptions: {
|
|
@@ -52,8 +71,6 @@ export const globalQueryConfig = {
|
|
|
52
71
|
},
|
|
53
72
|
};
|
|
54
73
|
|
|
55
|
-
export const useGrpcClient = () => ({ client });
|
|
56
|
-
|
|
57
74
|
/*
|
|
58
75
|
|
|
59
76
|
// the following is an example of how to use the hybrid api
|
|
@@ -1,50 +1,59 @@
|
|
|
1
1
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
* Standard Async: {{functionName}}
|
|
3
|
+
* Used for manual actions. Invalidates the cache for this resource on success.
|
|
4
|
+
*/
|
|
5
|
+
const {{functionName}} = async (req: {{inputType}}): Promise<APIResponse<{{outputType}}>> => {
|
|
6
|
+
const res = await callConnect(client.value.{{functionName}}.bind(client.value), req, (res) => res);
|
|
7
|
+
if (!res.error) {
|
|
8
|
+
queryClient.invalidateQueries({ queryKey: ["{{resource}}"] });
|
|
9
|
+
}
|
|
10
|
+
return res;
|
|
11
|
+
};
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
{{
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
})
|
|
48
|
-
{
|
|
49
|
-
|
|
50
|
-
|
|
13
|
+
/**
|
|
14
|
+
* TanStack Hook: {{hookName}}
|
|
15
|
+
* Provides reactive data binding with caching.
|
|
16
|
+
*/
|
|
17
|
+
const {{hookName}} = (
|
|
18
|
+
{{#isQuery}}
|
|
19
|
+
input: any, // Accepts Ref<{{inputType}}> or {{inputType}}
|
|
20
|
+
options: any = {}
|
|
21
|
+
{{/isQuery}}
|
|
22
|
+
{{^isQuery}}
|
|
23
|
+
options: any = {}
|
|
24
|
+
{{/isQuery}}
|
|
25
|
+
) => {
|
|
26
|
+
{{#isPaginated}}
|
|
27
|
+
return useInfiniteQuery({
|
|
28
|
+
queryKey: queryKeys.{{functionName}}(input),
|
|
29
|
+
queryFn: async ({ pageParam }) => {
|
|
30
|
+
const req = { ...unref(input), page: pageParam };
|
|
31
|
+
return client.value.{{functionName}}(req);
|
|
32
|
+
},
|
|
33
|
+
initialPageParam: 1,
|
|
34
|
+
getNextPageParam: (lastPage: any) => lastPage.nextPage ?? undefined,
|
|
35
|
+
...options,
|
|
36
|
+
});
|
|
37
|
+
{{/isPaginated}}
|
|
38
|
+
|
|
39
|
+
{{^isPaginated}}
|
|
40
|
+
{{#isQuery}}
|
|
41
|
+
return useQuery({
|
|
42
|
+
queryKey: queryKeys.{{functionName}}(input),
|
|
43
|
+
queryFn: () => client.value.{{functionName}}(unref(input)),
|
|
44
|
+
...options,
|
|
45
|
+
});
|
|
46
|
+
{{/isQuery}}
|
|
47
|
+
|
|
48
|
+
{{^isQuery}}
|
|
49
|
+
return useMutation({
|
|
50
|
+
mutationFn: (req: {{inputType}}) => client.value.{{functionName}}(req),
|
|
51
|
+
onSuccess: async (data, variables, context) => {
|
|
52
|
+
await queryClient.invalidateQueries({ queryKey: ["{{resource}}"] });
|
|
53
|
+
if (options.onSuccess) return options.onSuccess(data, variables, context);
|
|
54
|
+
},
|
|
55
|
+
...options,
|
|
56
|
+
});
|
|
57
|
+
{{/isQuery}}
|
|
58
|
+
{{/isPaginated}}
|
|
59
|
+
};
|