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 @@ -426,6 +426,34 @@ export interface DeleteArgs<TWhere, TSelect = undefined> {
select?: TSelect;
}

export interface BulkInsertArgs<TSelect, TData, TOnConflict = unknown> {
data: TData[];
select?: TSelect;
onConflict?: TOnConflict;
}

export interface BulkUpsertArgs<TSelect, TData, TOnConflict = unknown> {
data: TData[];
select?: TSelect;
onConflict: TOnConflict;
}

export interface BulkUpdateArgs<TSelect, TWhere, TData> {
where: TWhere;
data: TData;
select?: TSelect;
}

export interface BulkDeleteArgs<TSelect, TWhere> {
where: TWhere;
select?: TSelect;
}

export interface BulkMutationResult<T> {
affectedCount: number;
returning: T[];
}

type DepthLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
type DecrementDepth = {
0: 0;
Expand Down
287 changes: 287 additions & 0 deletions graphql/codegen/src/core/codegen/cli/table-command-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1322,6 +1322,275 @@ function buildMutationHandler(
);
}

type BulkCliOp = 'bulk-create' | 'bulk-upsert' | 'bulk-update' | 'bulk-delete';

function buildBulkMutationHandler(
table: Table,
operation: BulkCliOp,
targetName?: string,
): t.FunctionDeclaration {
const { singularName } = getTableNames(table);
const selectObj = buildSelectObject(table);

// Map CLI op name to ORM method name
const ormMethod = (() => {
switch (operation) {
case 'bulk-create': return 'bulkCreate';
case 'bulk-upsert': return 'bulkUpsert';
case 'bulk-update': return 'bulkUpdate';
case 'bulk-delete': return 'bulkDelete';
}
})();

const tryBody: t.Statement[] = [];

if (operation === 'bulk-create' || operation === 'bulk-upsert') {
// Parse --data (JSON array) from argv
tryBody.push(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier('dataRaw'),
t.memberExpression(t.identifier('argv'), t.identifier('data')),
),
]),
);
tryBody.push(
t.ifStatement(
t.unaryExpression('!', t.identifier('dataRaw')),
t.blockStatement([
t.expressionStatement(
t.callExpression(
t.memberExpression(t.identifier('console'), t.identifier('error')),
[t.stringLiteral(`--data is required for ${operation}. Provide a JSON array.`)],
),
),
t.expressionStatement(
t.callExpression(
t.memberExpression(t.identifier('process'), t.identifier('exit')),
[t.numericLiteral(1)],
),
),
]),
),
);
tryBody.push(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier('data'),
t.callExpression(
t.memberExpression(t.identifier('JSON'), t.identifier('parse')),
[t.tsAsExpression(t.identifier('dataRaw'), t.tsStringKeyword())],
),
),
]),
);

let ormArgs: t.ObjectExpression;
if (operation === 'bulk-upsert') {
// Also parse --on-conflict
tryBody.push(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier('onConflictRaw'),
t.memberExpression(t.identifier('argv'), t.identifier('on-conflict')),
),
]),
);
tryBody.push(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier('onConflict'),
t.conditionalExpression(
t.identifier('onConflictRaw'),
t.callExpression(
t.memberExpression(t.identifier('JSON'), t.identifier('parse')),
[t.tsAsExpression(t.identifier('onConflictRaw'), t.tsStringKeyword())],
),
t.objectExpression([]),
),
),
]),
);
ormArgs = t.objectExpression([
t.objectProperty(t.identifier('data'), t.identifier('data')),
t.objectProperty(t.identifier('onConflict'), t.identifier('onConflict')),
t.objectProperty(t.identifier('select'), selectObj),
]);
} else {
ormArgs = t.objectExpression([
t.objectProperty(t.identifier('data'), t.identifier('data')),
t.objectProperty(t.identifier('select'), selectObj),
]);
}

