graphql-data-generator 0.1.7 → 0.2.1

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 CHANGED
@@ -84,6 +84,72 @@ const createPost = build.CreatePost({ data: { createPost: { id: "post-id" } } })
84
84
  .withAuthorId("user-3");
85
85
  ```
86
86
 
87
+ ## CLI options
88
+
89
+ - `banner=file|copy`: Places copy at the beginning of the generated output. If a
90
+ file path is detected, will use the contents of the file.
91
+ - `enums=[enums|literals|none|import:*]`: Describes how enums are generated.
92
+ - `enums`: Use TypeScript enum. E.g., `enum Status { Draft, Submitted }`
93
+ - `literals`: Use string literals. E.g.,
94
+ `type Status = "Draft" | "Submitted";`
95
+ - `none`: Skip generating of enums all together.
96
+ - `import:file` Import enums from the provided path. E.g.,
97
+ `import { Status } from "file";`
98
+ - `exports=[operations|types]`: Toggle exporting of operations and/or types.
99
+ - `notypenames`: Toggle automatic inclusion of `__typename`.
100
+ - `operations=dir`: Restrict generation of operations to a specific directory.
101
+ Can be used multiple times for multiple directories.
102
+ - `outfile=file`: Switch output to right to `file` instead of stdout.
103
+ - `scalar=Scalar:Type`: Specify the type of an individual scalar. E.g.,
104
+ `type Scalar = Type;`
105
+ - `scalars=file.json`: Specify multiple scalars from a json file.
106
+ - `schema=file`: Specify the GraphQL schema file to use.
107
+ - `typesFile=file`: Specify file generated by
108
+ [`@graphql-codegen`](https://the-guild.dev/graphql/codegen) to import types
109
+ from.
110
+
111
+ ## @graphql-codegen plugin
112
+
113
+ A plugin shim exists for
114
+ [`@graphql-codegen`](https://the-guild.dev/graphql/codegen):
115
+
116
+ ```ts
117
+ import type { CodegenConfig } from "@graphql-codegen/cli";
118
+
119
+ const config: CodegenConfig = {
120
+ schema: "src/schema.graphql",
121
+ documents: ["src/**/*.gql", "src/**/*.ts"],
122
+ generates: {
123
+ "./src/graphql/": {
124
+ preset: "client",
125
+ config: {
126
+ scalars: {
127
+ DateTime: "string",
128
+ URL: "string",
129
+ },
130
+ },
131
+ },
132
+ "./src/build/generated.ts": {
133
+ plugins: ["graphql-data-generator/plugin"],
134
+ config: {
135
+ typesFile: "../graphql/graphql.js",
136
+ scalars: {
137
+ DateTime: "string",
138
+ URL: "string",
139
+ },
140
+ banner: "// generated\n",
141
+ },
142
+ },
143
+ },
144
+ };
145
+
146
+ export default config;
147
+ ```
148
+
149
+ Specifying a `typesFile` will skip outputting generated types and will instead
150
+ depend on the types generated by `@graphql-codegen` itself. The `generated.ts`
151
+ file can then be consumed by a `build` script similar to the above example.
152
+
87
153
  ## Patches
88
154
 
89
155
  A `patch` is similar to a `DeepPartial` with a few extensions. First, functions
package/esm/cli.js CHANGED
@@ -9,14 +9,16 @@ import process from "node:process";
9
9
  const args = parseArgs({
10
10
  args: process.argv.slice(2),
11
11
  options: {
12
- schema: { type: "string" },
13
- scalars: { type: "string" },
12
+ banner: { type: "string" },
13
+ enums: { type: "string" },
14
+ exports: { type: "string", multiple: true },
15
+ notypenames: { type: "boolean" },
14
16
  operations: { type: "string", multiple: true },
15
- scalar: { type: "string", multiple: true },
16
17
  outfile: { type: "string" },
17
- enums: { type: "boolean" },
18
- notypenames: { type: "boolean" },
19
- exports: { type: "string", multiple: true },
18
+ scalar: { type: "string", multiple: true },
19
+ scalars: { type: "string" },
20
+ schema: { type: "string" },
21
+ typesFile: { type: "string" },
20
22
  },
21
23
  }).values;
22
24
  const findFirst = async (path) => {
@@ -39,14 +41,7 @@ if (!schemaPath) {
39
41
  }
40
42
  const operationDirs = args.operations?.map((v) => `${v}`) ?? ["."];
41
43
  const [schema, operations] = await loadFiles(schemaPath, operationDirs);
42
- const defaultScalars = {
43
- Int: "number",
44
- Float: "number",
45
- String: "string",
46
- Boolean: "boolean",
47
- ID: "string",
48
- };
49
- const scalars = { ...defaultScalars };
44
+ const scalars = {};
50
45
  if (args.scalars) {
51
46
  readFile;
52
47
  Object.assign(scalars, JSON.parse(await dntShim.Deno.readTextFile(args.scalars)));
@@ -69,12 +64,26 @@ const exports = args.exports
69
64
  return true;
70
65
  })
71
66
  : [];
67
+ if (typeof args.enums === "string" &&
68
+ (!["enums", "literals", "none"].includes(args.enums) &&
69
+ !args.enums.startsWith("import:"))) {
70
+ throw new Error(`Invalid 'enums'. Must be one of 'enums', 'literals', 'import', 'none'`);
71
+ }
72
+ let banner = "";
73
+ if (typeof args.banner === "string") {
74
+ if (await dntShim.Deno.lstat(args.banner).catch(() => false)) {
75
+ banner = await dntShim.Deno.readTextFile(args.banner);
76
+ }
77
+ else
78
+ banner = args.banner;
79
+ }
72
80
  try {
73
- const file = await formatCode(codegen(schema, operations, {
74
- useEnums: args.enums ?? false,
81
+ const file = banner + await formatCode(codegen(schema, operations, {
82
+ enums: args.enums,
75
83
  includeTypenames: !args.notypenames,
76
84
  scalars,
77
85
  exports,
86
+ typesFile: args.typesFile,
78
87
  }));
79
88
  if (args.outfile)
80
89
  await dntShim.Deno.writeTextFile(args.outfile, file);
package/esm/codegen.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { readFile } from "node:fs/promises";
2
- import { Kind, parse } from "graphql";
2
+ import { Kind, parse, printSchema } from "graphql";
3
3
  import fg from "fast-glob";
4
- import { join } from "node:path";
4
+ import { join, relative } from "node:path";
5
5
  import { raise } from "./util.js";
6
+ import process from "node:process";
6
7
  const getType = ({ type, ...props }) => {
7
8
  if (type.kind === "NamedType") {
8
9
  if (props.selections) {
@@ -222,6 +223,7 @@ const serializeType = (type, variables = false, depth = 0) => {
222
223
  return `"${type.value}"`;
223
224
  }
224
225
  };
226
+ const drop = () => false;
225
227
  const serializeInput = (fields, optional, inputs, references) => ({
226
228
  kind: "Object",
227
229
  value: Object.fromEntries(fields.map((f) => [
@@ -278,8 +280,16 @@ const simpleType = (type, types, optional = true) => {
278
280
  };
279
281
  }
280
282
  };
281
- export const codegen = (schema, files, { useEnums = true, scalars = {}, includeTypenames = true, exports = [], } = {}) => {
282
- const schemaDoc = parse(schema);
283
+ const defaultScalars = {
284
+ Int: "number",
285
+ Float: "number",
286
+ String: "string",
287
+ Boolean: "boolean",
288
+ ID: "string",
289
+ };
290
+ export const codegen = (schema, files, { enums = "literals", scalars, includeTypenames = true, exports = [], typesFile, } = {}) => {
291
+ const schemaDoc = parse(typeof schema === "string" ? schema : printSchema(schema));
292
+ scalars = { ...defaultScalars, ...scalars };
283
293
  const types = {};
284
294
  const inputs = {};
285
295
  const fragments = {};
@@ -290,16 +300,36 @@ export const codegen = (schema, files, { useEnums = true, scalars = {}, includeT
290
300
  let query = {};
291
301
  let mutation = {};
292
302
  let subscription = {};
303
+ let queryKey = "Query";
304
+ let mutationKey = "Mutation";
305
+ let subscriptionKey = "Subscription";
293
306
  for (const definition of schemaDoc.definitions) {
294
307
  switch (definition.kind) {
308
+ case "SchemaDefinition":
309
+ {
310
+ for (const operationType of definition.operationTypes) {
311
+ switch (operationType.operation) {
312
+ case "query":
313
+ queryKey = operationType.type.name.value;
314
+ break;
315
+ case "mutation":
316
+ mutationKey = operationType.type.name.value;
317
+ break;
318
+ case "subscription":
319
+ subscriptionKey = operationType.type.name.value;
320
+ break;
321
+ }
322
+ }
323
+ }
324
+ break;
295
325
  case "ObjectTypeDefinition":
296
- if (definition.name.value === "Query") {
326
+ if (definition.name.value === queryKey) {
297
327
  query = Object.fromEntries(definition.fields?.map((f) => [f.name.value, f]) ?? []);
298
328
  }
299
- else if (definition.name.value === "Mutation") {
329
+ else if (definition.name.value === mutationKey) {
300
330
  mutation = Object.fromEntries(definition.fields?.map((f) => [f.name.value, f]) ?? []);
301
331
  }
302
- else if (definition.name.value === "Subscription") {
332
+ else if (definition.name.value === subscriptionKey) {
303
333
  subscription = Object.fromEntries(definition.fields?.map((f) => [f.name.value, f]) ?? []);
304
334
  }
305
335
  else
@@ -375,7 +405,7 @@ export const codegen = (schema, files, { useEnums = true, scalars = {}, includeT
375
405
  }
376
406
  }
377
407
  const operationDataName = (name, type) => {
378
- if (inputs[name] || types[name]) {
408
+ if (inputs[name] || types[name] || typesFile) {
379
409
  return `${name}${type[0].toUpperCase()}${type.slice(1)}`;
380
410
  }
381
411
  for (const key in operations) {
@@ -387,6 +417,7 @@ export const codegen = (schema, files, { useEnums = true, scalars = {}, includeT
387
417
  return name;
388
418
  };
389
419
  const handledInputs = new Set();
420
+ const filterOutputTypes = typesFile ? drop : Boolean;
390
421
  const serializedTypes = Object.entries(operations).filter(([, v]) => v.length)
391
422
  .flatMap(([operationType, collection]) => [
392
423
  ...collection.flatMap((c) => c.definition.variableDefinitions?.map((v) => {
@@ -434,17 +465,19 @@ export const codegen = (schema, files, { useEnums = true, scalars = {}, includeT
434
465
  handledInputs.add(current);
435
466
  }
436
467
  // return `type ${type.value} = ${serializeType(inputType)};`;
437
- }).filter(Boolean)),
468
+ }).filter(Boolean)).filter(filterOutputTypes),
438
469
  ...collection.flatMap((o) => {
439
470
  const name = operationDataName(o.name, operationType);
471
+ const resolvedOperationType = getOperationType(o.definition, types, fragments, roots[o.definition.operation], references, includeTypenames);
440
472
  const arr = [
441
- `${exports.includes("operations") ? "export " : ""}type ${name} = ${serializeType(getOperationType(o.definition, types, fragments, roots[o.definition.operation], references, includeTypenames), false)};`,
473
+ `${exports.includes("operations") ? "export " : ""}type ${name} = ${serializeType(resolvedOperationType, false, undefined)};`,
442
474
  ];
443
475
  if (o.definition.variableDefinitions?.length) {
444
476
  arr.push(`${exports.includes("operations") ? "export " : ""}type ${name}Variables = ${serializeType(getOperationVariables(o.definition, references), true)};`);
445
477
  }
446
478
  return arr;
447
- }),
479
+ }).filter(filterOutputTypes),
480
+ ,
448
481
  `export type ${operationNames[operationType].types} = {
449
482
  ${collection.map((o) => {
450
483
  const name = operationDataName(o.name, operationType);
@@ -455,16 +488,16 @@ ${collection.map((o) => {
455
488
  };
456
489
 
457
490
  export const ${operationNames[operationType].list} = {
458
- ${collection.map((o) => ` ${o.name}: "${o.path}",`).join("\n")}
491
+ ${collection.map((o) => ` ${o.name}: "${relative(process.cwd(), o.path)}",`).join("\n")}
459
492
  };`,
460
493
  ]);
461
494
  if (handledInputs.size) {
462
- serializedTypes.unshift(...Array.from(handledInputs).map((i) => {
495
+ serializedTypes.unshift(...(Array.from(handledInputs)).map((i) => {
463
496
  const def = inputs[i]?.[0];
464
497
  if (!def)
465
498
  throw new Error(`Could not find input '${i}'`);
466
499
  return `${exports.includes("types") ? "export " : ""}type ${i} = ${serializeType(serializeInput(def.fields ?? [], false, {}, references), true)};`;
467
- }), `export type Inputs = {
500
+ }).filter(filterOutputTypes), `export type Inputs = {
468
501
  ${Array.from(handledInputs).map((i) => ` ${i}: ${i};`).join("\n")}
469
502
  };`, `export const inputs = [${Array.from(handledInputs).map((i) => `"${i}"`).join(", ")}] as const;`);
470
503
  }
@@ -474,7 +507,7 @@ ${Array.from(handledInputs).map((i) => ` ${i}: ${i};`).join("\n")}
474
507
  if (usedTypes.length) {
475
508
  serializedTypes.unshift(...usedTypes.map(([name, type, usage]) => `${exports.includes("types") ? "export " : ""}type ${name} = {
476
509
  ${includeTypenames ? ` __typename: "${name}";\n` : ""}${type.fields?.filter((f) => usage.has(f.name.value)).map((v) => ` ${v.name.value}: ${serializeType(simpleType(v.type, types))};`).join("\n")}
477
- };`), `export type Types = {
510
+ };`).filter(filterOutputTypes), `export type Types = {
478
511
  ${usedTypes.map(([name]) => ` ${name}: ${name};`).join("\n")}
479
512
  };`, `export const types = [${usedTypes.map(([name]) => `"${name}"`).join(", ")}] as const;`);
480
513
  }
@@ -485,15 +518,36 @@ ${usedTypes.map(([name]) => ` ${name}: ${name};`).join("\n")}
485
518
  return `${exports.includes("types") ? "export " : ""}type ${r.name.value} = ${scalars[r.name.value] ??
486
519
  raise(`Could not find scalar '${r.name.value}'`)};`;
487
520
  }
488
- if (useEnums) {
521
+ if (enums === "enums") {
489
522
  return `${exports.includes("types") ? "export " : ""}enum ${r.name.value} {
490
523
  ${r.values?.map((r) => r.name.value).join(",\n ")},
491
524
  }`;
492
525
  }
493
- else {
526
+ else if (enums === "literals") {
494
527
  return `${exports.includes("types") ? "export " : ""}type ${r.name.value} = ${r.values?.map((r) => `"${r.name.value}"`).join(" | ")};`;
495
528
  }
496
- }));
529
+ }).filter(filterOutputTypes));
530
+ if (typesFile) {
531
+ const operationsImports = (operations) => operations.flatMap((o) => {
532
+ const prefix = operationDataName(o.name, o.definition.operation);
533
+ return o.definition.variableDefinitions?.length
534
+ ? [`${prefix}`, `${prefix}Variables`]
535
+ : `${prefix}`;
536
+ });
537
+ const imports = [
538
+ ...operationsImports(operations.query),
539
+ ...operationsImports(operations.mutation),
540
+ ...operationsImports(operations.subscription),
541
+ ...usedTypes.map((u) => u[0]),
542
+ ...Array.from(handledInputs),
543
+ ];
544
+ serializedTypes.unshift(`import {\n ${imports.sort().join(",\n ")},
545
+ } from "${typesFile}";`);
546
+ }
547
+ else if (enums.startsWith("import:") &&
548
+ usedReferences.some((r) => r.kind === Kind.ENUM_TYPE_DEFINITION)) {
549
+ serializedTypes.unshift(`import { ${usedReferences.filter((r) => r.kind === Kind.ENUM_TYPE_DEFINITION).map((r) => r.name.value).join(", ")} } from "${enums.slice(7)}";`);
550
+ }
497
551
  return serializedTypes.join("\n\n") + "\n";
