@zachacious/protoc-gen-connect-vue 1.0.9 → 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,69 +336793,58 @@ 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 = [
336813
- "page",
336814
- "offset",
336815
- "cursor",
336816
- "limit",
336817
- "pagesize",
336818
- "pagenumber"
336819
- ];
336820
- for (const field of message.fields) {
336821
- if (pagingKeys.includes(field.name.toLowerCase()))
336822
- return true;
336823
- if (field.fieldKind === "message" && isPaginatedDeep(field.message, visited))
336824
- return true;
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];
336825
336815
  }
336826
- return false;
336827
- }
336828
- function resolveImportPath(message) {
336829
- return `./gen/${message.file.name.replace(".proto", "_pb")}`;
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];
336821
+ }
336822
+ }
336823
+ return null;
336830
336824
  }
336831
336825
  function processService(service) {
336832
- const rpcs = [];
336833
- const wktImports = new Set;
336834
336826
  const importMap = new Map;
336835
- const allMessages = new Map;
336836
- function trackMessage(msg) {
336837
- if (allMessages.has(msg.name))
336838
- return;
336839
- const fullTypeName = msg.typeName;
336840
- if (KNOWN_TYPES[fullTypeName]) {
336841
- wktImports.add(KNOWN_TYPES[fullTypeName]);
336842
- allMessages.set(KNOWN_TYPES[fullTypeName], "@bufbuild/protobuf/wkt");
336827
+ const wktImports = new Set;
336828
+ const allMessages = new Set;
336829
+ function track(msg) {
336830
+ if (KNOWN_WKT.includes(msg.typeName)) {
336831
+ wktImports.add(msg.name);
336843
336832
  return;
336844
336833
  }
336845
- const targetPath = resolveImportPath(msg);
336846
- if (!importMap.has(targetPath))
336847
- importMap.set(targetPath, new Set);
336848
- importMap.get(targetPath).add(msg.name);
336849
- allMessages.set(msg.name, targetPath);
336850
- for (const field of msg.fields) {
336851
- if (field.fieldKind === "message")
336852
- trackMessage(field.message);
336853
- }
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));
336854
336842
  }
336855
- for (const method of service.methods) {
336856
- trackMessage(method.input);
336857
- trackMessage(method.output);
336858
- const mutationVerbs = [
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 = [
336859
336848
  "Create",
336860
336849
  "Update",
336861
336850
  "Delete",
@@ -336865,27 +336854,37 @@ function processService(service) {
336865
336854
  "Set",
336866
336855
  "Add"
336867
336856
  ];
336868
- const camelName = method.name.charAt(0).toLowerCase() + method.name.slice(1);
336869
- let resource = method.name;
336870
- mutationVerbs.forEach((v) => {
336871
- if (method.name.startsWith(v))
336872
- resource = method.name.replace(v, "");
336873
- });
336874
- rpcs.push({
336875
- functionName: camelName,
336876
- hookName: `use${method.name}`,
336877
- resource,
336878
- inputType: method.input.name,
336879
- outputType: method.output.name,
336880
- isQuery: method.methodKind === METHOD_KIND_UNARY && !mutationVerbs.some((v) => method.name.startsWith(v)),
336881
- isPaginated: isPaginatedDeep(method.input) && method.methodKind === METHOD_KIND_UNARY
336882
- });
336883
- }
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
+ });
336884
336883
  return {
336885
336884
  serviceName: service.name,
336886
336885
  protoPbFile: service.file.name.replace(".proto", "_pb"),
336887
336886
  rpcs,
336888
- messageNames: Array.from(allMessages.keys()),
336887
+ messageNames: Array.from(allMessages),
336889
336888
  wktImports: Array.from(wktImports),
336890
336889
  externalImports: Array.from(importMap.entries()).map(([path2, types2]) => ({
336891
336890
  path: path2,
@@ -336895,15 +336894,15 @@ function processService(service) {
336895
336894
  }
336896
336895
  var plugin = createEcmaScriptPlugin({
336897
336896
  name: "protoc-gen-connect-vue",
336898
- version: "v1.0.4",
336897
+ version: "v1.0.7",
336899
336898
  generateTs: (schema) => {
336900
- const firstService = schema.files.flatMap((f) => f.services)[0];
336901
- if (!firstService)
336899
+ const service = schema.files.flatMap((f) => f.services)[0];
336900
+ if (!service)
336902
336901
  return;
336903
- const viewData = processService(firstService);
336904
- schema.generateFile("client.ts").print(mustache_default.render(clientTemplate, viewData));
336905
- schema.generateFile("api.ts").print(mustache_default.render(apiTemplate, viewData, partials));
336906
- 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, {}));
336907
336906
  }
336908
336907
  });
336909
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.9",
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,14 +1,9 @@
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
- import { create, type MessageInit } from "@bufbuild/protobuf";
6
+ import { create } from "@bufbuild/protobuf";
12
7
  import { useGrpcClient } from "./client";
13
8
 
14
9
  {{#wktImports}}
@@ -24,18 +19,15 @@ import type { {{#types}}{{.}}, {{/types}} } from "{{{path}}}";
24
19
  export type APIResponse<T> = { error: string | null; data: T | null; };
25
20
 
26
21
  async function callConnect<ReqT, ResT, DataT>(
27
- connectMethod: (req: ReqT) => Promise<ResT>,
28
- request: ReqT,
29
- dataExtractor: (res: ResT) => DataT
22
+ method: (req: ReqT) => Promise<ResT>,
23
+ req: ReqT,
24
+ extractor: (res: ResT) => DataT
30
25
  ): Promise<APIResponse<DataT>> {
31
26
  try {
32
- const res = await connectMethod(request);
33
- return { error: null, data: dataExtractor(res) };
27
+ const res = await method(req);
28
+ return { error: null, data: extractor(res) };
34
29
  } catch (err) {
35
- return {
36
- error: err instanceof ConnectError ? err.message : "Unknown error",
37
- data: null
38
- };
30
+ return { error: err instanceof ConnectError ? err.message : "Unknown error", data: null };
39
31
  }
40
32
  }
41
33
 
@@ -47,29 +39,19 @@ export const queryKeys = {
47
39
 
48
40
  export const createEmpty = {
49
41
  {{#messageNames}}
50
- {{.}}: (data?: MessageInit<{{.}}>) => create({{.}}Schema, data),
42
+ {{.}}: (data?: any) => create({{.}}Schema, data) as {{.}},
51
43
  {{/messageNames}}
52
44
  };
53
45
 
54
46
  export const useApi = () => {
55
47
  const { client } = useGrpcClient();
56
48
  const queryClient = useQueryClient();
49
+ const isGlobalLoading = computed(() => useIsFetching().value > 0 || useIsMutating().value > 0);
57
50
 
58
- const isFetching = useIsFetching();
59
- const isMutating = useIsMutating();
60
- const isGlobalLoading = computed(() => isFetching.value > 0 || isMutating.value > 0);
61
-
62
- {{#rpcs}}
63
- {{> rpc}}
64
- {{/rpcs}}
51
+ {{#rpcs}}{{> rpc}}{{/rpcs}}
65
52
 
66
53
  return {
67
- {{#rpcs}}
68
- {{functionName}},
69
- {{hookName}},
70
- {{/rpcs}}
71
- isGlobalLoading,
72
- queryKeys,
73
- createEmpty
54
+ {{#rpcs}}{{functionName}}, {{hookName}}, {{/rpcs}}
55
+ isGlobalLoading, queryKeys, createEmpty
74
56
  };
75
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
  });