diff --git a/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap b/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap index ddb5f51ad..9ab1b715c 100644 --- a/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap +++ b/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap @@ -1740,6 +1740,7 @@ import { useCarsQuery } from './hooks'; // Query hooks: useQuery, usesQuery // Mutation hooks: useCreateMutation, useUpdateMutation, useDeleteMutation +// Bulk mutation hooks (when enabled): useBulkCreateMutation, useBulkUpsertMutation, etc. const { data, isLoading } = useCarsQuery({ selection: { fields: { id: true } }, diff --git a/graphql/codegen/src/core/codegen/cli/docs-generator.ts b/graphql/codegen/src/core/codegen/cli/docs-generator.ts index 023eb3210..18b3ce4ef 100644 --- a/graphql/codegen/src/core/codegen/cli/docs-generator.ts +++ b/graphql/codegen/src/core/codegen/cli/docs-generator.ts @@ -139,6 +139,18 @@ export function generateReadme( lines.push(`| \`create\` | Create a new ${singularName} |`); lines.push(`| \`update\` | Update an existing ${singularName} |`); lines.push(`| \`delete\` | Delete a ${singularName} |`); + if (table.query?.bulkInsert) { + lines.push(`| \`bulk-create\` | Bulk create ${singularName} records |`); + } + if (table.query?.bulkUpsert) { + lines.push(`| \`bulk-upsert\` | Bulk upsert ${singularName} records |`); + } + if (table.query?.bulkUpdate) { + lines.push(`| \`bulk-update\` | Bulk update ${singularName} records |`); + } + if (table.query?.bulkDelete) { + lines.push(`| \`bulk-delete\` | Bulk delete ${singularName} records |`); + } lines.push(''); lines.push('**Fields:**'); lines.push(''); @@ -465,6 +477,10 @@ export function generateSkills( `${toolName} ${kebab} create ${createFlags}`, `${toolName} ${kebab} update --${pk.name} <${cleanTypeName(pk.gqlType)}> ${editableFields.map((f) => `[--${f.name} <${cleanTypeName(f.type.gqlType)}>]`).join(' ')}`, `${toolName} ${kebab} delete --${pk.name} <${cleanTypeName(pk.gqlType)}>`, + ...(table.query?.bulkInsert ? [`${toolName} ${kebab} bulk-create --data ''`] : []), + ...(table.query?.bulkUpsert ? [`${toolName} ${kebab} bulk-upsert --data ''`] : []), + ...(table.query?.bulkUpdate ? [`${toolName} ${kebab} bulk-update --where '' --data ''`] : []), + ...(table.query?.bulkDelete ? [`${toolName} ${kebab} bulk-delete --where ''`] : []), ], examples: [ { @@ -797,6 +813,18 @@ export function generateMultiTargetReadme( lines.push(`| \`create\` | Create a new ${singularName} |`); lines.push(`| \`update\` | Update an existing ${singularName} |`); lines.push(`| \`delete\` | Delete a ${singularName} |`); + if (table.query?.bulkInsert) { + lines.push(`| \`bulk-create\` | Bulk create ${singularName} records |`); + } + if (table.query?.bulkUpsert) { + lines.push(`| \`bulk-upsert\` | Bulk upsert ${singularName} records |`); + } + if (table.query?.bulkUpdate) { + lines.push(`| \`bulk-update\` | Bulk update ${singularName} records |`); + } + if (table.query?.bulkDelete) { + lines.push(`| \`bulk-delete\` | Bulk delete ${singularName} records |`); + } lines.push(''); lines.push('**Fields:**'); lines.push(''); diff --git a/graphql/codegen/src/core/codegen/hooks-docs-generator.ts b/graphql/codegen/src/core/codegen/hooks-docs-generator.ts index 15b558e34..b99fff6d9 100644 --- a/graphql/codegen/src/core/codegen/hooks-docs-generator.ts +++ b/graphql/codegen/src/core/codegen/hooks-docs-generator.ts @@ -22,6 +22,10 @@ import { getCreateMutationHookName, getUpdateMutationHookName, getDeleteMutationHookName, + getBulkCreateMutationHookName, + getBulkUpsertMutationHookName, + getBulkUpdateMutationHookName, + getBulkDeleteMutationHookName, hasValidPrimaryKey, ucFirst, lcFirst, @@ -91,6 +95,26 @@ export function generateHooksReadme( `| \`${getDeleteMutationHookName(table)}\` | Mutation | ${table.description || `Delete a ${singularName}`} |`, ); } + if (table.query?.bulkInsert) { + lines.push( + `| \`${getBulkCreateMutationHookName(table)}\` | Mutation | Bulk create ${pluralName} |`, + ); + } + if (table.query?.bulkUpsert) { + lines.push( + `| \`${getBulkUpsertMutationHookName(table)}\` | Mutation | Bulk upsert ${pluralName} |`, + ); + } + if (table.query?.bulkUpdate) { + lines.push( + `| \`${getBulkUpdateMutationHookName(table)}\` | Mutation | Bulk update ${pluralName} |`, + ); + } + if (table.query?.bulkDelete) { + lines.push( + `| \`${getBulkDeleteMutationHookName(table)}\` | Mutation | Bulk delete ${pluralName} |`, + ); + } } for (const op of customOperations) { lines.push( @@ -145,6 +169,20 @@ export function generateHooksReadme( lines.push( `create({ ${scalarFields.filter((f) => f.name !== pk.name && f.name !== 'nodeId' && f.name !== 'createdAt' && f.name !== 'updatedAt').map((f) => `${f.name}: ${fieldPlaceholder(f)}`).join(', ')} });`, ); + if (table.query?.bulkInsert) { + lines.push(''); + lines.push(`// Bulk create ${pluralName}`); + lines.push( + `const { mutate: bulkCreate } = ${getBulkCreateMutationHookName(table)}({`, + ); + lines.push( + ` selection: { fields: { ${pk.name}: true } },`, + ); + lines.push('});'); + lines.push( + `bulkCreate({ data: [{ ${scalarFields.filter((f) => f.name !== pk.name && f.name !== 'nodeId' && f.name !== 'createdAt' && f.name !== 'updatedAt').map((f) => `${f.name}: ${fieldPlaceholder(f)}`).join(', ')} }] });`, + ); + } lines.push('```'); lines.push(''); } @@ -226,6 +264,7 @@ export function generateHooksAgentsDocs( lines.push(''); lines.push('- Query hooks: `useQuery`, `useQuery`'); lines.push('- Mutation hooks: `useCreateMutation`, `useUpdateMutation`, `useDeleteMutation`'); + lines.push('- Bulk mutation hooks (when enabled): `useBulkCreateMutation`, `useBulkUpsertMutation`, `useBulkUpdateMutation`, `useBulkDeleteMutation`'); lines.push('- All hooks accept a `selection` parameter to pick fields'); lines.push(''); @@ -282,6 +321,10 @@ export function generateHooksSkills( `${getDeleteMutationHookName(table)}({})`, ] : []), + ...(table.query?.bulkInsert ? [`${getBulkCreateMutationHookName(table)}() — bulk create with data array`] : []), + ...(table.query?.bulkUpsert ? [`${getBulkUpsertMutationHookName(table)}() — bulk upsert with onConflict`] : []), + ...(table.query?.bulkUpdate ? [`${getBulkUpdateMutationHookName(table)}() — bulk update with where + data`] : []), + ...(table.query?.bulkDelete ? [`${getBulkDeleteMutationHookName(table)}() — bulk delete with where`] : []), ], examples: [ { @@ -365,6 +408,7 @@ export function generateHooksSkills( '', `// Query hooks: useQuery, usesQuery`, `// Mutation hooks: useCreateMutation, useUpdateMutation, useDeleteMutation`, + `// Bulk mutation hooks (when enabled): useBulkCreateMutation, useBulkUpsertMutation, etc.`, '', `const { data, isLoading } = ${hookExamples[0] || 'useModelQuery'}({`, ` selection: { fields: { id: true } },`, diff --git a/graphql/codegen/src/core/codegen/orm/docs-generator.ts b/graphql/codegen/src/core/codegen/orm/docs-generator.ts index fe2827329..3ca6e9e39 100644 --- a/graphql/codegen/src/core/codegen/orm/docs-generator.ts +++ b/graphql/codegen/src/core/codegen/orm/docs-generator.ts @@ -50,8 +50,14 @@ export function generateOrmReadme( lines.push('|-------|------------|'); for (const table of tables) { const { singularName } = getTableNames(table); + const bulkOps: string[] = []; + if (table.query?.bulkInsert) bulkOps.push('bulkCreate'); + if (table.query?.bulkUpsert) bulkOps.push('bulkUpsert'); + if (table.query?.bulkUpdate) bulkOps.push('bulkUpdate'); + if (table.query?.bulkDelete) bulkOps.push('bulkDelete'); + const ops = ['findMany', 'findOne', 'create', 'update', 'delete', ...bulkOps].join(', '); lines.push( - `| \`${singularName}\` | findMany, findOne, create, update, delete |`, + `| \`${singularName}\` | ${ops} |`, ); } lines.push(''); @@ -108,6 +114,34 @@ export function generateOrmReadme( lines.push( `const deleted = await db.${singularName}.delete({ where: { ${pk.name}: ${pkPlaceholder(pk)} } }).execute();`, ); + if (table.query?.bulkInsert) { + lines.push(''); + lines.push(`// Bulk Create`); + lines.push( + `const bulkCreated = await db.${singularName}.bulkCreate({ data: [{ ${editableFields.map((f) => `${f.name}: ${fieldPlaceholder(f)}`).join(', ')} }], select: { ${pk.name}: true } }).execute();`, + ); + } + if (table.query?.bulkUpsert) { + lines.push(''); + lines.push(`// Bulk Upsert`); + lines.push( + `const bulkUpserted = await db.${singularName}.bulkUpsert({ data: [{ ${editableFields.map((f) => `${f.name}: ${fieldPlaceholder(f)}`).join(', ')} }], onConflict: { constraint: 'PRIMARY_KEY', action: 'UPDATE' }, select: { ${pk.name}: true } }).execute();`, + ); + } + if (table.query?.bulkUpdate) { + lines.push(''); + lines.push(`// Bulk Update`); + lines.push( + `const bulkUpdated = await db.${singularName}.bulkUpdate({ where: { ${editableFields[0]?.name || 'field'}: { equalTo: ${editableFields[0] ? fieldPlaceholder(editableFields[0]) : "''"} } }, data: { ${editableFields[0]?.name || 'field'}: ${editableFields[0] ? fieldPlaceholder(editableFields[0]) : "''"} }, select: { ${pk.name}: true } }).execute();`, + ); + } + if (table.query?.bulkDelete) { + lines.push(''); + lines.push(`// Bulk Delete`); + lines.push( + `const bulkDeleted = await db.${singularName}.bulkDelete({ where: { ${pk.name}: { equalTo: ${pkPlaceholder(pk)} } } }).execute();`, + ); + } lines.push('```'); lines.push(''); const ormSpecialGroups = categorizeSpecialFields(table); @@ -227,6 +261,7 @@ export function generateOrmAgentsDocs( lines.push(''); lines.push('- Access models via `db.` (e.g. `db.User`)'); lines.push('- CRUD methods: `findMany`, `findOne`, `create`, `update`, `delete`'); + lines.push('- Bulk methods (when enabled via smart tags): `bulkCreate`, `bulkUpsert`, `bulkUpdate`, `bulkDelete`'); lines.push('- Chain `.execute().unwrap()` to run and throw on error, or `.execute()` alone for discriminated union result'); lines.push('- Custom operations via `db.query.` or `db.mutation.`'); lines.push(''); @@ -269,6 +304,20 @@ export function generateOrmSkills( ormSkillSpecialGroups.map((g) => `**${g.label}:** ${g.fields.map((f) => `\`${f.name}\``).join(', ')}\n${g.description}`).join('\n\n') : ormSkillBaseDesc; + const bulkUsageLines: string[] = []; + if (table.query?.bulkInsert) { + bulkUsageLines.push(`db.${modelName}.bulkCreate({ data: [...], select: { id: true } }).execute()`); + } + if (table.query?.bulkUpsert) { + bulkUsageLines.push(`db.${modelName}.bulkUpsert({ data: [...], onConflict: { constraint: '...', action: 'UPDATE' }, select: { id: true } }).execute()`); + } + if (table.query?.bulkUpdate) { + bulkUsageLines.push(`db.${modelName}.bulkUpdate({ where: {...}, data: {...}, select: { id: true } }).execute()`); + } + if (table.query?.bulkDelete) { + bulkUsageLines.push(`db.${modelName}.bulkDelete({ where: {...} }).execute()`); + } + files.push({ fileName: `${skillName}/references/${refName}.md`, content: buildSkillReference({ @@ -281,6 +330,7 @@ export function generateOrmSkills( `db.${modelName}.create({ data: { ${editableFields.map((f) => `${f.name}: ${fieldPlaceholder(f)}`).join(', ')} }, select: { id: true } }).execute()`, `db.${modelName}.update({ where: { ${pk.name}: ${pkPlaceholder(pk)} }, data: { ${editableFields[0]?.name || 'field'}: ${editableFields[0] ? fieldPlaceholder(editableFields[0]) : "''"} }, select: { id: true } }).execute()`, `db.${modelName}.delete({ where: { ${pk.name}: ${pkPlaceholder(pk)} } }).execute()`, + ...bulkUsageLines, ], examples: [ { @@ -335,6 +385,7 @@ export function generateOrmSkills( // Generate the overview SKILL.md const tableNames = tables.map((t) => lcFirst(getTableNames(t).singularName)); + const hasBulkTables = tables.some((t) => t.query?.bulkInsert || t.query?.bulkUpsert || t.query?.bulkUpdate || t.query?.bulkDelete); files.push({ fileName: `${skillName}/SKILL.md`, content: buildSkillFile( @@ -352,6 +403,14 @@ export function generateOrmSkills( `db..create({ data: { ... }, select: { id: true } }).execute()`, `db..update({ where: { id: '' }, data: { ... }, select: { id: true } }).execute()`, `db..delete({ where: { id: '' } }).execute()`, + ...(hasBulkTables ? [ + '', + `// Bulk operations (on tables with @behavior +bulkInsert/+bulkUpsert/+bulkUpdate/+bulkDelete)`, + `db..bulkCreate({ data: [...], select: { id: true } }).execute()`, + `db..bulkUpsert({ data: [...], onConflict: { constraint: '...', action: 'UPDATE' }, select: { id: true } }).execute()`, + `db..bulkUpdate({ where: {...}, data: {...}, select: { id: true } }).execute()`, + `db..bulkDelete({ where: {...} }).execute()`, + ] : []), ], examples: [ {