graphql-jerky 1.0.1 → 1.0.3

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.
@@ -12,6 +12,7 @@ import 'just-pascal-case';
12
12
  import 'url';
13
13
  import 'typescript';
14
14
  import 'glob';
15
+ import 'picomatch';
15
16
  import 'just-zip-it';
16
17
 
17
18
  program
package/build/lib.cjs CHANGED
@@ -10,6 +10,7 @@ var url = require('url');
10
10
  var fs = require('fs/promises');
11
11
  var ts = require('typescript');
12
12
  var glob = require('glob');
13
+ var pm = require('picomatch');
13
14
  var zip = require('just-zip-it');
14
15
 
15
16
  async function parseSource(filepath) {
@@ -38,23 +39,26 @@ async function findExports(source, resolveModuleFn = resolveModule) {
38
39
  if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
39
40
  if (node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {
40
41
  if (node.modifiers.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword)) {
41
- exports.default = { declaration: node, source, name: "default" };
42
+ (exports.default || (exports.default = [])).push({ declaration: node, source, name: "default" });
42
43
  }
43
44
  else if (node.name) {
44
- exports[node.name.getText(source)] = { declaration: node, source, name: node.name.getText(source) };
45
+ const name = node.name.getText(source);
46
+ (exports[name] || (exports[name] = [])).push({ declaration: node, source, name: name });
45
47
  }
46
48
  }
47
49
  }
48
50
  else if (ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node) || ts.isModuleDeclaration(node)) {
49
51
  if (node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {
50
- exports[node.name.getText(source)] = { declaration: node, source, name: node.name.getText(source) };
52
+ const name = node.name.getText(source);
53
+ (exports[name] || (exports[name] = [])).push({ declaration: node, source, name: name });
51
54
  }
52
55
  }
53
56
  else if (ts.isVariableStatement(node)) {
54
57
  if (node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {
55
58
  for (const decl of node.declarationList.declarations) {
56
59
  if (decl.name) {
57
- exports[decl.name.getText(source)] = { declaration: decl, source, name: decl.name.getText(source) };
60
+ const name = decl.name.getText(source);
61
+ (exports[name] || (exports[name] = [])).push({ declaration: decl, source, name: name });
58
62
  }
59
63
  }
60
64
  }
@@ -72,7 +76,7 @@ async function findExports(source, resolveModuleFn = resolveModule) {
72
76
  if (!(name in moduleExports)) {
73
77
  throw new Error(`Exported name ${name} not found in ${JSON.stringify(specifier)}`);
74
78
  }
75
- exports[name] = moduleExports[name];
79
+ (exports[name] || (exports[name] = [])).push(...moduleExports[name]);
76
80
  }
77
81
  }
78
82
  else {
@@ -81,7 +85,6 @@ async function findExports(source, resolveModuleFn = resolveModule) {
81
85
  name: element.name.text,
82
86
  property: element.propertyName?.text,
83
87
  });
84
- // exports[element.name.getText(source)] = { declaration: element, source };
85
88
  }
86
89
  }
87
90
  }
@@ -97,57 +100,63 @@ async function findExports(source, resolveModuleFn = resolveModule) {
97
100
  if (ts.isVariableStatement(statement)) {
98
101
  for (const decl of statement.declarationList.declarations) {
99
102
  if (ts.isIdentifier(decl.name) && decl.name.getText(source) in names) {
100
- exports[names[decl.name.getText(source)]] = {
103
+ const name = names[decl.name.getText(source)];
104
+ (exports[name] || (exports[name] = [])).push({
101
105
  declaration: decl,
102
106
  source,
103
107
  name: names[decl.name.getText(source)],
104
- };
108
+ });
105
109
  }
106
110
  }
107
111
  }
108
112
  if (ts.isFunctionDeclaration(statement)) {
109
113
  if (statement.name && ts.isIdentifier(statement.name) && statement.name.getText(source) in names) {
110
- exports[names[statement.name.getText(source)]] = {
114
+ const name = names[statement.name.getText(source)];
115
+ (exports[name] || (exports[name] = [])).push({
111
116
  declaration: statement,
112
117
  source,
113
118
  name: names[statement.name.getText(source)],
114
- };
119
+ });
115
120
  }
116
121
  }
117
122
  if (ts.isClassDeclaration(statement)) {
118
123
  if (statement.name && ts.isIdentifier(statement.name) && statement.name.getText(source) in names) {
119
- exports[names[statement.name.getText(source)]] = {
124
+ const name = names[statement.name.getText(source)];
125
+ (exports[name] || (exports[name] = [])).push({
120
126
  declaration: statement,
121
127
  source,
122
128
  name: names[statement.name.getText(source)],
123
- };
129
+ });
124
130
  }
125
131
  }
126
132
  if (ts.isTypeAliasDeclaration(statement)) {
127
133
  if (statement.name && ts.isIdentifier(statement.name) && statement.name.getText(source) in names) {
128
- exports[names[statement.name.getText(source)]] = {
134
+ const name = names[statement.name.getText(source)];
135
+ (exports[name] || (exports[name] = [])).push({
129
136
  declaration: statement,
130
137
  source,
131
138
  name: names[statement.name.getText(source)],
132
- };
139
+ });
133
140
  }
134
141
  }
135
142
  if (ts.isInterfaceDeclaration(statement)) {
136
143
  if (statement.name && ts.isIdentifier(statement.name) && statement.name.getText(source) in names) {
137
- exports[names[statement.name.getText(source)]] = {
144
+ const name = names[statement.name.getText(source)];
145
+ (exports[name] || (exports[name] = [])).push({
138
146
  declaration: statement,
139
147
  source,
140
148
  name: names[statement.name.getText(source)],
141
- };
149
+ });
142
150
  }
143
151
  }
144
152
  if (ts.isEnumDeclaration(statement)) {
145
153
  if (statement.name && ts.isIdentifier(statement.name) && statement.name.getText(source) in names) {
146
- exports[names[statement.name.getText(source)]] = {
154
+ const name = names[statement.name.getText(source)];
155
+ (exports[name] || (exports[name] = [])).push({
147
156
  declaration: statement,
148
157
  source,
149
158
  name: names[statement.name.getText(source)],
150
- };
159
+ });
151
160
  }
152
161
  }
153
162
  if (ts.isImportDeclaration(statement)) {
@@ -167,11 +176,12 @@ async function findExports(source, resolveModuleFn = resolveModule) {
167
176
  if (!importExports[element.name.getText(source)]) {
168
177
  throw new Error(`Exported name ${element.name.getText(source)} not found in ${JSON.stringify(statement.moduleSpecifier.getText(source))}`);
169
178
  }
170
- exports[names[element.propertyName.getText(source)]] = {
179
+ const name = names[element.propertyName.getText(source)];
180
+ (exports[name] || (exports[name] = [])).push({
171
181
  declaration: element,
172
182
  source,
173
183
  name: names[element.propertyName.getText(source)],
174
- };
184
+ });
175
185
  }
176
186
  }
177
187
  }
