@nmarks/graphql-codegen-per-operation-file-preset 1.0.10 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/index.js +37 -0
- package/cjs/persisted-documents.js +179 -0
- package/esm/index.js +37 -0
- package/esm/persisted-documents.js +172 -0
- package/package.json +1 -1
- package/typings/index.d.cts +29 -0
- package/typings/index.d.ts +29 -0
- package/typings/persisted-documents.d.cts +44 -0
- package/typings/persisted-documents.d.ts +44 -0
package/cjs/index.js
CHANGED
|
@@ -8,6 +8,7 @@ const add_1 = tslib_1.__importDefault(require("@graphql-codegen/add"));
|
|
|
8
8
|
const visitor_plugin_common_1 = require("@graphql-codegen/visitor-plugin-common");
|
|
9
9
|
const resolve_document_imports_js_1 = require("./resolve-document-imports.js");
|
|
10
10
|
const utils_js_1 = require("./utils.js");
|
|
11
|
+
const persisted_documents_js_1 = require("./persisted-documents.js");
|
|
11
12
|
/**
|
|
12
13
|
* Extract operation and fragment names from a document
|
|
13
14
|
*/
|
|
@@ -530,6 +531,42 @@ exports.preset = {
|
|
|
530
531
|
});
|
|
531
532
|
}
|
|
532
533
|
}
|
|
534
|
+
/**
|
|
535
|
+
* PERSISTED DOCUMENTS GENERATION
|
|
536
|
+
*
|
|
537
|
+
* If persistedDocuments is configured, generate a JSON file mapping
|
|
538
|
+
* operation hashes to their full query bodies (with __typename added).
|
|
539
|
+
* This happens in a single pass - no second document scan required.
|
|
540
|
+
*/
|
|
541
|
+
const { persistedDocuments: persistedDocumentsConfig } = options.presetConfig;
|
|
542
|
+
if (persistedDocumentsConfig === null || persistedDocumentsConfig === void 0 ? void 0 : persistedDocumentsConfig.output) {
|
|
543
|
+
// Collect all source documents for persisted document generation
|
|
544
|
+
const allDocuments = options.documents;
|
|
545
|
+
// Generate the persisted documents map
|
|
546
|
+
const persistedDocsMap = (0, persisted_documents_js_1.generatePersistedDocuments)(allDocuments);
|
|
547
|
+
// Add artifact for the persisted documents JSON file
|
|
548
|
+
artifacts.push({
|
|
549
|
+
...options,
|
|
550
|
+
filename: (0, path_1.join)(options.baseOutputDir, persistedDocumentsConfig.output),
|
|
551
|
+
plugins: [
|
|
552
|
+
{
|
|
553
|
+
[`persisted-documents`]: {},
|
|
554
|
+
},
|
|
555
|
+
],
|
|
556
|
+
pluginMap: {
|
|
557
|
+
[`persisted-documents`]: {
|
|
558
|
+
plugin: () => ({
|
|
559
|
+
content: JSON.stringify(persistedDocsMap, null, 2),
|
|
560
|
+
}),
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
schema: options.schema,
|
|
564
|
+
schemaAst: schemaObject,
|
|
565
|
+
config: {},
|
|
566
|
+
documents: allDocuments,
|
|
567
|
+
skipDocumentsValidation: true,
|
|
568
|
+
});
|
|
569
|
+
}
|
|
533
570
|
return artifacts;
|
|
534
571
|
},
|
|
535
572
|
};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.addTypenameToDocument = addTypenameToDocument;
|
|
4
|
+
exports.collectFragmentReferences = collectFragmentReferences;
|
|
5
|
+
exports.collectAllFragmentDeps = collectAllFragmentDeps;
|
|
6
|
+
exports.sortTopLevelDefinitions = sortTopLevelDefinitions;
|
|
7
|
+
exports.generatePersistedDocuments = generatePersistedDocuments;
|
|
8
|
+
const crypto_1 = require("crypto");
|
|
9
|
+
const graphql_1 = require("graphql");
|
|
10
|
+
/**
|
|
11
|
+
* Adds __typename field to all selection sets matching Apollo Client's InMemoryCache behavior.
|
|
12
|
+
*
|
|
13
|
+
* Rules:
|
|
14
|
+
* - Add __typename at the END of selection sets
|
|
15
|
+
* - Skip root selection set of operations (query/mutation/subscription)
|
|
16
|
+
* - DO add __typename to fragment root selection sets
|
|
17
|
+
*/
|
|
18
|
+
function addTypenameToDocument(doc) {
|
|
19
|
+
return (0, graphql_1.visit)(doc, {
|
|
20
|
+
SelectionSet(node, _key, parent) {
|
|
21
|
+
// Skip root selection set of operations (parent is OperationDefinition)
|
|
22
|
+
// But DON'T skip root selection set of fragments (they should get __typename)
|
|
23
|
+
if (parent && parent.kind === graphql_1.Kind.OPERATION_DEFINITION) {
|
|
24
|
+
return node;
|
|
25
|
+
}
|
|
26
|
+
// Check if __typename already exists
|
|
27
|
+
const hasTypename = node.selections.some(selection => selection.kind === graphql_1.Kind.FIELD && selection.name.value === '__typename');
|
|
28
|
+
if (hasTypename) {
|
|
29
|
+
return node;
|
|
30
|
+
}
|
|
31
|
+
// Add __typename at the END (matching Apollo Client behavior)
|
|
32
|
+
return {
|
|
33
|
+
...node,
|
|
34
|
+
selections: [
|
|
35
|
+
...node.selections,
|
|
36
|
+
{
|
|
37
|
+
kind: graphql_1.Kind.FIELD,
|
|
38
|
+
name: { kind: graphql_1.Kind.NAME, value: '__typename' },
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Collects all fragment names referenced in a document via FragmentSpread nodes
|
|
47
|
+
*/
|
|
48
|
+
function collectFragmentReferences(doc) {
|
|
49
|
+
const refs = new Set();
|
|
50
|
+
(0, graphql_1.visit)(doc, {
|
|
51
|
+
FragmentSpread(node) {
|
|
52
|
+
refs.add(node.name.value);
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
return refs;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Recursively collects all fragment dependencies (including nested)
|
|
59
|
+
*/
|
|
60
|
+
function collectAllFragmentDeps(fragmentName, fragmentMap, collected = new Set()) {
|
|
61
|
+
if (collected.has(fragmentName))
|
|
62
|
+
return collected;
|
|
63
|
+
collected.add(fragmentName);
|
|
64
|
+
const fragment = fragmentMap.get(fragmentName);
|
|
65
|
+
if (fragment) {
|
|
66
|
+
const refs = collectFragmentReferences(fragment);
|
|
67
|
+
for (const ref of refs) {
|
|
68
|
+
collectAllFragmentDeps(ref, fragmentMap, collected);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return collected;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Sort the definitions in a document so that operations come before fragments,
|
|
75
|
+
* and so that each kind of definition is sorted by name.
|
|
76
|
+
*
|
|
77
|
+
* This is a direct implementation of the sorting logic from @apollo/persisted-query-lists
|
|
78
|
+
* to avoid an external dependency.
|
|
79
|
+
*/
|
|
80
|
+
function sortTopLevelDefinitions(query) {
|
|
81
|
+
const definitions = [...query.definitions];
|
|
82
|
+
definitions.sort((a, b) => {
|
|
83
|
+
var _a, _b, _c, _d;
|
|
84
|
+
// This is a reverse sort by kind, so that OperationDefinition precedes FragmentDefinition.
|
|
85
|
+
if (a.kind > b.kind) {
|
|
86
|
+
return -1;
|
|
87
|
+
}
|
|
88
|
+
if (a.kind < b.kind) {
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
// Extract the name from each definition
|
|
92
|
+
const aName = a.kind === graphql_1.Kind.OPERATION_DEFINITION || a.kind === graphql_1.Kind.FRAGMENT_DEFINITION
|
|
93
|
+
? ((_b = (_a = a.name) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '')
|
|
94
|
+
: '';
|
|
95
|
+
const bName = b.kind === graphql_1.Kind.OPERATION_DEFINITION || b.kind === graphql_1.Kind.FRAGMENT_DEFINITION
|
|
96
|
+
? ((_d = (_c = b.name) === null || _c === void 0 ? void 0 : _c.value) !== null && _d !== void 0 ? _d : '')
|
|
97
|
+
: '';
|
|
98
|
+
// Sort by name ascending.
|
|
99
|
+
if (aName < bName) {
|
|
100
|
+
return -1;
|
|
101
|
+
}
|
|
102
|
+
if (aName > bName) {
|
|
103
|
+
return 1;
|
|
104
|
+
}
|
|
105
|
+
return 0;
|
|
106
|
+
});
|
|
107
|
+
return {
|
|
108
|
+
...query,
|
|
109
|
+
definitions,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Generates persisted documents map from all documents.
|
|
114
|
+
*
|
|
115
|
+
* Returns a map of hash -> query body for all operations (not standalone fragments).
|
|
116
|
+
* Each operation includes its fragment dependencies inline.
|
|
117
|
+
*/
|
|
118
|
+
function generatePersistedDocuments(documents) {
|
|
119
|
+
// Build fragment registry from all documents
|
|
120
|
+
const fragmentMap = new Map();
|
|
121
|
+
for (const doc of documents) {
|
|
122
|
+
if (!doc.document)
|
|
123
|
+
continue;
|
|
124
|
+
for (const def of doc.document.definitions) {
|
|
125
|
+
if (def.kind === graphql_1.Kind.FRAGMENT_DEFINITION) {
|
|
126
|
+
fragmentMap.set(def.name.value, def);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const result = {};
|
|
131
|
+
for (const doc of documents) {
|
|
132
|
+
if (!doc.document)
|
|
133
|
+
continue;
|
|
134
|
+
// Find operations in this document
|
|
135
|
+
const operations = doc.document.definitions.filter((def) => def.kind === graphql_1.Kind.OPERATION_DEFINITION);
|
|
136
|
+
// Skip documents with no operations (fragment-only files)
|
|
137
|
+
if (operations.length === 0)
|
|
138
|
+
continue;
|
|
139
|
+
for (const operation of operations) {
|
|
140
|
+
// Create a document with just this operation
|
|
141
|
+
let operationDoc = {
|
|
142
|
+
kind: graphql_1.Kind.DOCUMENT,
|
|
143
|
+
definitions: [operation],
|
|
144
|
+
};
|
|
145
|
+
// Collect all required fragments (recursively)
|
|
146
|
+
const directRefs = collectFragmentReferences(operationDoc);
|
|
147
|
+
const allFragments = new Set();
|
|
148
|
+
for (const ref of directRefs) {
|
|
149
|
+
collectAllFragmentDeps(ref, fragmentMap, allFragments);
|
|
150
|
+
}
|
|
151
|
+
// Add fragment definitions to the document
|
|
152
|
+
const fragmentDefs = [];
|
|
153
|
+
for (const fragName of allFragments) {
|
|
154
|
+
const frag = fragmentMap.get(fragName);
|
|
155
|
+
if (frag)
|
|
156
|
+
fragmentDefs.push(frag);
|
|
157
|
+
}
|
|
158
|
+
if (fragmentDefs.length > 0) {
|
|
159
|
+
operationDoc = {
|
|
160
|
+
...operationDoc,
|
|
161
|
+
definitions: [...operationDoc.definitions, ...fragmentDefs],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
// Add __typename to all selection sets (matching Apollo Client behavior)
|
|
165
|
+
const docWithTypename = addTypenameToDocument(operationDoc);
|
|
166
|
+
// Sort definitions (operation first, then fragments alphabetically)
|
|
167
|
+
const sortedDoc = sortTopLevelDefinitions(docWithTypename);
|
|
168
|
+
// Print the document
|
|
169
|
+
const body = (0, graphql_1.print)(sortedDoc);
|
|
170
|
+
// Skip empty documents
|
|
171
|
+
if (!body || body.trim() === '')
|
|
172
|
+
continue;
|
|
173
|
+
// Hash is just for the JSON key - gets re-computed by generate-persisted-query-manifest
|
|
174
|
+
const hash = (0, crypto_1.createHash)('sha256').update(body).digest('hex');
|
|
175
|
+
result[hash] = body;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
}
|
package/esm/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import addPlugin from '@graphql-codegen/add';
|
|
|
4
4
|
import { generateImportStatement, getConfigValue, resolveImportSource, } from '@graphql-codegen/visitor-plugin-common';
|
|
5
5
|
import { resolveDocumentImports } from './resolve-document-imports.js';
|
|
6
6
|
import { generateOperationFilePath } from './utils.js';
|
|
7
|
+
import { generatePersistedDocuments } from './persisted-documents.js';
|
|
7
8
|
/**
|
|
8
9
|
* Extract operation and fragment names from a document
|
|
9
10
|
*/
|
|
@@ -526,6 +527,42 @@ export const preset = {
|
|
|
526
527
|
});
|
|
527
528
|
}
|
|
528
529
|
}
|
|
530
|
+
/**
|
|
531
|
+
* PERSISTED DOCUMENTS GENERATION
|
|
532
|
+
*
|
|
533
|
+
* If persistedDocuments is configured, generate a JSON file mapping
|
|
534
|
+
* operation hashes to their full query bodies (with __typename added).
|
|
535
|
+
* This happens in a single pass - no second document scan required.
|
|
536
|
+
*/
|
|
537
|
+
const { persistedDocuments: persistedDocumentsConfig } = options.presetConfig;
|
|
538
|
+
if (persistedDocumentsConfig === null || persistedDocumentsConfig === void 0 ? void 0 : persistedDocumentsConfig.output) {
|
|
539
|
+
// Collect all source documents for persisted document generation
|
|
540
|
+
const allDocuments = options.documents;
|
|
541
|
+
// Generate the persisted documents map
|
|
542
|
+
const persistedDocsMap = generatePersistedDocuments(allDocuments);
|
|
543
|
+
// Add artifact for the persisted documents JSON file
|
|
544
|
+
artifacts.push({
|
|
545
|
+
...options,
|
|
546
|
+
filename: join(options.baseOutputDir, persistedDocumentsConfig.output),
|
|
547
|
+
plugins: [
|
|
548
|
+
{
|
|
549
|
+
[`persisted-documents`]: {},
|
|
550
|
+
},
|
|
551
|
+
],
|
|
552
|
+
pluginMap: {
|
|
553
|
+
[`persisted-documents`]: {
|
|
554
|
+
plugin: () => ({
|
|
555
|
+
content: JSON.stringify(persistedDocsMap, null, 2),
|
|
556
|
+
}),
|
|
557
|
+
},
|
|
558
|
+
},
|
|
559
|
+
schema: options.schema,
|
|
560
|
+
schemaAst: schemaObject,
|
|
561
|
+
config: {},
|
|
562
|
+
documents: allDocuments,
|
|
563
|
+
skipDocumentsValidation: true,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
529
566
|
return artifacts;
|
|
530
567
|
},
|
|
531
568
|
};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { Kind, print, visit, } from 'graphql';
|
|
3
|
+
/**
|
|
4
|
+
* Adds __typename field to all selection sets matching Apollo Client's InMemoryCache behavior.
|
|
5
|
+
*
|
|
6
|
+
* Rules:
|
|
7
|
+
* - Add __typename at the END of selection sets
|
|
8
|
+
* - Skip root selection set of operations (query/mutation/subscription)
|
|
9
|
+
* - DO add __typename to fragment root selection sets
|
|
10
|
+
*/
|
|
11
|
+
export function addTypenameToDocument(doc) {
|
|
12
|
+
return visit(doc, {
|
|
13
|
+
SelectionSet(node, _key, parent) {
|
|
14
|
+
// Skip root selection set of operations (parent is OperationDefinition)
|
|
15
|
+
// But DON'T skip root selection set of fragments (they should get __typename)
|
|
16
|
+
if (parent && parent.kind === Kind.OPERATION_DEFINITION) {
|
|
17
|
+
return node;
|
|
18
|
+
}
|
|
19
|
+
// Check if __typename already exists
|
|
20
|
+
const hasTypename = node.selections.some(selection => selection.kind === Kind.FIELD && selection.name.value === '__typename');
|
|
21
|
+
if (hasTypename) {
|
|
22
|
+
return node;
|
|
23
|
+
}
|
|
24
|
+
// Add __typename at the END (matching Apollo Client behavior)
|
|
25
|
+
return {
|
|
26
|
+
...node,
|
|
27
|
+
selections: [
|
|
28
|
+
...node.selections,
|
|
29
|
+
{
|
|
30
|
+
kind: Kind.FIELD,
|
|
31
|
+
name: { kind: Kind.NAME, value: '__typename' },
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Collects all fragment names referenced in a document via FragmentSpread nodes
|
|
40
|
+
*/
|
|
41
|
+
export function collectFragmentReferences(doc) {
|
|
42
|
+
const refs = new Set();
|
|
43
|
+
visit(doc, {
|
|
44
|
+
FragmentSpread(node) {
|
|
45
|
+
refs.add(node.name.value);
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
return refs;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Recursively collects all fragment dependencies (including nested)
|
|
52
|
+
*/
|
|
53
|
+
export function collectAllFragmentDeps(fragmentName, fragmentMap, collected = new Set()) {
|
|
54
|
+
if (collected.has(fragmentName))
|
|
55
|
+
return collected;
|
|
56
|
+
collected.add(fragmentName);
|
|
57
|
+
const fragment = fragmentMap.get(fragmentName);
|
|
58
|
+
if (fragment) {
|
|
59
|
+
const refs = collectFragmentReferences(fragment);
|
|
60
|
+
for (const ref of refs) {
|
|
61
|
+
collectAllFragmentDeps(ref, fragmentMap, collected);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return collected;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Sort the definitions in a document so that operations come before fragments,
|
|
68
|
+
* and so that each kind of definition is sorted by name.
|
|
69
|
+
*
|
|
70
|
+
* This is a direct implementation of the sorting logic from @apollo/persisted-query-lists
|
|
71
|
+
* to avoid an external dependency.
|
|
72
|
+
*/
|
|
73
|
+
export function sortTopLevelDefinitions(query) {
|
|
74
|
+
const definitions = [...query.definitions];
|
|
75
|
+
definitions.sort((a, b) => {
|
|
76
|
+
var _a, _b, _c, _d;
|
|
77
|
+
// This is a reverse sort by kind, so that OperationDefinition precedes FragmentDefinition.
|
|
78
|
+
if (a.kind > b.kind) {
|
|
79
|
+
return -1;
|
|
80
|
+
}
|
|
81
|
+
if (a.kind < b.kind) {
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
// Extract the name from each definition
|
|
85
|
+
const aName = a.kind === Kind.OPERATION_DEFINITION || a.kind === Kind.FRAGMENT_DEFINITION
|
|
86
|
+
? ((_b = (_a = a.name) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '')
|
|
87
|
+
: '';
|
|
88
|
+
const bName = b.kind === Kind.OPERATION_DEFINITION || b.kind === Kind.FRAGMENT_DEFINITION
|
|
89
|
+
? ((_d = (_c = b.name) === null || _c === void 0 ? void 0 : _c.value) !== null && _d !== void 0 ? _d : '')
|
|
90
|
+
: '';
|
|
91
|
+
// Sort by name ascending.
|
|
92
|
+
if (aName < bName) {
|
|
93
|
+
return -1;
|
|
94
|
+
}
|
|
95
|
+
if (aName > bName) {
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
return 0;
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
...query,
|
|
102
|
+
definitions,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Generates persisted documents map from all documents.
|
|
107
|
+
*
|
|
108
|
+
* Returns a map of hash -> query body for all operations (not standalone fragments).
|
|
109
|
+
* Each operation includes its fragment dependencies inline.
|
|
110
|
+
*/
|
|
111
|
+
export function generatePersistedDocuments(documents) {
|
|
112
|
+
// Build fragment registry from all documents
|
|
113
|
+
const fragmentMap = new Map();
|
|
114
|
+
for (const doc of documents) {
|
|
115
|
+
if (!doc.document)
|
|
116
|
+
continue;
|
|
117
|
+
for (const def of doc.document.definitions) {
|
|
118
|
+
if (def.kind === Kind.FRAGMENT_DEFINITION) {
|
|
119
|
+
fragmentMap.set(def.name.value, def);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const result = {};
|
|
124
|
+
for (const doc of documents) {
|
|
125
|
+
if (!doc.document)
|
|
126
|
+
continue;
|
|
127
|
+
// Find operations in this document
|
|
128
|
+
const operations = doc.document.definitions.filter((def) => def.kind === Kind.OPERATION_DEFINITION);
|
|
129
|
+
// Skip documents with no operations (fragment-only files)
|
|
130
|
+
if (operations.length === 0)
|
|
131
|
+
continue;
|
|
132
|
+
for (const operation of operations) {
|
|
133
|
+
// Create a document with just this operation
|
|
134
|
+
let operationDoc = {
|
|
135
|
+
kind: Kind.DOCUMENT,
|
|
136
|
+
definitions: [operation],
|
|
137
|
+
};
|
|
138
|
+
// Collect all required fragments (recursively)
|
|
139
|
+
const directRefs = collectFragmentReferences(operationDoc);
|
|
140
|
+
const allFragments = new Set();
|
|
141
|
+
for (const ref of directRefs) {
|
|
142
|
+
collectAllFragmentDeps(ref, fragmentMap, allFragments);
|
|
143
|
+
}
|
|
144
|
+
// Add fragment definitions to the document
|
|
145
|
+
const fragmentDefs = [];
|
|
146
|
+
for (const fragName of allFragments) {
|
|
147
|
+
const frag = fragmentMap.get(fragName);
|
|
148
|
+
if (frag)
|
|
149
|
+
fragmentDefs.push(frag);
|
|
150
|
+
}
|
|
151
|
+
if (fragmentDefs.length > 0) {
|
|
152
|
+
operationDoc = {
|
|
153
|
+
...operationDoc,
|
|
154
|
+
definitions: [...operationDoc.definitions, ...fragmentDefs],
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
// Add __typename to all selection sets (matching Apollo Client behavior)
|
|
158
|
+
const docWithTypename = addTypenameToDocument(operationDoc);
|
|
159
|
+
// Sort definitions (operation first, then fragments alphabetically)
|
|
160
|
+
const sortedDoc = sortTopLevelDefinitions(docWithTypename);
|
|
161
|
+
// Print the document
|
|
162
|
+
const body = print(sortedDoc);
|
|
163
|
+
// Skip empty documents
|
|
164
|
+
if (!body || body.trim() === '')
|
|
165
|
+
continue;
|
|
166
|
+
// Hash is just for the JSON key - gets re-computed by generate-persisted-query-manifest
|
|
167
|
+
const hash = createHash('sha256').update(body).digest('hex');
|
|
168
|
+
result[hash] = body;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return result;
|
|
172
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nmarks/graphql-codegen-per-operation-file-preset",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"description": "GraphQL Code Generator preset for generating one file per operation/fragment",
|
|
5
5
|
"peerDependencies": {
|
|
6
6
|
"graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
|
package/typings/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Types } from '@graphql-codegen/plugin-helpers';
|
|
2
|
+
import { PersistedDocumentsConfig } from './persisted-documents.cjs';
|
|
2
3
|
export type PerOperationFileConfig = {
|
|
3
4
|
/**
|
|
4
5
|
* @description Required, should point to the base schema types file.
|
|
@@ -126,6 +127,34 @@ export type PerOperationFileConfig = {
|
|
|
126
127
|
* ```
|
|
127
128
|
*/
|
|
128
129
|
importTypesNamespace?: string;
|
|
130
|
+
/**
|
|
131
|
+
* @description Optional, enables generation of a persisted-documents.json file
|
|
132
|
+
* that maps operation hashes to their full query bodies (with __typename added).
|
|
133
|
+
* Compatible with Apollo Client's persisted queries.
|
|
134
|
+
*
|
|
135
|
+
* @exampleMarkdown
|
|
136
|
+
* ```ts filename="codegen.ts"
|
|
137
|
+
* import type { CodegenConfig } from '@graphql-codegen/cli';
|
|
138
|
+
*
|
|
139
|
+
* const config: CodegenConfig = {
|
|
140
|
+
* // ...
|
|
141
|
+
* generates: {
|
|
142
|
+
* 'path/to/file.ts': {
|
|
143
|
+
* preset: 'per-operation-file',
|
|
144
|
+
* plugins: ['typescript-operations'],
|
|
145
|
+
* presetConfig: {
|
|
146
|
+
* baseTypesPath: 'types.ts',
|
|
147
|
+
* persistedDocuments: {
|
|
148
|
+
* output: 'build/persisted-documents.json'
|
|
149
|
+
* }
|
|
150
|
+
* },
|
|
151
|
+
* },
|
|
152
|
+
* },
|
|
153
|
+
* };
|
|
154
|
+
* export default config;
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
persistedDocuments?: PersistedDocumentsConfig;
|
|
129
158
|
};
|
|
130
159
|
/**
|
|
131
160
|
* PER-OPERATION-FILE PRESET
|
package/typings/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Types } from '@graphql-codegen/plugin-helpers';
|
|
2
|
+
import { PersistedDocumentsConfig } from './persisted-documents.js';
|
|
2
3
|
export type PerOperationFileConfig = {
|
|
3
4
|
/**
|
|
4
5
|
* @description Required, should point to the base schema types file.
|
|
@@ -126,6 +127,34 @@ export type PerOperationFileConfig = {
|
|
|
126
127
|
* ```
|
|
127
128
|
*/
|
|
128
129
|
importTypesNamespace?: string;
|
|
130
|
+
/**
|
|
131
|
+
* @description Optional, enables generation of a persisted-documents.json file
|
|
132
|
+
* that maps operation hashes to their full query bodies (with __typename added).
|
|
133
|
+
* Compatible with Apollo Client's persisted queries.
|
|
134
|
+
*
|
|
135
|
+
* @exampleMarkdown
|
|
136
|
+
* ```ts filename="codegen.ts"
|
|
137
|
+
* import type { CodegenConfig } from '@graphql-codegen/cli';
|
|
138
|
+
*
|
|
139
|
+
* const config: CodegenConfig = {
|
|
140
|
+
* // ...
|
|
141
|
+
* generates: {
|
|
142
|
+
* 'path/to/file.ts': {
|
|
143
|
+
* preset: 'per-operation-file',
|
|
144
|
+
* plugins: ['typescript-operations'],
|
|
145
|
+
* presetConfig: {
|
|
146
|
+
* baseTypesPath: 'types.ts',
|
|
147
|
+
* persistedDocuments: {
|
|
148
|
+
* output: 'build/persisted-documents.json'
|
|
149
|
+
* }
|
|
150
|
+
* },
|
|
151
|
+
* },
|
|
152
|
+
* },
|
|
153
|
+
* };
|
|
154
|
+
* export default config;
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
persistedDocuments?: PersistedDocumentsConfig;
|
|
129
158
|
};
|
|
130
159
|
/**
|
|
131
160
|
* PER-OPERATION-FILE PRESET
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { DocumentNode, FragmentDefinitionNode } from 'graphql';
|
|
2
|
+
import type { Source } from '@graphql-tools/utils';
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for persisted documents output
|
|
5
|
+
*/
|
|
6
|
+
export interface PersistedDocumentsConfig {
|
|
7
|
+
/**
|
|
8
|
+
* Output path for the persisted-documents.json file
|
|
9
|
+
* (relative to the baseOutputDir or absolute)
|
|
10
|
+
*/
|
|
11
|
+
output: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Adds __typename field to all selection sets matching Apollo Client's InMemoryCache behavior.
|
|
15
|
+
*
|
|
16
|
+
* Rules:
|
|
17
|
+
* - Add __typename at the END of selection sets
|
|
18
|
+
* - Skip root selection set of operations (query/mutation/subscription)
|
|
19
|
+
* - DO add __typename to fragment root selection sets
|
|
20
|
+
*/
|
|
21
|
+
export declare function addTypenameToDocument(doc: DocumentNode): DocumentNode;
|
|
22
|
+
/**
|
|
23
|
+
* Collects all fragment names referenced in a document via FragmentSpread nodes
|
|
24
|
+
*/
|
|
25
|
+
export declare function collectFragmentReferences(doc: DocumentNode | FragmentDefinitionNode): Set<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Recursively collects all fragment dependencies (including nested)
|
|
28
|
+
*/
|
|
29
|
+
export declare function collectAllFragmentDeps(fragmentName: string, fragmentMap: Map<string, FragmentDefinitionNode>, collected?: Set<string>): Set<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Sort the definitions in a document so that operations come before fragments,
|
|
32
|
+
* and so that each kind of definition is sorted by name.
|
|
33
|
+
*
|
|
34
|
+
* This is a direct implementation of the sorting logic from @apollo/persisted-query-lists
|
|
35
|
+
* to avoid an external dependency.
|
|
36
|
+
*/
|
|
37
|
+
export declare function sortTopLevelDefinitions(query: DocumentNode): DocumentNode;
|
|
38
|
+
/**
|
|
39
|
+
* Generates persisted documents map from all documents.
|
|
40
|
+
*
|
|
41
|
+
* Returns a map of hash -> query body for all operations (not standalone fragments).
|
|
42
|
+
* Each operation includes its fragment dependencies inline.
|
|
43
|
+
*/
|
|
44
|
+
export declare function generatePersistedDocuments(documents: Source[]): Record<string, string>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { DocumentNode, FragmentDefinitionNode } from 'graphql';
|
|
2
|
+
import type { Source } from '@graphql-tools/utils';
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for persisted documents output
|
|
5
|
+
*/
|
|
6
|
+
export interface PersistedDocumentsConfig {
|
|
7
|
+
/**
|
|
8
|
+
* Output path for the persisted-documents.json file
|
|
9
|
+
* (relative to the baseOutputDir or absolute)
|
|
10
|
+
*/
|
|
11
|
+
output: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Adds __typename field to all selection sets matching Apollo Client's InMemoryCache behavior.
|
|
15
|
+
*
|
|
16
|
+
* Rules:
|
|
17
|
+
* - Add __typename at the END of selection sets
|
|
18
|
+
* - Skip root selection set of operations (query/mutation/subscription)
|
|
19
|
+
* - DO add __typename to fragment root selection sets
|
|
20
|
+
*/
|
|
21
|
+
export declare function addTypenameToDocument(doc: DocumentNode): DocumentNode;
|
|
22
|
+
/**
|
|
23
|
+
* Collects all fragment names referenced in a document via FragmentSpread nodes
|
|
24
|
+
*/
|
|
25
|
+
export declare function collectFragmentReferences(doc: DocumentNode | FragmentDefinitionNode): Set<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Recursively collects all fragment dependencies (including nested)
|
|
28
|
+
*/
|
|
29
|
+
export declare function collectAllFragmentDeps(fragmentName: string, fragmentMap: Map<string, FragmentDefinitionNode>, collected?: Set<string>): Set<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Sort the definitions in a document so that operations come before fragments,
|
|
32
|
+
* and so that each kind of definition is sorted by name.
|
|
33
|
+
*
|
|
34
|
+
* This is a direct implementation of the sorting logic from @apollo/persisted-query-lists
|
|
35
|
+
* to avoid an external dependency.
|
|
36
|
+
*/
|
|
37
|
+
export declare function sortTopLevelDefinitions(query: DocumentNode): DocumentNode;
|
|
38
|
+
/**
|
|
39
|
+
* Generates persisted documents map from all documents.
|
|
40
|
+
*
|
|
41
|
+
* Returns a map of hash -> query body for all operations (not standalone fragments).
|
|
42
|
+
* Each operation includes its fragment dependencies inline.
|
|
43
|
+
*/
|
|
44
|
+
export declare function generatePersistedDocuments(documents: Source[]): Record<string, string>;
|