@luvio/graphql-parser 0.99.0 → 0.100.0
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/gql.d.ts +49 -0
- package/dist/luvioGraphqlParser.js +322 -2
- package/dist/luvioGraphqlParser.mjs +319 -3
- package/dist/main.d.ts +2 -0
- package/dist/metaschema.d.ts +10 -0
- package/dist/util/language.d.ts +9 -0
- package/package.json +22 -17
- package/src/__tests__/ast.json +403 -0
- package/src/__tests__/astNoLoc.json +147 -0
- package/src/__tests__/gql.spec.ts +684 -0
- package/src/__tests__/metaschema.spec.ts +230 -0
- package/src/gql.ts +237 -0
- package/src/main.ts +7 -0
- package/src/metaschema.ts +162 -0
- package/src/util/language.ts +10 -0
- package/src/__tests__/__snapshots__/schema.spec.ts.snap +0 -2132
- package/src/__tests__/data/github.graphql +0 -48492
- package/src/__tests__/data/swapi.graphql +0 -1166
- package/src/__tests__/schema.spec.ts +0 -21
|
@@ -10498,7 +10498,7 @@ function transform$9(node, transformState) {
|
|
|
10498
10498
|
}
|
|
10499
10499
|
return ret;
|
|
10500
10500
|
}
|
|
10501
|
-
function isCustomDirective(node) {
|
|
10501
|
+
function isCustomDirective$1(node) {
|
|
10502
10502
|
return (node.name.value === CUSTOM_DIRECTIVE_CONNECTION ||
|
|
10503
10503
|
node.name.value === CUSTOM_DIRECTIVE_RESOURCE);
|
|
10504
10504
|
}
|
|
@@ -10519,7 +10519,7 @@ function transform$8(node, transformState) {
|
|
|
10519
10519
|
else {
|
|
10520
10520
|
// object or custom field node
|
|
10521
10521
|
if (directives !== undefined && directives.length > 0) {
|
|
10522
|
-
const customDirectiveNode = directives.find(isCustomDirective);
|
|
10522
|
+
const customDirectiveNode = directives.find(isCustomDirective$1);
|
|
10523
10523
|
if (customDirectiveNode === undefined) {
|
|
10524
10524
|
// transform non client-side directives
|
|
10525
10525
|
luvioNode.directives = directives.map((directive) => transform$9(directive, transformState));
|
|
@@ -10796,6 +10796,322 @@ function transform(root) {
|
|
|
10796
10796
|
};
|
|
10797
10797
|
}
|
|
10798
10798
|
|
|
10799
|
+
const { create, keys } = Object;
|
|
10800
|
+
|
|
10801
|
+
const DIRECTIVE_RECORD_CATEGORY = {
|
|
10802
|
+
kind: 'Directive',
|
|
10803
|
+
name: {
|
|
10804
|
+
kind: 'Name',
|
|
10805
|
+
value: 'category',
|
|
10806
|
+
},
|
|
10807
|
+
arguments: [
|
|
10808
|
+
{
|
|
10809
|
+
kind: 'Argument',
|
|
10810
|
+
name: {
|
|
10811
|
+
kind: 'Name',
|
|
10812
|
+
value: 'name',
|
|
10813
|
+
},
|
|
10814
|
+
value: {
|
|
10815
|
+
kind: 'StringValue',
|
|
10816
|
+
value: 'recordQuery',
|
|
10817
|
+
block: false,
|
|
10818
|
+
},
|
|
10819
|
+
},
|
|
10820
|
+
],
|
|
10821
|
+
};
|
|
10822
|
+
const DIRECTIVE_PARENT_CATEGORY = {
|
|
10823
|
+
kind: 'Directive',
|
|
10824
|
+
name: {
|
|
10825
|
+
kind: 'Name',
|
|
10826
|
+
value: 'category',
|
|
10827
|
+
},
|
|
10828
|
+
arguments: [
|
|
10829
|
+
{
|
|
10830
|
+
kind: 'Argument',
|
|
10831
|
+
name: {
|
|
10832
|
+
kind: 'Name',
|
|
10833
|
+
value: 'name',
|
|
10834
|
+
},
|
|
10835
|
+
value: {
|
|
10836
|
+
kind: 'StringValue',
|
|
10837
|
+
value: 'parentRelationship',
|
|
10838
|
+
block: false,
|
|
10839
|
+
},
|
|
10840
|
+
},
|
|
10841
|
+
],
|
|
10842
|
+
};
|
|
10843
|
+
function substituteDirectives(directives, index, nodeName) {
|
|
10844
|
+
if (directives[index].name.value === CUSTOM_DIRECTIVE_CONNECTION) {
|
|
10845
|
+
// replace the custom directive node with the metaschema directive node
|
|
10846
|
+
// @ts-ignore - Document is read only
|
|
10847
|
+
directives[index] = DIRECTIVE_RECORD_CATEGORY;
|
|
10848
|
+
}
|
|
10849
|
+
else if (directives[index].name.value === CUSTOM_DIRECTIVE_RESOURCE) {
|
|
10850
|
+
// node gets its type from @category recordQuery
|
|
10851
|
+
if (nodeName === 'node') {
|
|
10852
|
+
// @ts-ignore - Document is read only
|
|
10853
|
+
directives.splice(index, 1);
|
|
10854
|
+
}
|
|
10855
|
+
else {
|
|
10856
|
+
// @ts-ignore - Document is read only
|
|
10857
|
+
directives[index] = DIRECTIVE_PARENT_CATEGORY;
|
|
10858
|
+
}
|
|
10859
|
+
}
|
|
10860
|
+
}
|
|
10861
|
+
/**
|
|
10862
|
+
* Returns true if the directive node is of legacy type
|
|
10863
|
+
* @param node : Directive node
|
|
10864
|
+
* @returns
|
|
10865
|
+
*/
|
|
10866
|
+
function isCustomDirective(node) {
|
|
10867
|
+
return (node.name.value === CUSTOM_DIRECTIVE_CONNECTION ||
|
|
10868
|
+
node.name.value === CUSTOM_DIRECTIVE_RESOURCE);
|
|
10869
|
+
}
|
|
10870
|
+
/**
|
|
10871
|
+
* Traverses a selection set and it's nested selections,
|
|
10872
|
+
* to find any legacy custom directives and substitute them with metaschema directives
|
|
10873
|
+
* @param node - SelectionSetNode
|
|
10874
|
+
* @returns SelectionSetNode
|
|
10875
|
+
*/
|
|
10876
|
+
function traverseSelectionSet(node) {
|
|
10877
|
+
if (node === undefined) {
|
|
10878
|
+
return;
|
|
10879
|
+
}
|
|
10880
|
+
for (const selection of node.selections) {
|
|
10881
|
+
// FragmentSpreadNode doesn't have a selection set
|
|
10882
|
+
// which should be handled at this methods entry point
|
|
10883
|
+
const { directives, selectionSet } = selection;
|
|
10884
|
+
let selectionName;
|
|
10885
|
+
if (selection.kind !== 'InlineFragment') {
|
|
10886
|
+
selectionName = selection.name.value;
|
|
10887
|
+
}
|
|
10888
|
+
if (directives !== undefined && directives.length > 0) {
|
|
10889
|
+
// we follow this pattern instead of map to preserve the order of directives
|
|
10890
|
+
// order of directives may be significant as per graphql spec
|
|
10891
|
+
const index = directives.findIndex(isCustomDirective);
|
|
10892
|
+
if (index !== -1) {
|
|
10893
|
+
substituteDirectives(directives, index, selectionName);
|
|
10894
|
+
}
|
|
10895
|
+
}
|
|
10896
|
+
traverseSelectionSet(selectionSet);
|
|
10897
|
+
}
|
|
10898
|
+
return node;
|
|
10899
|
+
}
|
|
10900
|
+
/**
|
|
10901
|
+
* Accepts a document node and replaces the legacy custom directives with metaschema directives "in-place"
|
|
10902
|
+
* @param doc
|
|
10903
|
+
*/
|
|
10904
|
+
function metaschemaMapper(doc) {
|
|
10905
|
+
// this method is only callable for Executable definitions
|
|
10906
|
+
// such as Operations and Fragments
|
|
10907
|
+
// so we have to explicitly cast the definitions for ts
|
|
10908
|
+
const { definitions } = doc;
|
|
10909
|
+
for (const def of definitions) {
|
|
10910
|
+
const { directives, selectionSet } = def;
|
|
10911
|
+
if (directives !== undefined && directives.length > 0) {
|
|
10912
|
+
// we are making an assumption here that only one custom directive can be applied to a node
|
|
10913
|
+
// we can revisit if this condition changes
|
|
10914
|
+
const index = directives.findIndex(isCustomDirective);
|
|
10915
|
+
if (index !== -1) {
|
|
10916
|
+
substituteDirectives(directives, index, undefined);
|
|
10917
|
+
}
|
|
10918
|
+
}
|
|
10919
|
+
traverseSelectionSet(selectionSet);
|
|
10920
|
+
}
|
|
10921
|
+
}
|
|
10922
|
+
|
|
10923
|
+
/**
|
|
10924
|
+
* we should look into optimizing this before it turns into a memory hog
|
|
10925
|
+
* weakmaps, or limiting the size of the cache, or something
|
|
10926
|
+
*/
|
|
10927
|
+
const docMap = new Map();
|
|
10928
|
+
/**
|
|
10929
|
+
* Opaque reference map to return keys to userland
|
|
10930
|
+
* As a user shouldn't have access to the Document
|
|
10931
|
+
*/
|
|
10932
|
+
const referenceMap = new WeakMap();
|
|
10933
|
+
let addMetaschemaDirectives = false;
|
|
10934
|
+
/**
|
|
10935
|
+
* Strips characters that are not significant to the validity or execution
|
|
10936
|
+
* of a GraphQL document:
|
|
10937
|
+
* - UnicodeBOM
|
|
10938
|
+
* - WhiteSpace
|
|
10939
|
+
* - LineTerminator
|
|
10940
|
+
* - Comment
|
|
10941
|
+
* - Comma
|
|
10942
|
+
* - BlockString indentation
|
|
10943
|
+
*/
|
|
10944
|
+
function operationKeyBuilder(inputString) {
|
|
10945
|
+
return stripIgnoredCharacters(inputString);
|
|
10946
|
+
}
|
|
10947
|
+
/**
|
|
10948
|
+
* Returns document node if cached or else update the cache and return the document node
|
|
10949
|
+
* @param inputString - operation string
|
|
10950
|
+
* @returns DocumentNode
|
|
10951
|
+
*/
|
|
10952
|
+
function parseDocument(inputString) {
|
|
10953
|
+
const operationKey = operationKeyBuilder(inputString);
|
|
10954
|
+
const cachedDoc = docMap.get(operationKey);
|
|
10955
|
+
if (cachedDoc !== undefined) {
|
|
10956
|
+
return cachedDoc;
|
|
10957
|
+
}
|
|
10958
|
+
// parse throws an GraphQLError in case of invalid query, should this be in try/catch?
|
|
10959
|
+
const parsedDoc = parse(inputString);
|
|
10960
|
+
if (!parsedDoc || parsedDoc.kind !== 'Document') {
|
|
10961
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
10962
|
+
throw new Error('Invalid graphql doc');
|
|
10963
|
+
}
|
|
10964
|
+
return null;
|
|
10965
|
+
}
|
|
10966
|
+
// in-place substitution for removal of legacy and adding metaschema directives
|
|
10967
|
+
if (addMetaschemaDirectives) {
|
|
10968
|
+
metaschemaMapper(parsedDoc);
|
|
10969
|
+
}
|
|
10970
|
+
const parsedDocNoLoc = stripLocation(parsedDoc);
|
|
10971
|
+
docMap.set(operationKey, parsedDocNoLoc);
|
|
10972
|
+
return parsedDocNoLoc;
|
|
10973
|
+
}
|
|
10974
|
+
/**
|
|
10975
|
+
* If the input string has fragment substitution
|
|
10976
|
+
* Insert the fragments AST to the query document node
|
|
10977
|
+
*/
|
|
10978
|
+
function insertFragments(doc, fragments) {
|
|
10979
|
+
fragments.forEach((fragment) => {
|
|
10980
|
+
// @ts-ignore
|
|
10981
|
+
// graphql describes definitions as "readonly"
|
|
10982
|
+
// so we aren't supposed to mutate the document node
|
|
10983
|
+
// but instead of parsing the fragment again, we substitute it's definition in the document node
|
|
10984
|
+
doc.definitions.push(fragment);
|
|
10985
|
+
});
|
|
10986
|
+
return doc;
|
|
10987
|
+
}
|
|
10988
|
+
function createReference(document) {
|
|
10989
|
+
const key = create(null);
|
|
10990
|
+
referenceMap.set(key, document);
|
|
10991
|
+
return key;
|
|
10992
|
+
}
|
|
10993
|
+
/**
|
|
10994
|
+
* Insert string and fragment substitutions with the actual nodes
|
|
10995
|
+
* @param inputString
|
|
10996
|
+
* @param substitutions - string | fragment DocumentNode
|
|
10997
|
+
* @returns { operation string, fragment docs [] }
|
|
10998
|
+
*/
|
|
10999
|
+
function processSubstitutions(inputString, substitutions) {
|
|
11000
|
+
let outputString = '';
|
|
11001
|
+
const fragments = [];
|
|
11002
|
+
const subLength = substitutions.length;
|
|
11003
|
+
for (let i = 0; i < subLength; i++) {
|
|
11004
|
+
const substitution = substitutions[i];
|
|
11005
|
+
outputString += inputString[i];
|
|
11006
|
+
if (typeof substitution === 'string' || typeof substitution === 'number') {
|
|
11007
|
+
outputString += substitution;
|
|
11008
|
+
}
|
|
11009
|
+
else if (typeof substitution === 'object') {
|
|
11010
|
+
const doc = referenceMap.get(substitution);
|
|
11011
|
+
if (doc === undefined) {
|
|
11012
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
11013
|
+
throw new Error('Invalid substitution fragment');
|
|
11014
|
+
}
|
|
11015
|
+
return null;
|
|
11016
|
+
}
|
|
11017
|
+
for (const def of doc.definitions) {
|
|
11018
|
+
fragments.push(def);
|
|
11019
|
+
}
|
|
11020
|
+
}
|
|
11021
|
+
else {
|
|
11022
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
11023
|
+
throw new Error('Unsupported substitution type');
|
|
11024
|
+
}
|
|
11025
|
+
return null;
|
|
11026
|
+
}
|
|
11027
|
+
}
|
|
11028
|
+
return {
|
|
11029
|
+
operationString: outputString + inputString[subLength],
|
|
11030
|
+
fragments,
|
|
11031
|
+
};
|
|
11032
|
+
}
|
|
11033
|
+
/**
|
|
11034
|
+
* strips Document node and nested definitions of location references
|
|
11035
|
+
*/
|
|
11036
|
+
function stripLocation(node) {
|
|
11037
|
+
if (node.loc) {
|
|
11038
|
+
delete node.loc;
|
|
11039
|
+
}
|
|
11040
|
+
const keys$1 = keys(node);
|
|
11041
|
+
const keysLength = keys$1.length;
|
|
11042
|
+
if (keysLength === 0) {
|
|
11043
|
+
return node;
|
|
11044
|
+
}
|
|
11045
|
+
for (const key of keys$1) {
|
|
11046
|
+
const subNode = node[key];
|
|
11047
|
+
if (subNode && typeof subNode === 'object') {
|
|
11048
|
+
stripLocation(subNode);
|
|
11049
|
+
}
|
|
11050
|
+
}
|
|
11051
|
+
return node;
|
|
11052
|
+
}
|
|
11053
|
+
/**
|
|
11054
|
+
*
|
|
11055
|
+
* @param astReference - ast reference passed from user land
|
|
11056
|
+
*/
|
|
11057
|
+
const astResolver = function (astReference) {
|
|
11058
|
+
return referenceMap.get(astReference);
|
|
11059
|
+
};
|
|
11060
|
+
/**
|
|
11061
|
+
*
|
|
11062
|
+
* @param literals - operation query string
|
|
11063
|
+
* @param subs - all other substitutions
|
|
11064
|
+
* @returns an opaque reference to the parsed document
|
|
11065
|
+
*/
|
|
11066
|
+
function gql(literals, ...subs) {
|
|
11067
|
+
let inputString;
|
|
11068
|
+
let inputSubstitutionFragments;
|
|
11069
|
+
if (!literals) {
|
|
11070
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
11071
|
+
throw new Error('Invalid query');
|
|
11072
|
+
}
|
|
11073
|
+
return null;
|
|
11074
|
+
}
|
|
11075
|
+
else if (typeof literals === 'string') {
|
|
11076
|
+
inputString = literals.trim();
|
|
11077
|
+
inputSubstitutionFragments = [];
|
|
11078
|
+
}
|
|
11079
|
+
else {
|
|
11080
|
+
// called as template literal
|
|
11081
|
+
const sub = processSubstitutions(literals, subs);
|
|
11082
|
+
// if invalid fragment references found
|
|
11083
|
+
if (sub === null) {
|
|
11084
|
+
return null;
|
|
11085
|
+
}
|
|
11086
|
+
const { operationString, fragments } = sub;
|
|
11087
|
+
inputString = operationString.trim();
|
|
11088
|
+
inputSubstitutionFragments = fragments;
|
|
11089
|
+
}
|
|
11090
|
+
if (inputString.length === 0) {
|
|
11091
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
11092
|
+
throw new Error('Invalid query');
|
|
11093
|
+
}
|
|
11094
|
+
return null;
|
|
11095
|
+
}
|
|
11096
|
+
const document = parseDocument(inputString);
|
|
11097
|
+
if (document === null) {
|
|
11098
|
+
return null;
|
|
11099
|
+
}
|
|
11100
|
+
if (inputSubstitutionFragments.length === 0) {
|
|
11101
|
+
return createReference(document);
|
|
11102
|
+
}
|
|
11103
|
+
return createReference(insertFragments(document, inputSubstitutionFragments));
|
|
11104
|
+
}
|
|
11105
|
+
/**
|
|
11106
|
+
* Enable the parser to add corresponding metaschema directives for backwards compatibility
|
|
11107
|
+
*/
|
|
11108
|
+
function enableAddMetaschemaDirective() {
|
|
11109
|
+
addMetaschemaDirectives = true;
|
|
11110
|
+
}
|
|
11111
|
+
function disableAddMetaschemaDirective() {
|
|
11112
|
+
addMetaschemaDirectives = false;
|
|
11113
|
+
}
|
|
11114
|
+
|
|
10799
11115
|
/*
|
|
10800
11116
|
Deprecated - Remove after existing usages are removed.
|
|
10801
11117
|
*/
|
|
@@ -10804,4 +11120,4 @@ function parseAndVisit(source) {
|
|
|
10804
11120
|
return transform(ast);
|
|
10805
11121
|
}
|
|
10806
11122
|
|
|
10807
|
-
export { GraphQLScalarType, GraphQLSchema, Kind, buildASTSchema, isScalarType, parse, parseAndVisit, print, stripIgnoredCharacters, visit };
|
|
11123
|
+
export { GraphQLScalarType, GraphQLSchema, Kind, astResolver, buildASTSchema, disableAddMetaschemaDirective, enableAddMetaschemaDirective, gql, isScalarType, parse, parseAndVisit, print, stripIgnoredCharacters, visit };
|
package/dist/main.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export type { ListTypeNode, BooleanValueNode, EnumTypeDefinitionNode, FieldDefin
|
|
|
7
7
|
export { ASTVisitor, parse, Kind, print, visit } from 'graphql/language';
|
|
8
8
|
export { isScalarType } from 'graphql/type';
|
|
9
9
|
export { stripIgnoredCharacters } from 'graphql/utilities';
|
|
10
|
+
export { gql, enableAddMetaschemaDirective, disableAddMetaschemaDirective, astResolver, } from './gql';
|
|
11
|
+
export type { AstResolver } from './gql';
|
|
10
12
|
/**
|
|
11
13
|
* @deprecated - Schema-backed adapters will use standard graphql types re-exported from @luvio/graphql
|
|
12
14
|
*/
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Add metaschema annotations to their corresponding custom notation counterparts
|
|
3
|
+
* @module metaschemaMapper
|
|
4
|
+
*/
|
|
5
|
+
import type { DocumentNode } from 'graphql/language';
|
|
6
|
+
/**
|
|
7
|
+
* Accepts a document node and replaces the legacy custom directives with metaschema directives "in-place"
|
|
8
|
+
* @param doc
|
|
9
|
+
*/
|
|
10
|
+
export declare function metaschemaMapper(doc: DocumentNode): void;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
declare const isArray: (arg: any) => arg is any[];
|
|
2
|
+
declare const create: {
|
|
3
|
+
(o: object | null): any;
|
|
4
|
+
(o: object | null, properties: PropertyDescriptorMap & ThisType<any>): any;
|
|
5
|
+
}, keys: {
|
|
6
|
+
(o: object): string[];
|
|
7
|
+
(o: {}): string[];
|
|
8
|
+
};
|
|
9
|
+
export { isArray as ArrayIsArray, keys as ObjectKeys, create as ObjectCreate, };
|
package/package.json
CHANGED
|
@@ -1,32 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luvio/graphql-parser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.100.0",
|
|
4
4
|
"description": "GraphQL parser for Luvio GraphQL adapter support",
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/salesforce/luvio.git",
|
|
8
|
+
"directory": "packages/@luvio/graphql-parser"
|
|
9
|
+
},
|
|
8
10
|
"license": "MIT",
|
|
9
11
|
"exports": {
|
|
10
12
|
"require": "./dist/luvioGraphqlParser.js",
|
|
11
13
|
"import": "./dist/luvioGraphqlParser.mjs"
|
|
12
14
|
},
|
|
15
|
+
"main": "./dist/luvioGraphqlParser.js",
|
|
16
|
+
"module": "./dist/luvioGraphqlParser.mjs",
|
|
17
|
+
"types": "dist/main.d.ts",
|
|
13
18
|
"scripts": {
|
|
14
|
-
"clean": "rm -rf dist",
|
|
15
19
|
"build": "rollup --config rollup.config.js",
|
|
20
|
+
"clean": "rm -rf dist",
|
|
16
21
|
"parse": "node ./scripts/cli.mjs",
|
|
17
|
-
"test
|
|
18
|
-
"test:debug": "node --inspect-brk
|
|
22
|
+
"test": "jest",
|
|
23
|
+
"test:debug": "node --inspect-brk ../../../node_modules/.bin/jest --runInBand",
|
|
19
24
|
"test:size": "bundlesize"
|
|
20
25
|
},
|
|
21
|
-
"nx": {
|
|
22
|
-
"targets": {
|
|
23
|
-
"build": {
|
|
24
|
-
"outputs": [
|
|
25
|
-
"packages/@luvio/graphql-parser/dist"
|
|
26
|
-
]
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
26
|
"dependencies": {
|
|
31
27
|
"graphql": "^15.0.0"
|
|
32
28
|
},
|
|
@@ -39,5 +35,14 @@
|
|
|
39
35
|
"maxSize": "58 kB",
|
|
40
36
|
"compression": "brotli"
|
|
41
37
|
}
|
|
42
|
-
]
|
|
38
|
+
],
|
|
39
|
+
"nx": {
|
|
40
|
+
"targets": {
|
|
41
|
+
"build": {
|
|
42
|
+
"outputs": [
|
|
43
|
+
"packages/@luvio/graphql-parser/dist"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
43
48
|
}
|