Skip to content

Commit 3005df7

Browse files
ikusakoveddeee888
authored andcommitted
documentsReadOnly implemented
1 parent c1fc1ae commit 3005df7

3 files changed

Lines changed: 240 additions & 35 deletions

File tree

packages/graphql-codegen-cli/src/codegen.ts

Lines changed: 59 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,7 @@ const makeDefaultLoader = (from: string) => {
5252

5353
type Ctx = { errors: Error[] };
5454

55-
function createCache(): <T>(
56-
namespace: string,
57-
key: string,
58-
factory: () => Promise<T>,
59-
) => Promise<T> {
55+
function createCache(): <T>(namespace: string, key: string, factory: () => Promise<T>) => Promise<T> {
6056
const cache = new Map<string, Promise<unknown>>();
6157

6258
return function ensure<T>(namespace: string, key: string, factory: () => Promise<T>): Promise<T> {
@@ -86,6 +82,7 @@ export async function executeCodegen(
8682
let rootConfig: { [key: string]: any } = {};
8783
let rootSchemas: Types.Schema[];
8884
let rootDocuments: Types.OperationDocument[];
85+
let rootDocumentsReadOnly: Types.OperationDocument[];
8986
const generates: { [filename: string]: Types.ConfiguredOutput } = {};
9087

9188
const cache = createCache();
@@ -136,6 +133,9 @@ export async function executeCodegen(
136133
/* Normalize root "documents" field */
137134
rootDocuments = normalizeInstanceOrArray<Types.OperationDocument>(config.documents);
138135

136+
/* Normalize root "documentsReadOnly" field */
137+
rootDocumentsReadOnly = normalizeInstanceOrArray<Types.OperationDocument>(config.documentsReadOnly);
138+
139139
/* Normalize "generators" field */
140140
const generateKeys = Object.keys(config.generates || {});
141141

@@ -181,7 +181,7 @@ export async function executeCodegen(
181181
if (
182182
rootSchemas.length === 0 &&
183183
Object.keys(generates).some(
184-
filename =>
184+
(filename) =>
185185
!generates[filename].schema ||
186186
(Array.isArray(generates[filename].schema === 'object') &&
187187
(generates[filename].schema as unknown as any[]).length === 0),
@@ -216,7 +216,7 @@ export async function executeCodegen(
216216
{
217217
title: 'Generate outputs',
218218
task: (ctx, task) => {
219-
const generateTasks: ListrTask<Ctx>[] = Object.keys(generates).map(filename => {
219+
const generateTasks: ListrTask<Ctx>[] = Object.keys(generates).map((filename) => {
220220
const outputConfig = generates[filename];
221221
const hasPreset = !!outputConfig.preset;
222222

@@ -229,12 +229,14 @@ export async function executeCodegen(
229229
let outputSchema: DocumentNode;
230230
const outputFileTemplateConfig = outputConfig.config || {};
231231
let outputDocuments: Types.DocumentFile[] = [];
232-
const outputSpecificSchemas = normalizeInstanceOrArray<Types.Schema>(
233-
outputConfig.schema,
234-
);
232+
let outputDocumentsReadOnly: Types.DocumentFile[] = [];
233+
const outputSpecificSchemas = normalizeInstanceOrArray<Types.Schema>(outputConfig.schema);
235234
let outputSpecificDocuments = normalizeInstanceOrArray<Types.OperationDocument>(
236235
outputConfig.documents,
237236
);
237+
const outputSpecificDocumentsReadOnly = normalizeInstanceOrArray<Types.OperationDocument>(
238+
outputConfig.documentsReadOnly,
239+
);
238240

239241
const preset: Types.OutputPreset | null = hasPreset
240242
? typeof outputConfig.preset === 'string'
@@ -243,10 +245,7 @@ export async function executeCodegen(
243245
: null;
244246

245247
if (preset?.prepareDocuments) {
246-
outputSpecificDocuments = await preset.prepareDocuments(
247-
filename,
248-
outputSpecificDocuments,
249-
);
248+
outputSpecificDocuments = await preset.prepareDocuments(filename, outputSpecificDocuments);
250249
}
251250

252251
return subTask.newListr(
@@ -258,10 +257,7 @@ export async function executeCodegen(
258257
debugLog(`[CLI] Loading Schemas`);
259258
const schemaPointerMap: any = {};
260259
const parsedSchemas: GraphQLSchema[] = [];
261-
const allSchemaDenormalizedPointers = [
262-
...rootSchemas,
263-
...outputSpecificSchemas,
264-
];
260+
const allSchemaDenormalizedPointers = [...rootSchemas, ...outputSpecificSchemas];
265261

266262
for (const denormalizedPtr of allSchemaDenormalizedPointers) {
267263
if (isSchema(denormalizedPtr)) {
@@ -274,8 +270,7 @@ export async function executeCodegen(
274270
}
275271

276272
const hash =
277-
JSON.stringify(schemaPointerMap) +
278-
parsedSchemas.map(getJsObjectId).join(',');
273+
JSON.stringify(schemaPointerMap) + parsedSchemas.map(getJsObjectId).join(',');
279274
const result = await cache('schema', hash, async () => {
280275
// collect parsed schemas
281276
const schemasToMerge: GraphQLSchema[] = [...parsedSchemas];
@@ -329,10 +324,7 @@ export async function executeCodegen(
329324
documents,
330325
};
331326
} catch (error) {
332-
if (
333-
error instanceof NoTypeDefinitionsFound &&
334-
config.ignoreNoDocuments
335-
) {
327+
if (error instanceof NoTypeDefinitionsFound && config.ignoreNoDocuments) {
336328
return {
337329
documents: [],
338330
};
@@ -343,6 +335,41 @@ export async function executeCodegen(
343335
});
344336

345337
outputDocuments = result.documents;
338+
339+
const allReadOnlyDenormalizedPointers = [
340+
...rootDocumentsReadOnly,
341+
...outputSpecificDocumentsReadOnly,
342+
];
343+
344+
if (allReadOnlyDenormalizedPointers.length > 0) {
345+
const readOnlyPointerMap: any = {};
346+
for (const denormalizedPtr of allReadOnlyDenormalizedPointers) {
347+
if (typeof denormalizedPtr === 'string') {
348+
readOnlyPointerMap[denormalizedPtr] = {};
349+
} else if (typeof denormalizedPtr === 'object') {
350+
Object.assign(readOnlyPointerMap, denormalizedPtr);
351+
}
352+
}
353+
354+
const readOnlyHash = JSON.stringify(readOnlyPointerMap);
355+
const readOnlyResult = await cache(
356+
'documents-read-only',
357+
readOnlyHash,
358+
async () => {
359+
try {
360+
const documents = await context.loadDocuments(readOnlyPointerMap);
361+
return { documents };
362+
} catch (error) {
363+
if (error instanceof NoTypeDefinitionsFound && config.ignoreNoDocuments) {
364+
return { documents: [] };
365+
}
366+
throw error;
367+
}
368+
},
369+
);
370+
371+
outputDocumentsReadOnly = readOnlyResult.documents;
372+
}
346373
},
347374
filename,
348375
`Load GraphQL documents: ${filename}`,
@@ -356,10 +383,9 @@ export async function executeCodegen(
356383
debugLog(`[CLI] Generating output`);
357384
const normalizedPluginsArray = normalizeConfig(outputConfig.plugins);
358385

359-
const pluginLoader =
360-
config.pluginLoader || makeDefaultLoader(context.cwd);
386+
const pluginLoader = config.pluginLoader || makeDefaultLoader(context.cwd);
361387
const pluginPackages = await Promise.all(
362-
normalizedPluginsArray.map(plugin =>
388+
normalizedPluginsArray.map((plugin) =>
363389
getPluginByName(Object.keys(plugin)[0], pluginLoader),
364390
),
365391
);
@@ -391,8 +417,7 @@ export async function executeCodegen(
391417
const mergedConfig = {
392418
...rawMergedConfig,
393419
importExtension,
394-
emitLegacyCommonJSImports:
395-
rawMergedConfig.emitLegacyCommonJSImports ?? true,
420+
emitLegacyCommonJSImports: rawMergedConfig.emitLegacyCommonJSImports ?? true,
396421
};
397422

398423
const documentTransforms = Array.isArray(outputConfig.documentTransforms)
@@ -417,6 +442,7 @@ export async function executeCodegen(
417442
schema: outputSchema,
418443
schemaAst: outputSchemaAst,
419444
documents: outputDocuments,
445+
documentsReadOnly: outputDocumentsReadOnly,
420446
config: mergedConfig,
421447
pluginMap,
422448
pluginContext,
@@ -432,6 +458,7 @@ export async function executeCodegen(
432458
schema: outputSchema,
433459
schemaAst: outputSchemaAst,
434460
documents: outputDocuments,
461+
documentsReadOnly: outputDocumentsReadOnly,
435462
config: mergedConfig,
436463
pluginMap,
437464
pluginContext,
@@ -444,8 +471,7 @@ export async function executeCodegen(
444471
const output = await codegen({
445472
...outputArgs,
446473
importExtension,
447-
emitLegacyCommonJSImports:
448-
rawMergedConfig.emitLegacyCommonJSImports ?? true,
474+
emitLegacyCommonJSImports: rawMergedConfig.emitLegacyCommonJSImports ?? true,
449475
cache,
450476
});
451477
result.push({
@@ -516,12 +542,10 @@ export async function executeCodegen(
516542

517543
let error: Error | null = null;
518544
if (executedContext.errors.length > 0) {
519-
const errors = executedContext.errors.map(subErr => subErr.message || subErr.toString());
545+
const errors = executedContext.errors.map((subErr) => subErr.message || subErr.toString());
520546
error = new AggregateError(executedContext.errors, String(errors.join('\n\n')));
521547
// Best-effort to all stack traces for debugging
522-
error.stack = `${error.stack}\n\n${executedContext.errors
523-
.map(subErr => subErr.stack)
524-
.join('\n\n')}`;
548+
error.stack = `${error.stack}\n\n${executedContext.errors.map((subErr) => subErr.stack).join('\n\n')}`;
525549
}
526550

527551
return { result, error };

packages/graphql-codegen-cli/tests/codegen.spec.ts

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1486,6 +1486,167 @@ describe('Codegen Executor', () => {
14861486
});
14871487
});
14881488

1489+
describe('documentsReadOnly', () => {
1490+
it('should pass documentsReadOnly to preset buildGeneratesSection', async () => {
1491+
let capturedDocumentsReadOnly: Types.DocumentFile[] | undefined;
1492+
1493+
const capturePreset: Types.OutputPreset = {
1494+
buildGeneratesSection: options => {
1495+
capturedDocumentsReadOnly = options.documentsReadOnly;
1496+
return [
1497+
{
1498+
filename: 'out1/result.ts',
1499+
pluginMap: { typescript: require('@graphql-codegen/typescript') },
1500+
plugins: [{ typescript: {} }],
1501+
schema: options.schema,
1502+
documents: options.documents,
1503+
config: options.config,
1504+
},
1505+
];
1506+
},
1507+
};
1508+
1509+
await executeCodegen({
1510+
schema: SIMPLE_TEST_SCHEMA,
1511+
documents: `query root { f }`,
1512+
documentsReadOnly: `fragment Frag on MyType { f }`,
1513+
generates: {
1514+
'out1/': { preset: capturePreset },
1515+
},
1516+
});
1517+
1518+
expect(capturedDocumentsReadOnly).toBeDefined();
1519+
expect(capturedDocumentsReadOnly).toHaveLength(1);
1520+
});
1521+
1522+
it('should not include documentsReadOnly content in regular documents', async () => {
1523+
let capturedDocuments: Types.DocumentFile[] | undefined;
1524+
let capturedDocumentsReadOnly: Types.DocumentFile[] | undefined;
1525+
1526+
const capturePreset: Types.OutputPreset = {
1527+
buildGeneratesSection: options => {
1528+
capturedDocuments = options.documents;
1529+
capturedDocumentsReadOnly = options.documentsReadOnly;
1530+
return [
1531+
{
1532+
filename: 'out1/result.ts',
1533+
pluginMap: { typescript: require('@graphql-codegen/typescript') },
1534+
plugins: [{ typescript: {} }],
1535+
schema: options.schema,
1536+
documents: options.documents,
1537+
config: options.config,
1538+
},
1539+
];
1540+
},
1541+
};
1542+
1543+
await executeCodegen({
1544+
schema: SIMPLE_TEST_SCHEMA,
1545+
documents: `query root { f }`,
1546+
documentsReadOnly: `query readOnlyQuery { f }`,
1547+
generates: {
1548+
'out1/': { preset: capturePreset },
1549+
},
1550+
});
1551+
1552+
expect(capturedDocuments).toHaveLength(1);
1553+
expect(capturedDocumentsReadOnly).toHaveLength(1);
1554+
1555+
const documentNames = capturedDocuments.flatMap(
1556+
d => d.document?.definitions.map((def: any) => def.name?.value) ?? []
1557+
);
1558+
const readOnlyNames = capturedDocumentsReadOnly.flatMap(
1559+
d => d.document?.definitions.map((def: any) => def.name?.value) ?? []
1560+
);
1561+
1562+
expect(documentNames).toContain('root');
1563+
expect(documentNames).not.toContain('readOnlyQuery');
1564+
expect(readOnlyNames).toContain('readOnlyQuery');
1565+
expect(readOnlyNames).not.toContain('root');
1566+
});
1567+
1568+
it('should not include documentsReadOnly operations in non-preset plugin output', async () => {
1569+
const { result } = await executeCodegen({
1570+
schema: SIMPLE_TEST_SCHEMA,
1571+
documents: `query root { f }`,
1572+
documentsReadOnly: `query readOnlyQuery { f }`,
1573+
generates: {
1574+
'out1.ts': { plugins: ['typescript-operations'] },
1575+
},
1576+
});
1577+
1578+
expect(result).toHaveLength(1);
1579+
// Only the regular document operation should be generated
1580+
expect(result[0].content).toContain('RootQuery');
1581+
expect(result[0].content).not.toContain('ReadOnlyQuery');
1582+
});
1583+
1584+
it('should support output-level documentsReadOnly', async () => {
1585+
let capturedDocumentsReadOnly: Types.DocumentFile[] | undefined;
1586+
1587+
const capturePreset: Types.OutputPreset = {
1588+
buildGeneratesSection: options => {
1589+
capturedDocumentsReadOnly = options.documentsReadOnly;
1590+
return [
1591+
{
1592+
filename: 'out1/result.ts',
1593+
pluginMap: { typescript: require('@graphql-codegen/typescript') },
1594+
plugins: [{ typescript: {} }],
1595+
schema: options.schema,
1596+
documents: options.documents,
1597+
config: options.config,
1598+
},
1599+
];
1600+
},
1601+
};
1602+
1603+
await executeCodegen({
1604+
schema: SIMPLE_TEST_SCHEMA,
1605+
generates: {
1606+
'out1/': {
1607+
preset: capturePreset,
1608+
documentsReadOnly: `fragment Frag on MyType { f }`,
1609+
},
1610+
},
1611+
});
1612+
1613+
expect(capturedDocumentsReadOnly).toHaveLength(1);
1614+
});
1615+
1616+
it('should merge root and output-level documentsReadOnly', async () => {
1617+
let capturedDocumentsReadOnly: Types.DocumentFile[] | undefined;
1618+
1619+
const capturePreset: Types.OutputPreset = {
1620+
buildGeneratesSection: options => {
1621+
capturedDocumentsReadOnly = options.documentsReadOnly;
1622+
return [
1623+
{
1624+
filename: 'out1/result.ts',
1625+
pluginMap: { typescript: require('@graphql-codegen/typescript') },
1626+
plugins: [{ typescript: {} }],
1627+
schema: options.schema,
1628+
documents: options.documents,
1629+
config: options.config,
1630+
},
1631+
];
1632+
},
1633+
};
1634+
1635+
await executeCodegen({
1636+
schema: SIMPLE_TEST_SCHEMA,
1637+
documentsReadOnly: `fragment RootFrag on MyType { f }`,
1638+
generates: {
1639+
'out1/': {
1640+
preset: capturePreset,
1641+
documentsReadOnly: `fragment OutputFrag on MyType { f }`,
1642+
},
1643+
},
1644+
});
1645+
1646+
expect(capturedDocumentsReadOnly).toHaveLength(2);
1647+
});
1648+
});
1649+
14891650
it('should not run out of memory when generating very complex types (issue #7720)', async () => {
14901651
const { result } = await executeCodegen({
14911652
schema: ['../../dev-test/gatsby/schema.graphql'],

0 commit comments

Comments
 (0)