Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1003,3 +1003,95 @@ describe('edge cases', () => {
);
});
});

// ============================================================================
// Tests - Table Field Argument Input Types (Bucket-style computed fields)
// ============================================================================

describe('table field argument input types', () => {
it('emits Input types referenced only by table-field args', () => {
const bucketTable = createTable({
name: 'FooBucket',
fields: [
{ name: 'id', type: fieldTypes.uuid },
{
name: 'requestBulkUploadUrls',
type: { gqlType: 'String', isArray: true } as FieldType,
args: [
{
name: 'files',
type: createNonNull(
createList(
createNonNull(
createTypeRef('INPUT_OBJECT', 'FooBulkUploadFileInput'),
),
),
),
isRequired: true,
},
],
},
],
});

const registry = createTypeRegistry({
FooBulkUploadFileInput: {
kind: 'INPUT_OBJECT',
name: 'FooBulkUploadFileInput',
inputFields: [
{
name: 'fileName',
type: createNonNull(createTypeRef('SCALAR', 'String')),
},
{
name: 'contentType',
type: createTypeRef('SCALAR', 'String'),
},
{
name: 'sizeBytes',
type: createNonNull(createTypeRef('SCALAR', 'Int')),
},
],
},
});

const result = generateInputTypesFile(registry, new Set(), [bucketTable]);

expect(result.content).toContain(
'export interface FooBulkUploadFileInput {',
);
expect(result.content).toContain('fileName: string;');
expect(result.content).toContain('contentType?: string;');
expect(result.content).toContain('sizeBytes: number;');
});

it('collectInputTypeNames discovers field-arg types when tables provided', () => {
const bucketTable = createTable({
name: 'FooBucket',
fields: [
{ name: 'id', type: fieldTypes.uuid },
{
name: 'requestBulkUploadUrls',
type: { gqlType: 'String', isArray: true } as FieldType,
args: [
{
name: 'files',
type: createNonNull(
createTypeRef('INPUT_OBJECT', 'FooBulkUploadFileInput'),
),
isRequired: true,
},
],
},
],
});

// Without tables: only collects from operations
const withoutTables = collectInputTypeNames([]);
expect(withoutTables.has('FooBulkUploadFileInput')).toBe(false);

// With tables: also collects from field args
const withTables = collectInputTypeNames([], [bucketTable]);
expect(withTables.has('FooBulkUploadFileInput')).toBe(true);
});
});
2 changes: 1 addition & 1 deletion graphql/codegen/src/core/codegen/orm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export function generateOrm(options: GenerateOrmOptions): GenerateOrmResult {
...(customOperations?.queries ?? []),
...(customOperations?.mutations ?? []),
];
const usedInputTypes = collectInputTypeNames(allOps);
const usedInputTypes = collectInputTypeNames(allOps, tables);
const usedPayloadTypes = collectPayloadTypeNames(allOps);

// Also include payload types for table CRUD mutations (they reference Edge types)
Expand Down
28 changes: 23 additions & 5 deletions graphql/codegen/src/core/codegen/orm/input-types-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1559,19 +1559,21 @@ function generateAllCrudInputTypes(
// ============================================================================

/**
* Collect all input type names used by operations
* Collect all input type names used by operations and table field arguments.
*
* Scans both custom operation args and computed-field args (e.g.
* `requestBulkUploadUrls(files: [FooBulkUploadFileInput!]!)` on bucket
* tables) so that referenced Input types are registered for generation.
*/
export function collectInputTypeNames(
operations: Array<{ args: Argument[] }>,
tables?: Table[],
): Set<string> {
const inputTypes = new Set<string>();

function collectFromTypeRef(typeRef: Argument['type']) {
const baseName = getTypeBaseName(typeRef);
if (baseName && baseName.endsWith('Input')) {
inputTypes.add(baseName);
}
if (baseName && baseName.endsWith('Filter')) {
if (baseName && (baseName.endsWith('Input') || baseName.endsWith('Filter'))) {
inputTypes.add(baseName);
}
}
Expand All @@ -1582,6 +1584,17 @@ export function collectInputTypeNames(
}
}

if (tables) {
for (const table of tables) {
for (const field of table.fields) {
if (!field.args) continue;
for (const arg of field.args) {
collectFromTypeRef(arg.type);
}
}
}
}

return inputTypes;
}

Expand Down Expand Up @@ -2057,6 +2070,7 @@ export function generateInputTypesFile(

// 7. Custom input types from TypeRegistry
// Also include any extra types referenced by plugin-injected filter fields
// and by table field arguments (e.g. bucket computed-field args)
const mergedUsedInputTypes = new Set(usedInputTypes);
if (hasTables) {
const filterExtraTypes = collectFilterExtraInputTypes(
Expand All @@ -2066,6 +2080,10 @@ export function generateInputTypesFile(
for (const typeName of filterExtraTypes) {
mergedUsedInputTypes.add(typeName);
}
const fieldArgTypes = collectInputTypeNames([], tablesList);
for (const typeName of fieldArgTypes) {
mergedUsedInputTypes.add(typeName);
}
}
const tableCrudTypes = tables ? buildTableCrudTypeNames(tables) : undefined;
// Pass customScalarTypes + enumTypes as already-generated to avoid duplicate declarations
Expand Down
Loading