* @brief Registers the project.batch multi-op endpoint. */
| 4355 | * @brief Registers the project.batch multi-op endpoint. |
| 4356 | */ |
| 4357 | void API::Handlers::ProjectHandler::registerBatchCommand() |
| 4358 | { |
| 4359 | auto& registry = CommandRegistry::instance(); |
| 4360 | |
| 4361 | registry.registerCommand( |
| 4362 | QStringLiteral("project.batch"), |
| 4363 | QStringLiteral( |
| 4364 | "Run several project mutations atomically WITH RESPECT TO AUTOSAVE -- " |
| 4365 | "all ops execute sequentially under one suspended-autosave window, " |
| 4366 | "and a single save is flushed at the end. Use this whenever you would " |
| 4367 | "otherwise issue more than ~5 sequential mutations (renames, retypes, " |
| 4368 | "reindexes), since N round-trips cost N times the latency and N times " |
| 4369 | "the autosave/tree-rebuild churn.\n" |
| 4370 | "Each op is {command: '<registered command name>', params: {...}}; " |
| 4371 | "results are returned in the same order with per-op success/error " |
| 4372 | "fields. Set stopOnError:true to abort the batch on the first failure " |
| 4373 | "(default false: best-effort, all ops attempted).\n" |
| 4374 | "Pass dryRun:true at the top level to preview every op without " |
| 4375 | "committing. Each op's per-result still carries the affected-entity " |
| 4376 | "payload as if it had run. Rejected when any op is not in the " |
| 4377 | "dryRun-aware command set -- mixing previewable and non-previewable " |
| 4378 | "ops would leave a partial mutation, which is worse than no preview " |
| 4379 | "at all.\n" |
| 4380 | "Note: NOT transactional. Already-applied ops are NOT rolled back on " |
| 4381 | "later failures -- this is a save-suspend wrapper, not a database " |
| 4382 | "transaction. Nested project.batch calls are rejected. Hard cap of " |
| 4383 | "1024 ops per call.\n" |
| 4384 | "Example: rename 40 datasets in one round-trip:\n" |
| 4385 | " ops: [\n" |
| 4386 | " {command:'project.dataset.update', params:{groupId:0, datasetId:0, title:'LED 1', index:1}},\n" |
| 4387 | " {command:'project.dataset.update', params:{groupId:0, datasetId:1, title:'LED 2', index:2}},\n" |
| 4388 | " ...\n" |
| 4389 | " ]"), |
| 4390 | makeSchema({ |
| 4391 | arrayProp( |
| 4392 | QStringLiteral("ops"), |
| 4393 | QStringLiteral("Array of {command, params} ops to execute sequentially. Max 1024."), |
| 4394 | QJsonObject{ |
| 4395 | {QStringLiteral("type"), QStringLiteral("object")}, |
| 4396 | {QStringLiteral("properties"), |
| 4397 | QJsonObject{ |
| 4398 | {QStringLiteral("command"), |
| 4399 | QJsonObject{{QStringLiteral("type"), QStringLiteral("string")}, |
| 4400 | {QStringLiteral("description"), |
| 4401 | QStringLiteral("Registered command name (e.g. " |
| 4402 | "'project.dataset.update'). Not " |
| 4403 | "'project.batch' -- nested batches are rejected.")}}}, |
| 4404 | {QStringLiteral("params"), |
| 4405 | QJsonObject{{QStringLiteral("type"), QStringLiteral("object")}, |
| 4406 | {QStringLiteral("description"), |
| 4407 | QStringLiteral( |
| 4408 | "Arguments object for the command, exactly " |
| 4409 | "what you would pass at the top level if calling it directly.")}}}}}, |
| 4410 | {QStringLiteral("required"), |
| 4411 | QJsonArray{QStringLiteral("command"), QStringLiteral("params")}}} |
| 4412 | ) |
| 4413 | }), |
| 4414 | &projectBatch); |
nothing calls this directly
no test coverage detected