Skip to content

Add multi-mutation bulk execute (bulkOperationRunMutations) with plan files and validation#8076

Draft
nickwesselman wants to merge 1 commit into
mainfrom
bulk_mutations_poc
Draft

Add multi-mutation bulk execute (bulkOperationRunMutations) with plan files and validation#8076
nickwesselman wants to merge 1 commit into
mainfrom
bulk_mutations_poc

Conversation

@nickwesselman

Copy link
Copy Markdown
Contributor

What

Extends shopify app bulk execute to run multi-step bulk mutation plans via the unstable bulkOperationRunMutations mutation, alongside the existing single-mutation bulkOperationRunMutation path.

Status: draft / POC. Depends on the unstable bulkOperationRunMutations Admin mutation (server-side work in progress).

Changes

  • Plan-file input (--plan-file): an ordered list of {mutationFile, variableFile} operations. Order is significant — cross-operation $ref:<Op>[<key>].<path> values resolve against earlier operations server-side.
  • Hybrid routing: a single-operation plan uses bulkOperationRunMutation; two or more operations use bulkOperationRunMutations.
  • Client-side validation (on by default, --no-validate to skip): introspects the store's Admin schema and validates each operation's document plus a representative JSONL row (required variables + input-field coercion), skipping coercion for $ref rows. Mirrors the server's synchronous structural checks; full $ref resolution and per-row validation stay server-side.
  • Sequential staging for the multi-op path — each upload renders its own progress task, and concurrent interactive renders clobber each other (which surfaced as an upload with no response).
  • Generated GraphQL types and the oclif manifest / README regenerated.

Example

shopify app bulk execute --plan-file plan/plan.json --store my-store --version unstable --watch

plan/plan.json:

[
  { "mutationFile": "ProductSet.graphql",         "variableFile": "ProductSet.jsonl" },
  { "mutationFile": "PublishablePublish.graphql", "variableFile": "PublishablePublish.jsonl" }
]

Tests

  • cli-kit bulk-operations + app bulk-operations suites (run-mutations, validate, plan, execute) — 177 passing.
  • type-check + lint green for cli-kit and app.

🤖 Generated with Claude Code

… files and validation

Extends `shopify app bulk execute` to run multi-step bulk mutation plans via the
unstable `bulkOperationRunMutations` mutation, alongside the existing single-mutation
`bulkOperationRunMutation` path.

- Plan-file input (`--plan-file`): an ordered list of {mutationFile, variableFile}
  operations. Order is significant — cross-operation `$ref:<Op>[<key>].<path>` values
  resolve against earlier operations server-side.
- Hybrid routing: a single-operation plan uses `bulkOperationRunMutation`; two or more
  operations use `bulkOperationRunMutations`.
- Client-side validation (on by default, `--no-validate` to skip): introspects the
  store's Admin schema and validates each operation's document plus a representative
  JSONL row (required variables + input-field coercion), skipping coercion for `$ref` rows.
- Staged uploads for the multi-op path run sequentially (each renders its own progress
  task; concurrent interactive renders clobber each other).
- Generated GraphQL types and oclif manifest / README regenerated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added the no-changelog This PR doesn't include a changeset entry. Is an internal only change not relevant to end users. label Jul 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Differences in type declarations

We detected differences in the type declarations generated by Typescript for this branch compared to the baseline ('main' branch). Please, review them to ensure they are backward-compatible. Here are some important things to keep in mind:

  • Some seemingly private modules might be re-exported through public modules.
  • If the branch is behind main you might see odd diffs, rebase main into this branch.

New type declarations

packages/cli-kit/dist/cli/api/graphql/bulk-operations/generated/bulk-operation-run-mutations.d.ts
import * as Types from './types.js';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
export type BulkOperationRunMutationsMutationVariables = Types.Exact<{
    operations: Types.BulkMutationOperationInput[] | Types.BulkMutationOperationInput;
}>;
export type BulkOperationRunMutationsMutation = {
    bulkOperationRunMutations?: {
        bulkOperation?: {
            type: Types.BulkOperationType;
            completedAt?: unknown | null;
            createdAt: unknown;
            errorCode?: Types.BulkOperationErrorCode | null;
            id: string;
            objectCount: unknown;
            partialDataUrl?: string | null;
            status: Types.BulkOperationStatus;
            url?: string | null;
        } | null;
        userErrors: {
            code?: Types.BulkOperationRunMutationsUserErrorCode | null;
            field?: string[] | null;
            message: string;
        }[];
    } | null;
};
export declare const BulkOperationRunMutations: DocumentNode<BulkOperationRunMutationsMutation, BulkOperationRunMutationsMutationVariables>;
packages/cli-kit/dist/public/node/api/bulk-operations/run-mutations.d.ts
import { BulkOperationRunMutationsMutation } from '../../../../cli/api/graphql/bulk-operations/generated/bulk-operation-run-mutations.js';
import { AdminSession } from '../../session.js';
/**
 * One operation within a `bulkOperationRunMutations` plan: a named GraphQL mutation document plus
 * the JSONL variables (one row per invocation) that operation runs over.
 */
