@zachacious/protoc-gen-connect-vue 1.0.8 → 1.0.10

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/generator.js CHANGED
@@ -336793,96 +336793,100 @@ var mustache_default = mustache;
336793
336793
 
336794
336794
  // src/generator.ts
336795
336795
  var METHOD_KIND_UNARY = 1;
336796
- var KNOWN_TYPES = {
336797
- "google.protobuf.Empty": "Empty",
336798
- "google.protobuf.Timestamp": "Timestamp",
336799
- "google.protobuf.Duration": "Duration"
336800
- };
336796
+ var KNOWN_WKT = [
336797
+ "google.protobuf.Empty",
336798
+ "google.protobuf.Timestamp",
336799
+ "google.protobuf.Duration"
336800
+ ];
336801
336801
  var currentDir = new URL(".", import.meta.url).pathname;
336802
336802
  var templateDir = path.join(currentDir, "..", "templates");
336803
- var clientTemplate = fs.readFileSync(path.join(templateDir, "client.ts.mustache"), "utf-8");
336804
- var apiTemplate = fs.readFileSync(path.join(templateDir, "api.ts.mustache"), "utf-8");
336805
- var rpcPartial = fs.readFileSync(path.join(templateDir, "rpc.ts.mustache"), "utf-8");
336806
- var indexTemplate = fs.readFileSync(path.join(templateDir, "index.ts.mustache"), "utf-8");
336807
- var partials = { rpc: rpcPartial };
336808
- function isPaginatedDeep(message, visited = new Set) {
336809
- if (visited.has(message.typeName))
336810
- return false;
336811
- visited.add(message.typeName);
336812
- const pagingKeys = ["page", "offset", "cursor", "limit", "pagesize", "pagenumber"];
336813
- for (const field of message.fields) {
336814
- if (pagingKeys.includes(field.name.toLowerCase()))
336815
- return true;
336816
- if (field.fieldKind === "message") {
336817
- if (isPaginatedDeep(field.message, visited))
336818
- return true;
336819
- }
336820
- }
336821
- return false;
336822
- }
336823
- function processType(typeDesc, serviceFile, wktImports, localImports, externalImports) {
336824
- const fullTypeName = typeDesc.typeName;
336825
- const baseName = typeDesc.name;
336826
- if (KNOWN_TYPES[fullTypeName]) {
336827
- wktImports.add(KNOWN_TYPES[fullTypeName]);
336828
- return KNOWN_TYPES[fullTypeName];
336829
- }
336830
- const importPath = `./gen/${typeDesc.file.name.replace(".proto", "_pb")}`;
336831
- if (typeDesc.file.name === serviceFile.name) {
336832
- localImports.add(baseName);
336833
- } else {
336834
- if (!externalImports.has(importPath)) {
336835
- externalImports.set(importPath, new Set);
336836
- }
336837
- externalImports.get(importPath).add(baseName);
336803
+ var templates = {
336804
+ client: fs.readFileSync(path.join(templateDir, "client.ts.mustache"), "utf-8"),
336805
+ api: fs.readFileSync(path.join(templateDir, "api.ts.mustache"), "utf-8"),
336806
+ rpc: fs.readFileSync(path.join(templateDir, "rpc.ts.mustache"), "utf-8"),
336807
+ index: fs.readFileSync(path.join(templateDir, "index.ts.mustache"), "utf-8")
336808
+ };
336809
+ function findPath(msg, targets, depth = 0) {
336810
+ if (depth > 4)
336811
+ return null;
336812
+ for (const f of msg.fields) {
336813
+ if (targets.includes(f.name.toLowerCase()))
336814
+ return [f.name];
336838
336815
  }
336839
- return baseName;
336840
- }
336841
- function collectAllMessages(message, serviceFile, wktImports, localImports, externalImports, seen = new Set) {
336842
- if (seen.has(message.typeName))
336843
- return;
336844
- seen.add(message.typeName);
336845
- processType(message, serviceFile, wktImports, localImports, externalImports);
336846
- for (const field of message.fields) {
336847
- if (field.fieldKind === "message") {
336848
- collectAllMessages(field.message, serviceFile, wktImports, localImports, externalImports, seen);
336816
+ for (const f of msg.fields) {
336817
+ if (f.fieldKind === "message") {
336818
+ const p = findPath(f.message, targets, depth + 1);
336819
+ if (p)
336820
+ return [f.name, ...p];
336849
336821
  }
336850
336822
  }
336823
+ return null;
336851
336824
  }
336852
- function processService(service, protoPbFile) {
336853
- const rpcs = [];
336825
+ function processService(service) {
336826
+ const importMap = new Map;
336854
336827
  const wktImports = new Set;
336855
- const localImports = new Set;
336856
- const externalImports = new Map;
336857
- const allSeenMessages = new Set;
336858
- for (const method of service.methods) {
336859
- collectAllMessages(method.input, service.file, wktImports, localImports, externalImports, allSeenMessages);
336860
- collectAllMessages(method.output, service.file, wktImports, localImports, externalImports, allSeenMessages);
336861
- const camelName = method.name.charAt(0).toLowerCase() + method.name.slice(1);
336862
- const mutationVerbs = ["Create", "Update", "Delete", "Remove", "Patch", "Post", "Set", "Add"];
336863
- let resource = method.name;
336864
- mutationVerbs.forEach((verb) => {
336865
- if (method.name.startsWith(verb))
336866
- resource = method.name.replace(verb, "");
336867
- });
336868
- rpcs.push({
336869
- functionName: camelName,
336870
- hookName: `use${method.name}`,
336871
- resource,
336872
- inputType: method.input.name,
336873
- outputType: method.output.name,
336874
- isQuery: method.methodKind === METHOD_KIND_UNARY && !mutationVerbs.some((v) => method.name.startsWith(v)),
336875
- isPaginated: isPaginatedDeep(method.input) && method.methodKind === METHOD_KIND_UNARY
336876
- });
336828
+ const allMessages = new Set;
336829
+ function track(msg) {
336830
+ if (KNOWN_WKT.includes(msg.typeName)) {
336831
+ wktImports.add(msg.name);
336832
+ return;
336833
+ }
336834
+ if (allMessages.has(msg.name))
336835
+ return;
336836
+ allMessages.add(msg.name);
336837
+ const importPath = `./gen/${msg.file.name.replace(".proto", "_pb")}`;
336838
+ if (!importMap.has(importPath))
336839
+ importMap.set(importPath, new Set);
336840
+ importMap.get(importPath).add(msg.name);
336841
+ msg.fields.forEach((f) => f.fieldKind === "message" && track(f.message));
336877
336842
  }
336843
+ const rpcs = service.methods.map((m) => {
336844
+ track(m.input);
336845
+ track(m.output);
336846
+ const isUnary = m.methodKind === METHOD_KIND_UNARY;
336847
+ const verbs = [
336848
+ "Create",
336849
+ "Update",
336850
+ "Delete",
336851
+ "Remove",
336852
+ "Patch",
336853
+ "Post",
336854
+ "Set",
336855
+ "Add"
336856
+ ];
336857
+ const isMutation = verbs.some((v) => m.name.startsWith(v));
336858
+ const reqPath = findPath(m.input, [
336859
+ "page",
336860
+ "offset",
336861
+ "pagetoken",
336862
+ "cursor",
336863
+ "pagenumber"
336864
+ ]);
336865
+ const resPath = findPath(m.output, [
336866
+ "nextpagetoken",
336867
+ "nextpage",
336868
+ "hasmore",
336869
+ "nextcursor"
336870
+ ]);
336871
+ return {
336872
+ functionName: m.name.charAt(0).toLowerCase() + m.name.slice(1),
336873
+ hookName: `use${m.name}`,
336874
+ resource: m.name.replace(/^(Get|ListAll|List|Create|Update|Delete|Remove|Patch|Post|Set|Add)/, ""),
336875
+ inputType: m.input.name,
336876
+ outputType: m.output.name,
336877
+ isQuery: isUnary && !isMutation,
336878
+ isPaginated: isUnary && !isMutation && !!reqPath && !!resPath,
336879
+ reqPath: reqPath?.join("."),
336880
+ resPath: resPath?.join(".")
336881
+ };
336882
+ });
336878
336883
  return {
336879
336884
  serviceName: service.name,
336880
- protoPbFile,
336885
+ protoPbFile: service.file.name.replace(".proto", "_pb"),
336881
336886
  rpcs,
336882
- messageNames: Array.from(allSeenMessages).map((m) => m.split(".").pop()),
336887
+ messageNames: Array.from(allMessages),
336883
336888
  wktImports: Array.from(wktImports),
336884
- localImports: Array.from(localImports),
336885
- externalImports: Array.from(externalImports.entries()).map(([path2, types2]) => ({
336889
+ externalImports: Array.from(importMap.entries()).map(([path2, types2]) => ({
336886
336890
  path: path2,
336887
336891
  types: Array.from(types2)
336888
336892
  }))
@@ -336890,16 +336894,15 @@ function processService(service, protoPbFile) {
336890
336894
  }
336891
336895
  var plugin = createEcmaScriptPlugin({
336892
336896
  name: "protoc-gen-connect-vue",
336893
- version: "v1.0.3",
336897
+ version: "v1.0.7",
336894
336898
  generateTs: (schema) => {
336895
- let firstService = schema.files.flatMap((f) => f.services)[0];
336896
- if (!firstService)
336899
+ const service = schema.files.flatMap((f) => f.services)[0];
336900
+ if (!service)
336897
336901
  return;
336898
- const protoFileStem = firstService.file.name.replace(".proto", "");
336899
- const viewData = processService(firstService, `${protoFileStem}_pb`);
336900
- schema.generateFile("client.ts").print(mustache_default.render(clientTemplate, viewData));
336901
- schema.generateFile("api.ts").print(mustache_default.render(apiTemplate, viewData, partials));
336902
- schema.generateFile("index.ts").print(mustache_default.render(indexTemplate, {}));
336902
+ const data = processService(service);
336903
+ schema.generateFile("client.ts").print(mustache_default.render(templates.client, data));
336904
+ schema.generateFile("api.ts").print(mustache_default.render(templates.api, data, { rpc: templates.rpc }));
336905
+ schema.generateFile("index.ts").print(mustache_default.render(templates.index, {}));
336903
336906
  }
336904
336907
  });
336905
336908
  runNodeJs(plugin);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zachacious/protoc-gen-connect-vue",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Smart TanStack Query & ConnectRPC SDK generator for Vue.js",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,28 +1,16 @@
1
1
  import { computed, unref } from "vue";
2
2
  import {
3
- useQuery,
4
- useMutation,
5
- useInfiniteQuery,
6
- useQueryClient,
7
- useIsFetching,
8
- useIsMutating
3
+ useQuery, useMutation, useInfiniteQuery, useQueryClient, useIsFetching, useIsMutating
9
4
  } from "@tanstack/vue-query";
10
5
  import { ConnectError } from "@connectrpc/connect";
11
- // FIXED: MessageInit is the correct type for 'create' payload
12
- import { create, type MessageInit } from "@bufbuild/protobuf";
6
+ import { create } from "@bufbuild/protobuf";
13
7
  import { useGrpcClient } from "./client";
14
8
 
15
9
  {{#wktImports}}
16
- // FIXED: Path for v2 WKTs
17
10
  import { {{.}}Schema } from "@bufbuild/protobuf/wkt";
18
11
  import type { {{.}} } from "@bufbuild/protobuf/wkt";
19
12
  {{/wktImports}}
20
13
 
21
- {{#localImports}}
22
- import { {{.}}Schema } from "./gen/{{{protoPbFile}}}";
23
- import type { {{.}} } from "./gen/{{{protoPbFile}}}";
24
- {{/localImports}}
25
-
26
14
  {{#externalImports}}
27
15
  import { {{#types}}{{.}}Schema, {{/types}} } from "{{{path}}}";
28
16
  import type { {{#types}}{{.}}, {{/types}} } from "{{{path}}}";
@@ -31,18 +19,15 @@ import type { {{#types}}{{.}}, {{/types}} } from "{{{path}}}";
31
19
  export type APIResponse<T> = { error: string | null; data: T | null; };
32
20
 
33
21
  async function callConnect<ReqT, ResT, DataT>(
34
- connectMethod: (req: ReqT) => Promise<ResT>,
35
- request: ReqT,
36
- dataExtractor: (res: ResT) => DataT
22
+ method: (req: ReqT) => Promise<ResT>,
23
+ req: ReqT,
24
+ extractor: (res: ResT) => DataT
37
25
  ): Promise<APIResponse<DataT>> {
38
26
  try {
39
- const res = await connectMethod(request);
40
- return { error: null, data: dataExtractor(res) };
27
+ const res = await method(req);
28
+ return { error: null, data: extractor(res) };
41
29
  } catch (err) {
42
- return {
43
- error: err instanceof ConnectError ? err.message : "Unknown error",
44
- data: null
45
- };
30
+ return { error: err instanceof ConnectError ? err.message : "Unknown error", data: null };
46
31
  }
47
32
  }
48
33
 
@@ -52,34 +37,21 @@ export const queryKeys = {
52
37
  {{/rpcs}}
53
38
  };
54
39
 
55
- /**
56
- * Utility to create default/empty versions of Protobuf messages
57
- */
58
40
  export const createEmpty = {
59
41
  {{#messageNames}}
60
- {{.}}: (data?: MessageInit<{{.}}>) => create({{.}}Schema, data),
42
+ {{.}}: (data?: any) => create({{.}}Schema, data) as {{.}},
61
43
  {{/messageNames}}
62
44
  };
63
45
 
64
46
  export const useApi = () => {
65
47
  const { client } = useGrpcClient();
66
48
  const queryClient = useQueryClient();
49
+ const isGlobalLoading = computed(() => useIsFetching().value > 0 || useIsMutating().value > 0);
67
50
 
68
- const isFetching = useIsFetching();
69
- const isMutating = useIsMutating();
70
- const isGlobalLoading = computed(() => isFetching.value > 0 || isMutating.value > 0);
71
-
72
- {{#rpcs}}
73
- {{> rpc}}
74
- {{/rpcs}}
51
+ {{#rpcs}}{{> rpc}}{{/rpcs}}
75
52
 
76
53
  return {
77
- {{#rpcs}}
78
- {{functionName}},
79
- {{hookName}},
80
- {{/rpcs}}
81
- isGlobalLoading,
82
- queryKeys,
83
- createEmpty
54
+ {{#rpcs}}{{functionName}}, {{hookName}}, {{/rpcs}}
55
+ isGlobalLoading, queryKeys, createEmpty
84
56
  };
85
57
  };
@@ -1,41 +1,41 @@
1
1
  /**
2
- * Standard Async: {{functionName}}
3
- * Used for manual actions. Invalidates the cache for this resource on success.
2
+ * Async Wrapper: {{functionName}}
4
3
  */
5
4
  const {{functionName}} = async (req: {{inputType}}): Promise<APIResponse<{{outputType}}>> => {
6
5
  const res = await callConnect(client.value.{{functionName}}.bind(client.value), req, (res) => res);
7
- if (!res.error) {
8
- queryClient.invalidateQueries({ queryKey: ["{{resource}}"] });
9
- }
6
+ if (!res.error) queryClient.invalidateQueries({ queryKey: ["{{resource}}"] });
10
7
  return res;
11
8
  };
12
9
 
13
10
  /**
14
- * TanStack Hook: {{hookName}}
15
- * Provides reactive data binding with caching.
11
+ * Hook: {{hookName}}
16
12
  */
17
13
  const {{hookName}} = (
18
- {{#isQuery}}
19
- input: any, // Accepts Ref<{{inputType}}> or {{inputType}}
20
- options: any = {}
21
- {{/isQuery}}
22
- {{^isQuery}}
23
- options: any = {}
24
- {{/isQuery}}
14
+ {{#isQuery}}input: any, options: any = {}{{/isQuery}}
15
+ {{^isQuery}}options: any = {}{{/isQuery}}
25
16
  ) => {
26
17
  {{#isPaginated}}
27
18
  return useInfiniteQuery({
28
19
  queryKey: queryKeys.{{functionName}}(input),
29
20
  queryFn: async ({ pageParam }) => {
30
- const req = { ...unref(input), page: pageParam };
21
+ const req = JSON.parse(JSON.stringify(unref(input)));
22
+ const path = "{{reqPath}}".split('.');
23
+ let curr = req;
24
+ for (let i = 0; i < path.length - 1; i++) {
25
+ curr[path[i]] = curr[path[i]] || {};
26
+ curr = curr[path[i]];
27
+ }
28
+ curr[path[path.length - 1]] = pageParam;
31
29
  return client.value.{{functionName}}(req);
32
30
  },
33
- initialPageParam: 1,
34
- getNextPageParam: (lastPage: any) => lastPage.nextPage ?? undefined,
31
+ initialPageParam: options.initialPageParam ?? 1,
32
+ getNextPageParam: (lastPage: any) => {
33
+ const path = "{{resPath}}".split('.');
34
+ return path.reduce((o, i) => o?.[i], lastPage) || undefined;
35
+ },
35
36
  ...options,
36
37
  });
37
38
  {{/isPaginated}}
38
-
39
39
  {{^isPaginated}}
40
40
  {{#isQuery}}
41
41
  return useQuery({
@@ -44,13 +44,12 @@ const {{hookName}} = (
44
44
  ...options,
45
45
  });
46
46
  {{/isQuery}}
47
-
48
47
  {{^isQuery}}
49
48
  return useMutation({
50
49
  mutationFn: (req: {{inputType}}) => client.value.{{functionName}}(req),
51
- onSuccess: async (data, variables, context) => {
50
+ onSuccess: async (d, v, c) => {
52
51
  await queryClient.invalidateQueries({ queryKey: ["{{resource}}"] });
53
- if (options.onSuccess) return options.onSuccess(data, variables, context);
52
+ if (options.onSuccess) return options.onSuccess(d, v, c);
54
53
  },
55
54
  ...options,
56
55
  });