tryBody.push(buildGetClientStatement(targetName));
tryBody.push(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier('result'),
t.awaitExpression(buildOrmCall(singularName, ormMethod, ormArgs)),
),
]),
);
} else if (operation === 'bulk-update') {
// Parse --where (JSON) and --data (JSON)
tryBody.push(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier('whereRaw'),
t.memberExpression(t.identifier('argv'), t.identifier('where')),
),
]),
);
tryBody.push(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier('dataRaw'),
t.memberExpression(t.identifier('argv'), t.identifier('data')),
),
]),
);
tryBody.push(
t.ifStatement(
t.logicalExpression(
'||',
t.unaryExpression('!', t.identifier('whereRaw')),
t.unaryExpression('!', t.identifier('dataRaw')),
),
t.blockStatement([
t.expressionStatement(
t.callExpression(
t.memberExpression(t.identifier('console'), t.identifier('error')),
[t.stringLiteral('--where and --data are required for bulk-update. Provide JSON objects.')],
),
),
t.expressionStatement(
t.callExpression(
t.memberExpression(t.identifier('process'), t.identifier('exit')),
[t.numericLiteral(1)],
),
),
]),
),
);
tryBody.push(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier('where'),
t.callExpression(
t.memberExpression(t.identifier('JSON'), t.identifier('parse')),
[t.tsAsExpression(t.identifier('whereRaw'), t.tsStringKeyword())],
),
),
]),
);
tryBody.push(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier('data'),
t.callExpression(
t.memberExpression(t.identifier('JSON'), t.identifier('parse')),
[t.tsAsExpression(t.identifier('dataRaw'), t.tsStringKeyword())],
),
),
]),
);
tryBody.push(buildGetClientStatement(targetName));
tryBody.push(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier('result'),
t.awaitExpression(
buildOrmCall(singularName, ormMethod, t.objectExpression([
t.objectProperty(t.identifier('where'), t.identifier('where')),
t.objectProperty(t.identifier('data'), t.identifier('data')),
t.objectProperty(t.identifier('select'), selectObj),
])),
),
),
]),
);
} else {
// bulk-delete: parse --where (JSON)
tryBody.push(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier('whereRaw'),
t.memberExpression(t.identifier('argv'), t.identifier('where')),
),
]),
);
tryBody.push(
t.ifStatement(
t.unaryExpression('!', t.identifier('whereRaw')),
t.blockStatement([
t.expressionStatement(
t.callExpression(
t.memberExpression(t.identifier('console'), t.identifier('error')),
[t.stringLiteral('--where is required for bulk-delete. Provide a JSON object.')],
),
),
t.expressionStatement(
t.callExpression(
t.memberExpression(t.identifier('process'), t.identifier('exit')),
[t.numericLiteral(1)],
),
),
]),
),
);
tryBody.push(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier('where'),
t.callExpression(
t.memberExpression(t.identifier('JSON'), t.identifier('parse')),
[t.tsAsExpression(t.identifier('whereRaw'), t.tsStringKeyword())],
),
),
]),
);
tryBody.push(buildGetClientStatement(targetName));
tryBody.push(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier('result'),
t.awaitExpression(
buildOrmCall(singularName, ormMethod, t.objectExpression([
t.objectProperty(t.identifier('where'), t.identifier('where')),
t.objectProperty(t.identifier('select'), selectObj),
])),
),
),
]),
);
}

tryBody.push(buildJsonLog(t.identifier('result')));

const argvParam = t.identifier('argv');
argvParam.typeAnnotation = buildArgvType();
const prompterParam = t.identifier('prompter');
prompterParam.typeAnnotation = t.tsTypeAnnotation(
t.tsTypeReference(t.identifier('Inquirerer')),
);

const handlerName = `handle${toPascalCase(operation)}`;

return t.functionDeclaration(
t.identifier(handlerName),
[argvParam, prompterParam],
t.blockStatement([
t.tryStatement(
t.blockStatement(tryBody),
buildErrorCatch(`Failed to ${operation}.`),
),
]),
false,
true,
);
}