export interface BulkMutationPlanOperation {
    mutation: string;
    variablesJsonl: string;
}
interface BulkOperationRunMutationsOptions {
    adminSession: AdminSession;
    operations: BulkMutationPlanOperation[];
    version?: string;
}
/**
 * Stages each operation's JSONL variables file, then starts a single bulk mutation *plan* on the
 * store via `bulkOperationRunMutations`. The returned bulk operation is the plan parent; each
 * operation runs as a hidden child through the existing bulk-mutation pipeline.
 *
 * Operations are staged sequentially, preserving the caller's order (order is significant: `$ref`
 * dependencies resolve against earlier operations). Sequential — not parallel — because each upload
 * renders its own progress task, and concurrent interactive renders clobber each other (leaving an
 * upload with no response).
 *
 * @param options - The admin session, the ordered operations (each a named mutation + JSONL
 * variables), and an optional API version.
 * @returns The bulkOperationRunMutations result, including the created plan parent and any user errors.
 */
export declare function runBulkOperationMutations(options: BulkOperationRunMutationsOptions): Promise<BulkOperationRunMutationsMutation['bulkOperationRunMutations']>;
export {};
packages/cli-kit/dist/public/node/api/bulk-operations/validate.d.ts
import { AdminSession } from '../../session.js';
/** An operation to validate: its GraphQL document and (optionally) a representative variables row. */
export interface OperationToValidate {
    /** Human-readable label used in error output (for example `operation 1 (SetProducts)`). */
    label: string;
    /** The GraphQL query or mutation document. */
    operation: string;
    /** The first JSONL variables row, used to check required variables and coercion. */
    representativeRow?: {
        [key: string]: unknown;
    };
}
/** The validation outcome for a single operation. */
export interface OperationValidationResult {
    label: string;
    errors: string[];
}
/** Options for {@link validateBulkOperations}. */
export interface ValidateBulkOperationsOptions {
    /** Admin session used to introspect the store's schema. */
    adminSession: AdminSession;
    /** API version to introspect; defaults to the latest supported. */
    version?: string;
    /** The operations to validate. */
    operations: OperationToValidate[];
}
/**
 * Validates bulk operations client-side against the store's Admin schema before submitting, so
 * obvious mistakes surface immediately instead of after a network round-trip. The Admin schema is
 * introspected once and reused for every operation.
 *
 * This mirrors the server's synchronous structural checks for the shape of each mutation document;
 * cross-operation `$ref` resolution and row-level validation still happen server-side.
 *
 * @param options - The admin session, optional API version, and the operations to validate.
 * @returns One result per operation, in the same order (empty `errors` means valid).
 */
export declare function validateBulkOperations(options: ValidateBulkOperationsOptions): Promise<OperationValidationResult[]>;

Existing type declarations

packages/cli-kit/dist/public/node/api/bulk-operations.d.ts
@@ -10,6 +10,8 @@ export { normalizeBulkOperationId, extractBulkOperationId, isMutation, validateS
 export { resolveBulkOperationQuery } from './bulk-operations/query.js';
 export { runBulkOperationQuery } from './bulk-operations/run-query.js';
 export { runBulkOperationMutation } from './bulk-operations/run-mutation.js';
+export { runBulkOperationMutations, type BulkMutationPlanOperation } from './bulk-operations/run-mutations.js';
+export { validateBulkOperations, type OperationToValidate, type OperationValidationResult, } from './bulk-operations/validate.js';
 export { stageFile } from './bulk-operations/stage-file.js';
 export { fetchBulkOperationById, fetchRecentBulkOperations } from './bulk-operations/fetch.js';
 export { cancelBulkOperationRequest } from './bulk-operations/cancel.js';

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-changelog This PR doesn't include a changeset entry. Is an internal only change not relevant to end users.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant