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