@@ -238,16 +248,9 @@ function compareEntryKey([a], [b]) {
238
248
  function compareName({ name: a }, { name: b }) {
239
249
  return a.localeCompare(b);
240
250
  }
251
+ const PRIMITIVE_TYPES = new Set(["ID", "Int", "Float", "String", "Boolean"]);
241
252
  function isPrimitiveType(type) {
242
- switch (type.name) {
243
- case "ID":
244
- case "Int":
245
- case "Float":
246
- case "String":
247
- case "Boolean":
248
- return true;
249
- }
250
- return false;
253
+ return PRIMITIVE_TYPES.has(type.name);
251
254
  }
252
255
  function importAlias(line) {
253
256
  const pos = line.indexOf(" as ");
@@ -419,7 +422,7 @@ class GeneratorContext {
419
422
  // NOTE: Resolver functions on interface definitions are not used by the default graphql-js
420
423
  // executor implementation, but nonetheless accessible by a potential alternate implementation.
421
424
  resolveFn && jen.prop("resolve", jen.parens(resolveFn).as.any),
422
- subscriber && jen.prop("subscribe", renderSymbolReference(this._pkgs, subscriber)),
425
+ subscriber && jen.prop("subscribe", jen.parens(renderSymbolReference(this._pkgs, subscriber)).as.any),
423
426
  ]));
424
427
  }
