@zachacious/protoc-gen-connect-vue 1.0.9 → 1.0.11

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,59 @@ 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 baseFileName = msg.file.name.replace(".proto", "");
336838
+ const importPath = `./gen/${baseFileName}_pb`;
336839
+ if (!importMap.has(importPath))
336840
+ importMap.set(importPath, new Set);
336841
+ importMap.get(importPath).add(msg.name);
336842
+ msg.fields.forEach((f) => f.fieldKind === "message" && track(f.message));
336854
336843
  }
336855
- for (const method of service.methods) {
336856
- trackMessage(method.input);
336857
- trackMessage(method.output);
336858
- const mutationVerbs = [
336844
+ const rpcs = service.methods.map((m) => {
336845
+ track(m.input);
336846
+ track(m.output);
336847
+ const isUnary = m.methodKind === METHOD_KIND_UNARY;
336848
+ const verbs = [
336859
336849
  "Create",
336860
336850
  "Update",
336861
336851
  "Delete",
@@ -336865,27 +336855,37 @@ function processService(service) {
336865
336855
  "Set",
336866
336856
  "Add"
336867
336857
  ];
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
- }
336858
+ const isMutation = verbs.some((v) => m.name.startsWith(v));
336859
+ const reqPath = findPath(m.input, [
336860
+ "page",
336861
+ "offset",
336862
+ "pagetoken",
336863
+ "cursor",
336864
+ "pagenumber"
336865
+ ]);
336866
+ const resPath = findPath(m.output, [
336867
+ "nextpagetoken",
336868
+ "nextpage",
336869
+ "hasmore",
336870
+ "nextcursor"
336871
+ ]);
336872
+ return {
336873
+ functionName: m.name.charAt(0).toLowerCase() + m.name.slice(1),
336874
+ hookName: `use${m.name}`,
336875
+ resource: m.name.replace(/^(Get|ListAll|List|Create|Update|Delete|Remove|Patch|Post|Set|Add)/, "") || "Global",
336876
+ inputType: m.input.name,
336877
+ outputType: m.output.name,
336878
+ isQuery: isUnary && !isMutation,
336879
+ isPaginated: isUnary && !isMutation && !!reqPath && !!resPath,
336880
+ reqPath: reqPath?.join("."),
336881
+ resPath: resPath?.join(".")
336882
+ };
336883
+ });
336884
336884
  return {
336885
336885
  serviceName: service.name,
336886
- protoPbFile: service.file.name.replace(".proto", "_pb"),
336886
+ protoPbFile: `${service.file.name.replace(".proto", "")}_pb`,
336887
336887
  rpcs,
336888
- messageNames: Array.from(allMessages.keys()),
336888
+ messageNames: Array.from(allMessages),
336889
336889
  wktImports: Array.from(wktImports),
336890
336890
  externalImports: Array.from(importMap.entries()).map(([path2, types2]) => ({
336891
336891
  path: path2,
@@ -336895,15 +336895,15 @@ function processService(service) {
336895
336895
  }
336896
336896
  var plugin = createEcmaScriptPlugin({
336897
336897
  name: "protoc-gen-connect-vue",
336898
- version: "v1.0.4",
336898
+ version: "v1.1.0",
336899
336899
  generateTs: (schema) => {
336900
- const firstService = schema.files.flatMap((f) => f.services)[0];
336901
- if (!firstService)
336900
+ const service = schema.files.flatMap((f) => f.services)[0];
336901
+ if (!service)
336902
336902
  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, {}));
336903
+ const data = processService(service);
336904
+ schema.generateFile("client.ts").print(mustache_default.render(templates.client, data));
336905
+ schema.generateFile("api.ts").print(mustache_default.render(templates.api, data, { rpc: templates.rpc }));
336906
+ schema.generateFile("index.ts").print(mustache_default.render(templates.index, {}));
336907
336907
  }
336908
336908
  });
336909
336909
  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.11",
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,14 +39,13 @@ 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();
57
-
58
49
  const isFetching = useIsFetching();
59
50
  const isMutating = useIsMutating();
60
51
  const isGlobalLoading = computed(() => isFetching.value > 0 || isMutating.value > 0);
@@ -1,6 +1,5 @@
1
1
  /**
2
- * Standard Async: {{functionName}}
3
- * Used for manual actions. Invalidates the cache for this resource on success.
2
+ * Async: {{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);
@@ -11,12 +10,11 @@ const {{functionName}} = async (req: {{inputType}}): Promise<APIResponse<{{outpu
11
10
  };
12
11
 
13
12
  /**
14
- * TanStack Hook: {{hookName}}
15
- * Provides reactive data binding with caching.
13
+ * Hook: {{hookName}}
16
14
  */
17
15
  const {{hookName}} = (
18
16
  {{#isQuery}}
19
- input: any, // Accepts Ref<{{inputType}}> or {{inputType}}
17
+ input: any,
20
18
  options: any = {}
21
19
  {{/isQuery}}
22
20
  {{^isQuery}}
@@ -27,11 +25,26 @@ const {{hookName}} = (
27
25
  return useInfiniteQuery({
28
26
  queryKey: queryKeys.{{functionName}}(input),
29
27
  queryFn: async ({ pageParam }) => {
30
- const req = { ...unref(input), page: pageParam };
28
+ // Deep clone to avoid mutating reactive state
29
+ const req = JSON.parse(JSON.stringify(unref(input)));
30
+
31
+ // Structural Injection: Navigate to the field found during generation
32
+ const path = "{{reqPath}}".split('.');
33
+ let current = req;
34
+ for (let i = 0; i < path.length - 1; i++) {
35
+ if (!current[path[i]]) current[path[i]] = {};
36
+ current = current[path[i]];
37
+ }
38
+ current[path[path.length - 1]] = pageParam;
39
+
31
40
  return client.value.{{functionName}}(req);
32
41
  },
33
- initialPageParam: 1,
34
- getNextPageParam: (lastPage: any) => lastPage.nextPage ?? undefined,
42
+ initialPageParam: options.initialPageParam ?? 1,
43
+ getNextPageParam: (lastPage: any) => {
44
+ // Structural Extraction: Navigate to the field found during generation
45
+ const path = "{{resPath}}".split('.');
46
+ return path.reduce((o, i) => o?.[i], lastPage) || undefined;
47
+ },
35
48
  ...options,
36
49
  });
37
50
  {{/isPaginated}}