export interface TableCommandOptions {
targetName?: string;
executorImportPath?: string;
Expand Down Expand Up @@ -1439,12 +1708,22 @@ export function generateTableCommand(table: Table, options?: TableCommandOptions
);
}

// Detect bulk mutations
const hasBulkCreate = !!table.query?.bulkInsert;
const hasBulkUpsert = !!table.query?.bulkUpsert;
const hasBulkUpdate = !!table.query?.bulkUpdate;
const hasBulkDelete = !!table.query?.bulkDelete;

const subcommands: string[] = ['list', 'find-first'];
if (hasSearchFields) subcommands.push('search');
if (hasGet) subcommands.push('get');
subcommands.push('create');
if (hasUpdate) subcommands.push('update');
if (hasDelete) subcommands.push('delete');
if (hasBulkCreate) subcommands.push('bulk-create');
if (hasBulkUpsert) subcommands.push('bulk-upsert');
if (hasBulkUpdate) subcommands.push('bulk-update');
if (hasBulkDelete) subcommands.push('bulk-delete');

const usageLines = [
'',
Expand All @@ -1466,6 +1745,10 @@ export function generateTableCommand(table: Table, options?: TableCommandOptions
);
}
if (hasDelete) usageLines.push(` delete Delete a ${singularName}`);
if (hasBulkCreate) usageLines.push(` bulk-create Bulk create ${singularName} records`);
if (hasBulkUpsert) usageLines.push(` bulk-upsert Bulk upsert ${singularName} records`);
if (hasBulkUpdate) usageLines.push(` bulk-update Bulk update ${singularName} records`);
if (hasBulkDelete) usageLines.push(` bulk-delete Bulk delete ${singularName} records`);
usageLines.push(
'',
'List Options:',
Expand Down Expand Up @@ -1684,6 +1967,10 @@ export function generateTableCommand(table: Table, options?: TableCommandOptions
statements.push(buildMutationHandler(table, 'create', vectorFieldNames, tn, options?.typeRegistry, ormTypes));
if (hasUpdate) statements.push(buildMutationHandler(table, 'update', vectorFieldNames, tn, options?.typeRegistry, ormTypes));
if (hasDelete) statements.push(buildMutationHandler(table, 'delete', vectorFieldNames, tn, options?.typeRegistry, ormTypes));
if (hasBulkCreate) statements.push(buildBulkMutationHandler(table, 'bulk-create', tn));
if (hasBulkUpsert) statements.push(buildBulkMutationHandler(table, 'bulk-upsert', tn));
if (hasBulkUpdate) statements.push(buildBulkMutationHandler(table, 'bulk-update', tn));
if (hasBulkDelete) statements.push(buildBulkMutationHandler(table, 'bulk-delete', tn));

const header = getGeneratedFileHeader(`CLI commands for ${table.name}`);
const code = generateCode(statements);
Expand Down
21 changes: 21 additions & 0 deletions graphql/codegen/src/core/codegen/mutation-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,27 @@ function generateEntityMutationKeysDeclaration(
addJSDocComment(deleteProp, [`Delete ${singularName} mutation key`]);
properties.push(deleteProp);

// Bulk mutation keys (only if table has bulk operations)
const bulkOps: Array<{ key: string; queryField: string | null | undefined }> = [
{ key: 'bulkCreate', queryField: table.query?.bulkInsert },
{ key: 'bulkUpsert', queryField: table.query?.bulkUpsert },
{ key: 'bulkUpdate', queryField: table.query?.bulkUpdate },
{ key: 'bulkDelete', queryField: table.query?.bulkDelete },
];
for (const { key, queryField } of bulkOps) {
if (!queryField) continue;
const arrowFn = t.arrowFunctionExpression(
[],
constArray([
t.stringLiteral('mutation'),
t.stringLiteral(entityKey),
t.stringLiteral(key),
]),
);
const prop = t.objectProperty(t.identifier(key), arrowFn);
properties.push(prop);
}

return t.exportNamedDeclaration(
t.variableDeclaration('const', [
t.variableDeclarator(
Expand Down
Loading
Loading