425
428
  _renderFieldsThunk(type, inputFieldDirectives, noresolver) {
@@ -501,7 +504,7 @@ class GeneratorContext {
501
504
  }
502
505
  _renderGraphQLObjectTypeDefinition(type, { inputFieldDirectives }) {
503
506
  const typeInterfaces = type.getInterfaces();
504
- return jen.new.add(this._gql("GraphQLObjectType")).call(jen.obj(jen.prop("name", jen.lit(type.name)), jen.prop("fields", this._renderFieldsThunk(type, inputFieldDirectives)), ...truthify([
507
+ return jen.new.add(this._gql("GraphQLObjectType")).call(jen.obj(jen.prop("name", jen.lit(type.name)), jen.prop("fields", this._renderFieldsThunk(type, inputFieldDirectives, type.name === "Subscription")), ...truthify([
505
508
  type.description && jen.prop("description", jen.lit(type.description)),
506
509
  typeInterfaces.length && jen.prop("interfaces", jen.arrow().arr(
507
510
  // do not sort interfaces, the declaration order may be important
@@ -741,6 +744,20 @@ class GeneratorContext {
741
744
  .map((directive) => [directive.name, this._renderGraphQLDirectiveDefinition(directive)])
742
745
  .sort(compareEntryKey),
743
746
  ].map(([, expr]) => expr),
747
+ ...Array.from(PRIMITIVE_TYPES)
748
+ .flatMap((type) => {
749
+ const info = this._scalarsInfo[type];
750
+ if (!info) {
751
+ return [];
752
+ }
753
+ return [
754
+ jen.statements(...[
755
+ info.serializeFunc && this._gql("GraphQL" + type).dot("serialize").op("=").parens(renderSymbolReference(this._pkgs, info.serializeFunc).as.any),
756
+ info.parseValueFunc && this._gql("GraphQL" + type).dot("parseValue").op("=").parens(renderSymbolReference(this._pkgs, info.parseValueFunc).as.any),
757
+ info.parseLiteralFunc && this._gql("GraphQL" + type).dot("parseLiteral").op("=").parens(renderSymbolReference(this._pkgs, info.parseLiteralFunc).as.any),
758
+ ].filter(Boolean))
759
+ ];
760
+ }),
744
761
  // render schema
745
762
  jen.const.id("schema").op("=").new.add(this._gql("GraphQLSchema")).call(jen.obj(...(schema.getQueryType()
746
763
  ? [jen.prop("query", this._renderGraphQLTypeIdentifier(schema.getQueryType()))]
@@ -880,9 +897,44 @@ function generate(schema, outfile, options = {}) {
880
897
  return createGenerator(schema, options).generate(outfile);
881
898
  }
882
899
 
900
+ function createMatcher(pattern) {
901
+ if (!pattern) {
902
+ return () => false;
903
+ }
904
+ if (typeof pattern === "function") {
905
+ return pattern;
906
+ }
907
+ if (pattern instanceof RegExp) {
908
+ return (s) => pattern.test(s);
909
+ }
910
+ if (typeof pattern === "string") {
911
+ return pm(pattern);
912
+ }
913
+ let matchers = [];
914
+ let globs = [];
915
+ for (const p of Array.isArray(pattern) ? pattern : [pattern]) {
916
+ if (typeof p === "string") {
917
+ globs.push(p);
918
+ }
919
+ else if (typeof p === "function") {
920
+ matchers.push(p);
921
+ }
922
+ else if (p instanceof RegExp) {
923
+ matchers.push(RegExp.prototype.test.bind(p));
924
+ }
925
+ else {
926
+ throw new TypeError(`Invalid pattern: ${p}`);
927
+ }
928
+ }
929
+ if (globs.length) {
930
+ matchers.push(pm(globs));
931
+ }
932
+ return (s) => matchers.some((m) => m(s));
933
+ }
934
+
883
935
  async function parseResolverFile(path) {
884
936
  return Object.fromEntries(Object.entries((await parseModule(path)).exports)
885
- .flatMap(([name, { declaration }]) => {
937
+ .flatMap(([name, decls]) => decls.flatMap(({ declaration }) => {
886
938
  if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
887
939
  if (ts.isFunctionExpression(declaration.initializer)) {
888
940
  return [[name, { module: path, symbol: name }]];
@@ -895,13 +947,14 @@ async function parseResolverFile(path) {
895
947
  return [[name, { module: path, symbol: name }]];
896
948
  }
897
949
  return [];
898
- }));
950
+ })));
899
951
  }
900
- async function parseTypeResolvers(typeName, glob$1) {
952
+ async function parseTypeResolvers(typeName, glob$1, ignore) {
953
+ const shouldIgnore = createMatcher(ignore);
901
954
  const result = {};
902
955
  for await (const entry of glob.globIterate(glob$1, { withFileTypes: true })) {
903
956
  const fullpath = entry.fullpath();
904
- if (entry.isFile() && entry.name.endsWith(".ts")) {
957
+ if (!shouldIgnore(fullpath) && entry.isFile() && entry.name.endsWith(".ts")) {
905
958
  for (const [name, reference] of Object.entries(await parseResolverFile(fullpath))) {
906
959
  let realName = name;
907
960
  if (realName === "default") {
@@ -916,7 +969,7 @@ async function parseTypeResolvers(typeName, glob$1) {
916
969
  }
917
970
  return result;
918
971
  }
919
- async function parse$2(dirpath) {
972
+ async function parse$2(dirpath, ignore) {
920
973
  const dir = await fs.stat(dirpath);
921
974
  if (!dir.isDirectory()) {
922
975
  throw new Error(`Expected ${dirpath} to be a directory`);
@@ -925,14 +978,15 @@ async function parse$2(dirpath) {
925
978
  dirpath += "/";
926
979
  }
927
980
  const out = {};
981
+ const shouldIgnore = createMatcher(ignore);
928
982
  for await (const name of await fs.readdir(dirpath)) {
929
983
  const file = path.join(dirpath, name);
930
984
  const entry = await fs.stat(file);
931
985
  if (entry.isDirectory()) {
932
- out[name] = await parseTypeResolvers(name, file + "/*.ts");
986
+ out[name] = await parseTypeResolvers(name, file + "/*.ts", shouldIgnore);
933
987
  }
934
988
  else if (entry.isFile() && name.endsWith(".ts")) {
935
- out[name.slice(0, -3)] = await parseTypeResolvers(name.slice(0, -3), file);
989
+ out[name.slice(0, -3)] = await parseTypeResolvers(name.slice(0, -3), file, shouldIgnore);
936
990
  }
937
991
  }
938
992
  return out;
@@ -968,6 +1022,12 @@ function refFromTypeReferenceNode(typeNode, source) {
968
1022
  return;
969
1023
  }
970
1024
  const typeName = typeNode.typeName.text;
1025
+ if (typeNode.typeArguments?.length) {
1026
+ if (typeName !== 'Promise') {
1027
+ throw new Error('TODO: handle type arguments');
1028
+ }
1029
+ return refFromTypeNode(typeNode.typeArguments[0], source);
1030
+ }
971
1031
  for (const statement of source.statements) {
972
1032
  if (ts.isImportDeclaration(statement)) {
973
1033
  // search imports
@@ -1107,21 +1167,23 @@ async function parse$1(path) {
1107
1167
  const scalarFnsInfos = {};
1108
1168
  const scalarTypeNodes = {};
1109
1169
  const sources = {};
1110
- for (const [name, { declaration, source }] of Object.entries((await parseModule(path)).exports)) {
1111
- sources[name] = source;
1112
- if (ts.isVariableDeclaration(declaration)) {
1113
- if (declaration.initializer && ts.isObjectLiteralExpression(declaration.initializer)) {
1114
- scalarFnsInfos[name] = scalarInfoFromObjectLiteralExpression(name, new URL(source.fileName).toString(), declaration.initializer.properties);
1115
- }
1116
- }
1117
- else if (ts.isTypeAliasDeclaration(declaration)) {
1118
- if (declaration.typeParameters?.length) {
1119
- console.log(`WARNING: skipping scalar type ${name}: type parameters are not supported`);
1120
- continue;
1170
+ for (const [name, decls] of Object.entries((await parseModule(path)).exports)) {
1171
+ for (const { declaration, source } of decls) {
1172
+ sources[name] = source;
1173
+ if (ts.isVariableDeclaration(declaration)) {
1174
+ if (declaration.initializer && ts.isObjectLiteralExpression(declaration.initializer)) {
1175
+ scalarFnsInfos[name] = scalarInfoFromObjectLiteralExpression(name, new URL(source.fileName).toString(), declaration.initializer.properties);
1176
+ }
1121
1177
  }
1122
- const ref = refFromTypeNode(declaration.type, source);
1123
- if (ref) {
1124
- scalarTypeNodes[name] = declaration.type;
1178
+ else if (ts.isTypeAliasDeclaration(declaration)) {
1179
+ if (declaration.typeParameters?.length) {
1180
+ console.log(`WARNING: skipping scalar type ${name}: type parameters are not supported`);
1181
+ continue;
1182
+ }
1183
+ const ref = refFromTypeNode(declaration.type, source);
1184
+ if (ref) {
1185
+ scalarTypeNodes[name] = declaration.type;
1186
+ }
1125
1187
  }
1126
1188
  }
1127
1189
  }
@@ -1134,7 +1196,7 @@ async function parse$1(path) {
1134
1196
  module: source.fileName,
1135
1197
  symbol: name,
1136
1198
  } : reconcileScalarType(name, [
1137
- fnsInfo?.serializeFunc?.declaration?.parameters?.[0]?.type,
1199
+ // NOTE: omit serialize input type because it is supposed to be `unknown`
1138
1200
  fnsInfo?.parseValueFunc?.declaration?.type,
1139
1201
  fnsInfo?.parseLiteralFunc?.declaration?.type,
1140
1202
  ], source);
@@ -1517,9 +1579,13 @@ async function* findSchemaFiles(glob$1) {
1517
1579
  }
1518
1580
  }
1519
1581
  }
1520
- async function parse(glob) {
1582
+ async function parse(glob, ignore) {
1583
+ const shouldIgnore = createMatcher(ignore);
1521
1584
  const documentNodes = [];
1522
1585
  for await (const entry of findSchemaFiles(glob)) {
1586
+ if (shouldIgnore(entry)) {
1587
+ continue;
1588
+ }
1523
1589
  documentNodes.push(graphql.parse(new graphql.Source(await fs.readFile(entry, "utf-8"), entry)));
1524
1590
  }
1525
1591
  if (!documentNodes.length) {
package/build/lib.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import ts from 'typescript';
2
2
  import { GraphQLSchema } from 'graphql';
3
3
 
4
+ type MatchPattern = string | RegExp | ((s: string) => boolean) | Array<string | RegExp | ((s: string) => boolean)>;
5
+
4
6
  interface SymbolReference {
5
7
  module?: string;
6
8
  symbol: string;
@@ -8,8 +10,8 @@ interface SymbolReference {
8
10
  property?: string;
9
11
  }
10
12
 
11
- declare function parseTypeResolvers(typeName: string, glob: string | string[]): Promise<Record<string, SymbolReference>>;
12
- declare function parse$2(dirpath: string): Promise<Record<string, Record<string, SymbolReference>>>;
13
+ declare function parseTypeResolvers(typeName: string, glob: string | string[], ignore?: MatchPattern): Promise<Record<string, SymbolReference>>;
14
+ declare function parse$2(dirpath: string, ignore?: MatchPattern): Promise<Record<string, Record<string, SymbolReference>>>;
13
15
 
14
16
  type FunctionDeclaration = ts.MethodDeclaration | ts.FunctionExpression | ts.ArrowFunction;
15
17
  interface ScalarFnsInfo {
@@ -46,6 +48,6 @@ declare function generateSchema(schema: GraphQLSchema, outfile: string, options?
46
48
  declare function generateTypes(schema: GraphQLSchema, outfile: string, options?: GenerateOptions): string;
47
49
  declare function generate(schema: GraphQLSchema, outfile: string, options?: GenerateOptions): string;
48
50
 
49
- declare function parse(glob: string | string[]): Promise<GraphQLSchema>;
51
+ declare function parse(glob: string | string[], ignore?: MatchPattern): Promise<GraphQLSchema>;
50
52
 
51
53
  export { createGenerator, generate, generateSchema, generateTypes, parse$2 as parseResolvers, parse$1 as parseScalars, parse as parseSchema, parseTypeResolvers };
package/build/lib.mjs CHANGED
@@ -8,6 +8,7 @@ import { pathToFileURL, fileURLToPath } from 'url';
8
8
  import fs from 'fs/promises';
9
9
  import ts, { getLineAndCharacterOfPosition } from 'typescript';
10
10
  import { globIterate } from 'glob';
11
+ import pm from 'picomatch';
11
12
  import zip from 'just-zip-it';
12
13
 
13
14
  async function parseSource(filepath) {
@@ -36,23 +37,26 @@ async function findExports(source, resolveModuleFn = resolveModule) {
36
37
  if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
37
38
  if (node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {
38
39
  if (node.modifiers.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword)) {
39
- exports.default = { declaration: node, source, name: "default" };
40
+ (exports.default || (exports.default = [])).push({ declaration: node, source, name: "default" });
40
41
  }
41
42
  else if (node.name) {
42
- exports[node.name.getText(source)] = { declaration: node, source, name: node.name.getText(source) };
43
+ const name = node.name.getText(source);
44
+ (exports[name] || (exports[name] = [])).push({ declaration: node, source, name: name });
43
45
  }
44
46
  }
45
47
  }
46
48
  else if (ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node) || ts.isModuleDeclaration(node)) {
47
49
  if (node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {
48
- exports[node.name.getText(source)] = { declaration: node, source, name: node.name.getText(source) };
50
+ const name = node.name.getText(source);
51
+ (exports[name] || (exports[name] = [])).push({ declaration: node, source, name: name });
49
52
  }
50
53
  }
51
54
  else if (ts.isVariableStatement(node)) {
52
55
  if (node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {
53
56
  for (const decl of node.declarationList.declarations) {
54
57
  if (decl.name) {
55
- exports[decl.name.getText(source)] = { declaration: decl, source, name: decl.name.getText(source) };
58
+ const name = decl.name.getText(source);
59
+ (exports[name] || (exports[name] = [])).push({ declaration: decl, source, name: name });
56
60
  }
57
61
  }
58
62
  }
@@ -70,7 +74,7 @@ async function findExports(source, resolveModuleFn = resolveModule) {
70
74
  if (!(name in moduleExports)) {
71
75
  throw new Error(`Exported name ${name} not found in ${JSON.stringify(specifier)}`);
72
76
  }
73
- exports[name] = moduleExports[name];
77
+ (exports[name] || (exports[name] = [])).push(...moduleExports[name]);
74
78
  }
75
79
  }
76
80
  else {
@@ -79,7 +83,6 @@ async function findExports(source, resolveModuleFn = resolveModule) {
79
83
  name: element.name.text,
80
84
  property: element.propertyName?.text,
81
85
  });
82
- // exports[element.name.getText(source)] = { declaration: element, source };
83
86
  }
84
87
  }
85
88
  }
@@ -95,57 +98,63 @@ async function findExports(source, resolveModuleFn = resolveModule) {
95
98
  if (ts.isVariableStatement(statement)) {
96
99
  for (const decl of statement.declarationList.declarations) {
97
100
  if (ts.isIdentifier(decl.name) && decl.name.getText(source) in names) {
98
- exports[names[decl.name.getText(source)]] = {
101
+ const name = names[decl.name.getText(source)];
102
+ (exports[name] || (exports[name] = [])).push({
99
103
  declaration: decl,
100
104
  source,
101
105
  name: names[decl.name.getText(source)],
102
- };
106
+ });
103
107
  }
104
108
  }
105
109
  }
106
110
  if (ts.isFunctionDeclaration(statement)) {
107
111
  if (statement.name && ts.isIdentifier(statement.name) && statement.name.getText(source) in names) {
108
- exports[names[statement.name.getText(source)]] = {
112
+ const name = names[statement.name.getText(source)];
113
+ (exports[name] || (exports[name] = [])).push({
109
114
  declaration: statement,
110
115
  source,
111
116
  name: names[statement.name.getText(source)],
112
- };
117
+ });
113
118
  }
114
119
  }
115
120
  if (ts.isClassDeclaration(statement)) {
116
121
  if (statement.name && ts.isIdentifier(statement.name) && statement.name.getText(source) in names) {
117
- exports[names[statement.name.getText(source)]] = {
122
+ const name = names[statement.name.getText(source)];
123
+ (exports[name] || (exports[name] = [])).push({
118
124
  declaration: statement,
119
125
  source,
120
126
  name: names[statement.name.getText(source)],
121
- };
127
+ });
122
128
  }
123
129
  }
124
130
  if (ts.isTypeAliasDeclaration(statement)) {
125
131
  if (statement.name && ts.isIdentifier(statement.name) && statement.name.getText(source) in names) {
126
- exports[names[statement.name.getText(source)]] = {
132
+ const name = names[statement.name.getText(source)];
133
+ (exports[name] || (exports[name] = [])).push({
127
134
  declaration: statement,
128
135
  source,
129
136
  name: names[statement.name.getText(source)],
130
- };
137
+ });
131
138
  }
132
139
  }
133
140
  if (ts.isInterfaceDeclaration(statement)) {
134
141
  if (statement.name && ts.isIdentifier(statement.name) && statement.name.getText(source) in names) {
135
- exports[names[statement.name.getText(source)]] = {
142
+ const name = names[statement.name.getText(source)];
143
+ (exports[name] || (exports[name] = [])).push({
136
144
  declaration: statement,
137
145
  source,
138
146
  name: names[statement.name.getText(source)],
139
- };
147
+ });
140
148
  }
141
149
  }
142
150
  if (ts.isEnumDeclaration(statement)) {
143
151
  if (statement.name && ts.isIdentifier(statement.name) && statement.name.getText(source) in names) {
144
- exports[names[statement.name.getText(source)]] = {
152
+ const name = names[statement.name.getText(source)];
153
+ (exports[name] || (exports[name] = [])).push({
145
154
  declaration: statement,
146
155
  source,
147
156
  name: names[statement.name.getText(source)],
148
- };
157
+ });
149
158
  }
150
159
  }
151
160
  if (ts.isImportDeclaration(statement)) {
@@ -165,11 +174,12 @@ async function findExports(source, resolveModuleFn = resolveModule) {
165
174
  if (!importExports[element.name.getText(source)]) {
166
175
  throw new Error(`Exported name ${element.name.getText(source)} not found in ${JSON.stringify(statement.moduleSpecifier.getText(source))}`);
167
176
  }
168
- exports[names[element.propertyName.getText(source)]] = {
177
+ const name = names[element.propertyName.getText(source)];
178
+ (exports[name] || (exports[name] = [])).push({
169
179
  declaration: element,
170
180
  source,
171
181
  name: names[element.propertyName.getText(source)],
172
- };
182
+ });
173
183
  }
174
184
  }
175
185
  }
@@ -236,16 +246,9 @@ function compareEntryKey([a], [b]) {
236
246
  function compareName({ name: a }, { name: b }) {
237
247
  return a.localeCompare(b);
238
248
  }
249
+ const PRIMITIVE_TYPES = new Set(["ID", "Int", "Float", "String", "Boolean"]);
239
250
  function isPrimitiveType(type) {
240
- switch (type.name) {
241
- case "ID":
242
- case "Int":
243
- case "Float":
244
- case "String":
245
- case "Boolean":
246
- return true;
247
- }
248
- return false;
251
+ return PRIMITIVE_TYPES.has(type.name);
249
252
  }
250
253
  function importAlias(line) {
251
254
  const pos = line.indexOf(" as ");
@@ -417,7 +420,7 @@ class GeneratorContext {
417
420
  // NOTE: Resolver functions on interface definitions are not used by the default graphql-js
418
421
  // executor implementation, but nonetheless accessible by a potential alternate implementation.
419
422
  resolveFn && jen$1.prop("resolve", jen$1.parens(resolveFn).as.any),
420
- subscriber && jen$1.prop("subscribe", renderSymbolReference(this._pkgs, subscriber)),
423
+ subscriber && jen$1.prop("subscribe", jen$1.parens(renderSymbolReference(this._pkgs, subscriber)).as.any),
421
424
  ]));
422
425
  }
423
426
  _renderFieldsThunk(type, inputFieldDirectives, noresolver) {
@@ -499,7 +502,7 @@ class GeneratorContext {
499
502
  }
500
503
  _renderGraphQLObjectTypeDefinition(type, { inputFieldDirectives }) {
501
504
  const typeInterfaces = type.getInterfaces();
502
- return jen$1.new.add(this._gql("GraphQLObjectType")).call(jen$1.obj(jen$1.prop("name", jen$1.lit(type.name)), jen$1.prop("fields", this._renderFieldsThunk(type, inputFieldDirectives)), ...truthify([
505
+ return jen$1.new.add(this._gql("GraphQLObjectType")).call(jen$1.obj(jen$1.prop("name", jen$1.lit(type.name)), jen$1.prop("fields", this._renderFieldsThunk(type, inputFieldDirectives, type.name === "Subscription")), ...truthify([
503
506
  type.description && jen$1.prop("description", jen$1.lit(type.description)),
504
507
  typeInterfaces.length && jen$1.prop("interfaces", jen$1.arrow().arr(
505
508
  // do not sort interfaces, the declaration order may be important
@@ -739,6 +742,20 @@ class GeneratorContext {
739
742
  .map((directive) => [directive.name, this._renderGraphQLDirectiveDefinition(directive)])
740
743
  .sort(compareEntryKey),
741
744
  ].map(([, expr]) => expr),
745
+ ...Array.from(PRIMITIVE_TYPES)
746
+ .flatMap((type) => {
747
+ const info = this._scalarsInfo[type];
748
+ if (!info) {
749
+ return [];
750
+ }
751
+ return [
752
+ jen$1.statements(...[
753
+ info.serializeFunc && this._gql("GraphQL" + type).dot("serialize").op("=").parens(renderSymbolReference(this._pkgs, info.serializeFunc).as.any),
754
+ info.parseValueFunc && this._gql("GraphQL" + type).dot("parseValue").op("=").parens(renderSymbolReference(this._pkgs, info.parseValueFunc).as.any),
755
+ info.parseLiteralFunc && this._gql("GraphQL" + type).dot("parseLiteral").op("=").parens(renderSymbolReference(this._pkgs, info.parseLiteralFunc).as.any),
756
+ ].filter(Boolean))
757
+ ];
758
+ }),
742
759
  // render schema
743
760
  jen$1.const.id("schema").op("=").new.add(this._gql("GraphQLSchema")).call(jen$1.obj(...(schema.getQueryType()
744
761
  ? [jen$1.prop("query", this._renderGraphQLTypeIdentifier(schema.getQueryType()))]
@@ -878,9 +895,44 @@ function generate(schema, outfile, options = {}) {
878
895
  return createGenerator(schema, options).generate(outfile);
879
896
  }
880
897
 
898
+ function createMatcher(pattern) {
899
+ if (!pattern) {
900
+ return () => false;
901
+ }
902
+ if (typeof pattern === "function") {
903
+ return pattern;
904
+ }
905
+ if (pattern instanceof RegExp) {
906
+ return (s) => pattern.test(s);
907
+ }
908
+ if (typeof pattern === "string") {
909
+ return pm(pattern);
910
+ }
911
+ let matchers = [];
912
+ let globs = [];
913
+ for (const p of Array.isArray(pattern) ? pattern : [pattern]) {
914
+ if (typeof p === "string") {
915
+ globs.push(p);
916
+ }
917
+ else if (typeof p === "function") {
918
+ matchers.push(p);
919
+ }
920
+ else if (p instanceof RegExp) {
921
+ matchers.push(RegExp.prototype.test.bind(p));
922
+ }
923
+ else {
924
+ throw new TypeError(`Invalid pattern: ${p}`);
925
+ }
926
+ }
927
+ if (globs.length) {
928
+ matchers.push(pm(globs));
929
+ }
930
+ return (s) => matchers.some((m) => m(s));
931
+ }
932
+
881
933
  async function parseResolverFile(path) {
882
934
  return Object.fromEntries(Object.entries((await parseModule(path)).exports)
883
- .flatMap(([name, { declaration }]) => {
935
+ .flatMap(([name, decls]) => decls.flatMap(({ declaration }) => {
884
936
  if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
885
937
  if (ts.isFunctionExpression(declaration.initializer)) {
886
938
  return [[name, { module: path, symbol: name }]];
@@ -893,13 +945,14 @@ async function parseResolverFile(path) {
893
945
  return [[name, { module: path, symbol: name }]];
894
946
  }
895
947
  return [];
896
- }));
948
+ })));
897
949
  }
898
- async function parseTypeResolvers(typeName, glob) {
950
+ async function parseTypeResolvers(typeName, glob, ignore) {
951
+ const shouldIgnore = createMatcher(ignore);
899
952
  const result = {};
900
953
  for await (const entry of globIterate(glob, { withFileTypes: true })) {
901
954
  const fullpath = entry.fullpath();
902
- if (entry.isFile() && entry.name.endsWith(".ts")) {
955
+ if (!shouldIgnore(fullpath) && entry.isFile() && entry.name.endsWith(".ts")) {
903
956
  for (const [name, reference] of Object.entries(await parseResolverFile(fullpath))) {
904
957
  let realName = name;
905
958
  if (realName === "default") {
@@ -914,7 +967,7 @@ async function parseTypeResolvers(typeName, glob) {
914
967
  }
915
968
  return result;
916
969
  }
917
- async function parse$2(dirpath) {
970
+ async function parse$2(dirpath, ignore) {
918
971
  const dir = await fs.stat(dirpath);
919
972
  if (!dir.isDirectory()) {
920
973
  throw new Error(`Expected ${dirpath} to be a directory`);
@@ -923,14 +976,15 @@ async function parse$2(dirpath) {
923
976
  dirpath += "/";
924
977
  }
925
978
  const out = {};
979
+ const shouldIgnore = createMatcher(ignore);
926
980
  for await (const name of await fs.readdir(dirpath)) {
927
981
  const file = join(dirpath, name);
928
982
  const entry = await fs.stat(file);
929
983
  if (entry.isDirectory()) {
930
- out[name] = await parseTypeResolvers(name, file + "/*.ts");
984
+ out[name] = await parseTypeResolvers(name, file + "/*.ts", shouldIgnore);
931
985
  }
932
986
  else if (entry.isFile() && name.endsWith(".ts")) {
933
- out[name.slice(0, -3)] = await parseTypeResolvers(name.slice(0, -3), file);
987
+ out[name.slice(0, -3)] = await parseTypeResolvers(name.slice(0, -3), file, shouldIgnore);
934
988
  }
935
989
  }
936
990
  return out;
@@ -966,6 +1020,12 @@ function refFromTypeReferenceNode(typeNode, source) {
966
1020
  return;
967
1021
  }
968
1022
  const typeName = typeNode.typeName.text;
1023
+ if (typeNode.typeArguments?.length) {
1024
+ if (typeName !== 'Promise') {
1025
+ throw new Error('TODO: handle type arguments');
1026
+ }
1027
+ return refFromTypeNode(typeNode.typeArguments[0], source);
1028
+ }
969
1029
  for (const statement of source.statements) {
970
1030
  if (ts.isImportDeclaration(statement)) {
971
1031
  // search imports
@@ -1105,21 +1165,23 @@ async function parse$1(path) {
1105
1165
  const scalarFnsInfos = {};
1106
1166
  const scalarTypeNodes = {};
1107
1167
  const sources = {};
1108
- for (const [name, { declaration, source }] of Object.entries((await parseModule(path)).exports)) {
1109
- sources[name] = source;
1110
- if (ts.isVariableDeclaration(declaration)) {
1111
- if (declaration.initializer && ts.isObjectLiteralExpression(declaration.initializer)) {
1112
- scalarFnsInfos[name] = scalarInfoFromObjectLiteralExpression(name, new URL(source.fileName).toString(), declaration.initializer.properties);
1113
- }
1114
- }
1115
- else if (ts.isTypeAliasDeclaration(declaration)) {
1116
- if (declaration.typeParameters?.length) {
1117
- console.log(`WARNING: skipping scalar type ${name}: type parameters are not supported`);
1118
- continue;
1168
+ for (const [name, decls] of Object.entries((await parseModule(path)).exports)) {
1169
+ for (const { declaration, source } of decls) {
1170
+ sources[name] = source;
1171
+ if (ts.isVariableDeclaration(declaration)) {
1172
+ if (declaration.initializer && ts.isObjectLiteralExpression(declaration.initializer)) {
1173
+ scalarFnsInfos[name] = scalarInfoFromObjectLiteralExpression(name, new URL(source.fileName).toString(), declaration.initializer.properties);
1174
+ }
1119
1175
  }
1120
- const ref = refFromTypeNode(declaration.type, source);
1121
- if (ref) {
1122
- scalarTypeNodes[name] = declaration.type;
1176
+ else if (ts.isTypeAliasDeclaration(declaration)) {
1177
+ if (declaration.typeParameters?.length) {
1178
+ console.log(`WARNING: skipping scalar type ${name}: type parameters are not supported`);
1179
+ continue;
1180
+ }
1181
+ const ref = refFromTypeNode(declaration.type, source);
1182
+ if (ref) {
1183
+ scalarTypeNodes[name] = declaration.type;
1184
+ }
1123
1185
  }
1124
1186
  }
1125
1187
  }
@@ -1132,7 +1194,7 @@ async function parse$1(path) {
1132
1194
  module: source.fileName,
1133
1195
  symbol: name,
1134
1196
  } : reconcileScalarType(name, [
1135
- fnsInfo?.serializeFunc?.declaration?.parameters?.[0]?.type,
1197
+ // NOTE: omit serialize input type because it is supposed to be `unknown`
1136
1198
  fnsInfo?.parseValueFunc?.declaration?.type,
1137
1199
  fnsInfo?.parseLiteralFunc?.declaration?.type,
1138
1200
  ], source);
@@ -1515,9 +1577,13 @@ async function* findSchemaFiles(glob) {
1515
1577
  }
1516
1578
  }
1517
1579
  }
1518
- async function parse(glob) {
1580
+ async function parse(glob, ignore) {
1581
+ const shouldIgnore = createMatcher(ignore);
1519
1582
  const documentNodes = [];
1520
1583
  for await (const entry of findSchemaFiles(glob)) {
1584
+ if (shouldIgnore(entry)) {
1585
+ continue;
1586
+ }
1521
1587
  documentNodes.push(parse$3(new Source(await fs.readFile(entry, "utf-8"), entry)));
1522
1588
  }
1523
1589
  if (!documentNodes.length) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphql-jerky",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Jerky is a schema-first static graphql schema lightweight code generator.",
5
5
  "type": "module",
6
6
  "bin": "./build/bin/jerky.cjs",
@@ -19,11 +19,13 @@
19
19
  "just-partition": "^2.2.0",
20
20
  "just-pascal-case": "^3.2.0",
21
21
  "just-zip-it": "^3.2.0",
22
+ "picomatch": "^2.3.1",
22
23
  "typescript": "^5.1.3"
23
24
  },
24
25
  "devDependencies": {
25
26
  "@rollup/plugin-typescript": "^11.1.1",
26
27
  "@types/node": "^20.3.1",
28
+ "@types/picomatch": "^2.3.0",
27
29
  "rollup": "^3.25.1",
28
30
  "rollup-plugin-dts": "^5.3.0",
29
31
  "tslib": "^2.5.3"