498
552
  };
499
553
  export const loadFiles = async (schemaPath, operationDirs) => {
package/esm/init.js CHANGED
@@ -1,10 +1,35 @@
1
1
  import { readFileSync } from "node:fs";
2
- import { parse } from "graphql";
2
+ import { Kind, parse } from "graphql";
3
3
  import { dirname, join } from "node:path";
4
+ import { gqlPluckFromCodeStringSync } from "@graphql-tools/graphql-tag-pluck";
4
5
  import { operation, proxy, withGetDefaultPatch } from "./proxy.js";
5
6
  import { toObject } from "./util.js";
6
7
  const files = {};
7
- const loadFile = (path) => files[path] = readFileSync(path, "utf-8").replace(/#import "(.*)"/, (_, fragmentPath) => loadFile(join(dirname(path), fragmentPath)));
8
+ const loadFile = (path) => files[path] || (files[path] = readFileSync(path, "utf-8").replace(/#import "(.*)"/, (_, fragmentPath) => loadFile(join(dirname(path), fragmentPath))));
9
+ const getOperationContentMap = {};
10
+ const getOperationContent = (path, operationName) => {
11
+ const existing = getOperationContentMap[path];
12
+ if (existing) {
13
+ if (existing.kind === Kind.DOCUMENT)
14
+ return existing;
15
+ return getOperationContentMap[path][operationName];
16
+ }
17
+ const fileContent = readFileSync(path, "utf-8").replace(/#import "(.*)"/, (_, fragmentPath) => loadFile(join(dirname(path), fragmentPath)));
18
+ try {
19
+ const sources = gqlPluckFromCodeStringSync(path, fileContent);
20
+ getOperationContentMap[path] = Object.fromEntries(sources.map((s) => {
21
+ const document = parse(s);
22
+ const firstOp = document.definitions.find((d) => d.kind === Kind.OPERATION_DEFINITION);
23
+ if (!firstOp)
24
+ throw new Error(`Cound not find an operation in ${path}`);
25
+ return [firstOp.name?.value, document];
26
+ }));
27
+ }
28
+ catch {
29
+ getOperationContentMap[path] = parse(fileContent);
30
+ }
31
+ return getOperationContent(path, operationName);
32
+ };
8
33
  // - Types will be suffixed with their type: fooQuery or fooMutation
9
34
  export const init = (schema, queries, mutations, subscriptions, types, inputs, scalars, options) => (fn) => {
10
35
  const doc = parse(schema);
@@ -81,7 +106,7 @@ export const init = (schema, queries, mutations, subscriptions, types, inputs, s
81
106
  return obj;
82
107
  };
83
108
  const operationBuilder = (name, path) => addOperationTransforms(name, (...patches) => {
84
- const query = files[path] ?? loadFile(path);
109
+ const query = getOperationContent(path, name);
85
110
  if (transforms[name] && "default" in transforms[name]) {
86
111
  patches = [transforms[name].default, ...patches];
87
112
  }
package/esm/plugin.cjs ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ module.exports = {
3
+ async plugin(schema, documents, config) {
4
+ const { codegen } = await import("./codegen.js");
5
+ return (config.banner ?? "") + codegen(schema, documents.map((d) => ({ path: d.location, content: d.rawSDL })), config);
6
+ },
7
+ };
package/esm/proxy.js CHANGED
@@ -113,7 +113,9 @@ const resolveConcreteType = (definitions, definition, patch, prev) => {
113
113
  if (options.length === 1)
114
114
  return options[0];
115
115
  for (const field in patch) {
116
- options = options.filter((o) => o.fields?.some((f) => f.name.value === field));
116
+ options = options.filter((o) => field === "__typename"
117
+ ? o.name.value === patch[field]
118
+ : o.fields?.some((f) => f.name.value === field));
117
119
  if (options.length === 1)
118
120
  return options[0];
119
121
  }
@@ -560,7 +562,7 @@ const constToValue = (value) => {
560
562
  }
561
563
  };
562
564
  export const operation = (definitions, scalars, query, ...patches) => {
563
- const document = parse(query);
565
+ const document = typeof query === "string" ? parse(query) : query;
564
566
  const operations = document.definitions.filter((d) => d.kind === Kind.OPERATION_DEFINITION);
565
567
  if (operations.length !== 1) {
566
568
  throw new Error(`Expected 1 operation, got ${operations.length}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphql-data-generator",
3
- "version": "0.1.7",
3
+ "version": "0.2.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/vocesgraphql-data-generator/.git"
@@ -14,10 +14,16 @@
14
14
  "types": "./types/index.d.ts",
15
15
  "exports": {
16
16
  ".": {
17
- "import": {
18
- "types": "./types/index.d.ts",
19
- "default": "./esm/index.js"
20
- }
17
+ "import": "./esm/index.js",
18
+ "require": "./esm/index.js",
19
+ "types": "./types/index.d.ts",
20
+ "default": "./esm/index.js"
21
+ },
22
+ "./plugin": {
23
+ "import": "./esm/plugin.cjs",
24
+ "require": "./esm/plugin.cjs",
25
+ "types": "./types/plugin.cjs",
26
+ "default": "./esm/plugin.cjs"
21
27
  }
22
28
  },
23
29
  "bin": {
@@ -32,6 +38,7 @@
32
38
  },
33
39
  "_generatedBy": "dnt@dev",
34
40
  "peerDependencies": {
35
- "graphql": "*"
41
+ "graphql": "*",
42
+ "@graphql-tools/graphql-tag-pluck": "*"
36
43
  }
37
44
  }
@@ -1,11 +1,13 @@
1
- export declare const codegen: (schema: string, files: {
1
+ import type { GraphQLSchema } from "graphql";
2
+ export declare const codegen: (schema: GraphQLSchema | string, files: {
2
3
  path: string;
3
4
  content: string;
4
- }[], { useEnums, scalars, includeTypenames, exports, }?: {
5
- useEnums?: boolean;
5
+ }[], { enums, scalars, includeTypenames, exports, typesFile, }?: {
6
+ enums?: string;
6
7
  scalars?: Record<string, string | undefined>;
7
8
  includeTypenames?: boolean;
8
9
  exports?: ("types" | "operations")[];
10
+ typesFile?: string;
9
11
  }) => string;
10
12
  export declare const loadFiles: (schemaPath: string, operationDirs: string[]) => Promise<[schema: string, operations: {
11
13
  path: string;
@@ -0,0 +1 @@
1
+ export function plugin(schema: any, documents: any, config: any): Promise<string>;
package/types/proxy.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import type { DefinitionNode, GraphQLError } from "graphql";
1
+ import type { DefinitionNode, DocumentNode, GraphQLError } from "graphql";
2
2
  import type { OperationMock, Patch, SimpleOperationMock } from "./types.js";
3
3
  export declare const withGetDefaultPatch: <T>(newGetDefaultPatch: <U>(__typename: string) => Patch<U> | ((prev: U) => Patch<U> | undefined) | undefined, fn: () => T) => T;
4
4
  export declare const proxy: <T>(definitions: readonly DefinitionNode[], scalars: Record<string, unknown | ((typename: string) => unknown)>, type: string, ...patches: (Patch<T> | ((prev: T) => Patch<T>))[]) => T;
5
- export declare const operation: <O extends SimpleOperationMock, Extra = object>(definitions: readonly DefinitionNode[], scalars: Record<string, unknown | ((typename: string) => unknown)>, query: string, ...patches: (Patch<Omit<O, "error" | "errors">> & {
5
+ export declare const operation: <O extends SimpleOperationMock, Extra = object>(definitions: readonly DefinitionNode[], scalars: Record<string, unknown | ((typename: string) => unknown)>, query: string | DocumentNode, ...patches: (Patch<Omit<O, "error" | "errors">> & {
6
6
  error?: Error;
7
7
  errors?: GraphQLError[];
8
8
  } & Partial<Extra>)[]) => OperationMock<O["data"], O["variables"]> & Partial<Extra>;