@purposeinplay/payload-ai-translate 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +714 -0
  3. package/dist/alerts-collection.d.ts +21 -0
  4. package/dist/alerts-collection.js +159 -0
  5. package/dist/api.d.ts +4 -0
  6. package/dist/api.js +918 -0
  7. package/dist/bulk-translate-batches-collection.d.ts +29 -0
  8. package/dist/bulk-translate-batches-collection.js +404 -0
  9. package/dist/bulk-translate-units-collection.d.ts +35 -0
  10. package/dist/bulk-translate-units-collection.js +310 -0
  11. package/dist/client/estimated-cost-cell.d.ts +6 -0
  12. package/dist/client/estimated-cost-cell.js +12 -0
  13. package/dist/client/excluded-fields-field.d.ts +45 -0
  14. package/dist/client/excluded-fields-field.js +553 -0
  15. package/dist/client/field-translate-button.d.ts +6 -0
  16. package/dist/client/field-translate-button.js +199 -0
  17. package/dist/client/index.d.ts +6 -0
  18. package/dist/client/index.js +6 -0
  19. package/dist/client/lib/use-global-kill-switches.d.ts +20 -0
  20. package/dist/client/lib/use-global-kill-switches.js +58 -0
  21. package/dist/client/translate-button.d.ts +2 -0
  22. package/dist/client/translate-button.js +228 -0
  23. package/dist/client/translate-modal.d.ts +16 -0
  24. package/dist/client/translate-modal.js +549 -0
  25. package/dist/client/translation-progress.d.ts +10 -0
  26. package/dist/client/translation-progress.js +297 -0
  27. package/dist/components/TranslationNavGroup.d.ts +45 -0
  28. package/dist/components/TranslationNavGroup.js +104 -0
  29. package/dist/defaults.d.ts +11 -0
  30. package/dist/defaults.js +16 -0
  31. package/dist/endpoints/client-config.d.ts +44 -0
  32. package/dist/endpoints/client-config.js +145 -0
  33. package/dist/endpoints/estimate.d.ts +5 -0
  34. package/dist/endpoints/estimate.js +237 -0
  35. package/dist/endpoints/progress.d.ts +2 -0
  36. package/dist/endpoints/progress.js +314 -0
  37. package/dist/endpoints/translate.d.ts +11 -0
  38. package/dist/endpoints/translate.js +376 -0
  39. package/dist/endpoints/translation-hub/_helpers.d.ts +140 -0
  40. package/dist/endpoints/translation-hub/_helpers.js +297 -0
  41. package/dist/endpoints/translation-hub/active.d.ts +21 -0
  42. package/dist/endpoints/translation-hub/active.js +220 -0
  43. package/dist/endpoints/translation-hub/cancel.d.ts +22 -0
  44. package/dist/endpoints/translation-hub/cancel.js +233 -0
  45. package/dist/endpoints/translation-hub/enqueue.d.ts +70 -0
  46. package/dist/endpoints/translation-hub/enqueue.js +529 -0
  47. package/dist/endpoints/translation-hub/failures.d.ts +12 -0
  48. package/dist/endpoints/translation-hub/failures.js +67 -0
  49. package/dist/endpoints/translation-hub/force-reset.d.ts +20 -0
  50. package/dist/endpoints/translation-hub/force-reset.js +144 -0
  51. package/dist/endpoints/translation-hub/index.d.ts +21 -0
  52. package/dist/endpoints/translation-hub/index.js +20 -0
  53. package/dist/endpoints/translation-hub/list.d.ts +40 -0
  54. package/dist/endpoints/translation-hub/list.js +182 -0
  55. package/dist/endpoints/translation-hub/preflight.d.ts +19 -0
  56. package/dist/endpoints/translation-hub/preflight.js +141 -0
  57. package/dist/endpoints/translation-hub/retry-failed.d.ts +38 -0
  58. package/dist/endpoints/translation-hub/retry-failed.js +235 -0
  59. package/dist/endpoints/translation-hub/revert.d.ts +88 -0
  60. package/dist/endpoints/translation-hub/revert.js +405 -0
  61. package/dist/endpoints/translation-hub/status.d.ts +45 -0
  62. package/dist/endpoints/translation-hub/status.js +391 -0
  63. package/dist/endpoints/translation-hub/usage-summary.d.ts +114 -0
  64. package/dist/endpoints/translation-hub/usage-summary.js +481 -0
  65. package/dist/exports/client.d.ts +6 -0
  66. package/dist/exports/client.js +6 -0
  67. package/dist/exports/components.d.ts +6 -0
  68. package/dist/exports/components.js +5 -0
  69. package/dist/exports/index.d.ts +8 -0
  70. package/dist/exports/index.js +7 -0
  71. package/dist/exports/providers.d.ts +9 -0
  72. package/dist/exports/providers.js +5 -0
  73. package/dist/exports/views-client.d.ts +23 -0
  74. package/dist/exports/views-client.js +22 -0
  75. package/dist/exports/views.d.ts +30 -0
  76. package/dist/exports/views.js +29 -0
  77. package/dist/hooks/after-change-global.d.ts +4 -0
  78. package/dist/hooks/after-change-global.js +109 -0
  79. package/dist/hooks/after-change.d.ts +16 -0
  80. package/dist/hooks/after-change.js +205 -0
  81. package/dist/hooks/after-delete.d.ts +30 -0
  82. package/dist/hooks/after-delete.js +95 -0
  83. package/dist/index.d.ts +5 -0
  84. package/dist/index.js +5 -0
  85. package/dist/jobs-collection.d.ts +17 -0
  86. package/dist/jobs-collection.js +139 -0
  87. package/dist/lexical/classifier.d.ts +3 -0
  88. package/dist/lexical/classifier.js +108 -0
  89. package/dist/lexical/deserializer.d.ts +4 -0
  90. package/dist/lexical/deserializer.js +263 -0
  91. package/dist/lexical/placeholder-integrity.d.ts +6 -0
  92. package/dist/lexical/placeholder-integrity.js +21 -0
  93. package/dist/lexical/placeholders.d.ts +21 -0
  94. package/dist/lexical/placeholders.js +117 -0
  95. package/dist/lexical/serializer.d.ts +21 -0
  96. package/dist/lexical/serializer.js +233 -0
  97. package/dist/lexical/types.d.ts +32 -0
  98. package/dist/lexical/types.js +1 -0
  99. package/dist/lib/auth-diagnostics.d.ts +14 -0
  100. package/dist/lib/auth-diagnostics.js +19 -0
  101. package/dist/lib/batch-counts.d.ts +58 -0
  102. package/dist/lib/batch-counts.js +105 -0
  103. package/dist/lib/bulk-translate-migrations.d.ts +92 -0
  104. package/dist/lib/bulk-translate-migrations.js +153 -0
  105. package/dist/lib/coalescing-queue.d.ts +38 -0
  106. package/dist/lib/coalescing-queue.js +69 -0
  107. package/dist/lib/content-extractor.d.ts +16 -0
  108. package/dist/lib/content-extractor.js +410 -0
  109. package/dist/lib/content-hash.d.ts +1 -0
  110. package/dist/lib/content-hash.js +19 -0
  111. package/dist/lib/content-patcher.d.ts +15 -0
  112. package/dist/lib/content-patcher.js +293 -0
  113. package/dist/lib/cost-guards.d.ts +2 -0
  114. package/dist/lib/cost-guards.js +18 -0
  115. package/dist/lib/daily-spend-cap.d.ts +58 -0
  116. package/dist/lib/daily-spend-cap.js +233 -0
  117. package/dist/lib/effective-locales.d.ts +181 -0
  118. package/dist/lib/effective-locales.js +302 -0
  119. package/dist/lib/error-messages.d.ts +245 -0
  120. package/dist/lib/error-messages.js +626 -0
  121. package/dist/lib/events.d.ts +39 -0
  122. package/dist/lib/events.js +146 -0
  123. package/dist/lib/exclude-fields.d.ts +3 -0
  124. package/dist/lib/exclude-fields.js +64 -0
  125. package/dist/lib/field-breadcrumb.d.ts +31 -0
  126. package/dist/lib/field-breadcrumb.js +227 -0
  127. package/dist/lib/field-diff.d.ts +1 -0
  128. package/dist/lib/field-diff.js +25 -0
  129. package/dist/lib/field-empty.d.ts +2 -0
  130. package/dist/lib/field-empty.js +68 -0
  131. package/dist/lib/field-resolver.d.ts +3 -0
  132. package/dist/lib/field-resolver.js +164 -0
  133. package/dist/lib/group-soft-skips.d.ts +39 -0
  134. package/dist/lib/group-soft-skips.js +45 -0
  135. package/dist/lib/locale-merge.d.ts +44 -0
  136. package/dist/lib/locale-merge.js +357 -0
  137. package/dist/lib/locale-row-check.d.ts +30 -0
  138. package/dist/lib/locale-row-check.js +64 -0
  139. package/dist/lib/logger.d.ts +74 -0
  140. package/dist/lib/logger.js +97 -0
  141. package/dist/lib/manual-edit-guard.d.ts +128 -0
  142. package/dist/lib/manual-edit-guard.js +393 -0
  143. package/dist/lib/output-validation.d.ts +48 -0
  144. package/dist/lib/output-validation.js +148 -0
  145. package/dist/lib/payload-read.d.ts +16 -0
  146. package/dist/lib/payload-read.js +51 -0
  147. package/dist/lib/per-doc-claim.d.ts +90 -0
  148. package/dist/lib/per-doc-claim.js +140 -0
  149. package/dist/lib/per-doc-lock.d.ts +94 -0
  150. package/dist/lib/per-doc-lock.js +119 -0
  151. package/dist/lib/persist-usage.d.ts +91 -0
  152. package/dist/lib/persist-usage.js +116 -0
  153. package/dist/lib/progress-store.d.ts +103 -0
  154. package/dist/lib/progress-store.js +314 -0
  155. package/dist/lib/rate-limiter.d.ts +3 -0
  156. package/dist/lib/rate-limiter.js +53 -0
  157. package/dist/lib/snapshot-select.d.ts +43 -0
  158. package/dist/lib/snapshot-select.js +108 -0
  159. package/dist/lib/translate-prompt.d.ts +31 -0
  160. package/dist/lib/translate-prompt.js +66 -0
  161. package/dist/lib/translation-token-bucket.d.ts +57 -0
  162. package/dist/lib/translation-token-bucket.js +365 -0
  163. package/dist/lib/truncate-source-value.d.ts +1 -0
  164. package/dist/lib/truncate-source-value.js +27 -0
  165. package/dist/manual-edit-collection.d.ts +22 -0
  166. package/dist/manual-edit-collection.js +124 -0
  167. package/dist/plugin.d.ts +3 -0
  168. package/dist/plugin.js +934 -0
  169. package/dist/providers/ai-sdk-adapter.d.ts +35 -0
  170. package/dist/providers/ai-sdk-adapter.js +100 -0
  171. package/dist/providers/anthropic.d.ts +31 -0
  172. package/dist/providers/anthropic.js +66 -0
  173. package/dist/providers/custom.d.ts +36 -0
  174. package/dist/providers/custom.js +24 -0
  175. package/dist/providers/gemini.d.ts +20 -0
  176. package/dist/providers/gemini.js +48 -0
  177. package/dist/providers/mock.d.ts +2 -0
  178. package/dist/providers/mock.js +29 -0
  179. package/dist/providers/openai.d.ts +28 -0
  180. package/dist/providers/openai.js +69 -0
  181. package/dist/settings-global.d.ts +74 -0
  182. package/dist/settings-global.js +216 -0
  183. package/dist/tasks/bulk-translate-coordinator.d.ts +115 -0
  184. package/dist/tasks/bulk-translate-coordinator.js +708 -0
  185. package/dist/tasks/bulk-translate-doc-task.d.ts +142 -0
  186. package/dist/tasks/bulk-translate-doc-task.js +1000 -0
  187. package/dist/tasks/bulk-translate-janitor.d.ts +87 -0
  188. package/dist/tasks/bulk-translate-janitor.js +311 -0
  189. package/dist/tasks/translate-job-task.d.ts +51 -0
  190. package/dist/tasks/translate-job-task.js +154 -0
  191. package/dist/translate.d.ts +113 -0
  192. package/dist/translate.js +911 -0
  193. package/dist/translation-daily-spend-collection.d.ts +24 -0
  194. package/dist/translation-daily-spend-collection.js +133 -0
  195. package/dist/translation-rate-limits-collection.d.ts +30 -0
  196. package/dist/translation-rate-limits-collection.js +144 -0
  197. package/dist/types.d.ts +672 -0
  198. package/dist/types.js +1 -0
  199. package/dist/usage-collection.d.ts +14 -0
  200. package/dist/usage-collection.js +377 -0
  201. package/dist/views/BulkRunsHub/BatchRow.d.ts +32 -0
  202. package/dist/views/BulkRunsHub/BatchRow.js +1222 -0
  203. package/dist/views/BulkRunsHub/BucketRow.d.ts +62 -0
  204. package/dist/views/BulkRunsHub/BucketRow.js +982 -0
  205. package/dist/views/BulkRunsHub/BulkRunsHub.client.d.ts +18 -0
  206. package/dist/views/BulkRunsHub/BulkRunsHub.client.js +331 -0
  207. package/dist/views/BulkRunsHub/EmptyState.d.ts +6 -0
  208. package/dist/views/BulkRunsHub/EmptyState.js +64 -0
  209. package/dist/views/BulkRunsHub/FilterBar.d.ts +16 -0
  210. package/dist/views/BulkRunsHub/FilterBar.js +284 -0
  211. package/dist/views/BulkRunsHub/InFlightBanner.d.ts +14 -0
  212. package/dist/views/BulkRunsHub/InFlightBanner.js +59 -0
  213. package/dist/views/BulkRunsHub/StatusBadge.d.ts +64 -0
  214. package/dist/views/BulkRunsHub/StatusBadge.js +248 -0
  215. package/dist/views/BulkRunsHub/SummaryStrip.d.ts +22 -0
  216. package/dist/views/BulkRunsHub/SummaryStrip.js +249 -0
  217. package/dist/views/BulkRunsHub/bucket-grouping.d.ts +200 -0
  218. package/dist/views/BulkRunsHub/bucket-grouping.js +344 -0
  219. package/dist/views/BulkRunsHub/bucketFailureSummary.d.ts +9 -0
  220. package/dist/views/BulkRunsHub/bucketFailureSummary.js +36 -0
  221. package/dist/views/BulkRunsHub/dedupedStatusFetch.d.ts +5 -0
  222. package/dist/views/BulkRunsHub/dedupedStatusFetch.js +45 -0
  223. package/dist/views/BulkRunsHub/index.d.ts +17 -0
  224. package/dist/views/BulkRunsHub/index.js +80 -0
  225. package/dist/views/BulkRunsHub/urlFilters.d.ts +14 -0
  226. package/dist/views/BulkRunsHub/urlFilters.js +50 -0
  227. package/dist/views/BulkRunsHub/useBulkRunsList.d.ts +26 -0
  228. package/dist/views/BulkRunsHub/useBulkRunsList.js +204 -0
  229. package/dist/views/BulkRunsHub/useUrlFilters.d.ts +10 -0
  230. package/dist/views/BulkRunsHub/useUrlFilters.js +88 -0
  231. package/dist/views/TranslationHub/ActiveJobs.d.ts +6 -0
  232. package/dist/views/TranslationHub/ActiveJobs.js +320 -0
  233. package/dist/views/TranslationHub/AdvancedPanel.d.ts +17 -0
  234. package/dist/views/TranslationHub/AdvancedPanel.js +996 -0
  235. package/dist/views/TranslationHub/AlertBanner.d.ts +6 -0
  236. package/dist/views/TranslationHub/AlertBanner.js +568 -0
  237. package/dist/views/TranslationHub/AuditPanel.d.ts +6 -0
  238. package/dist/views/TranslationHub/AuditPanel.helpers.d.ts +44 -0
  239. package/dist/views/TranslationHub/AuditPanel.helpers.js +71 -0
  240. package/dist/views/TranslationHub/AuditPanel.js +1367 -0
  241. package/dist/views/TranslationHub/BulkTranslate.types.d.ts +242 -0
  242. package/dist/views/TranslationHub/BulkTranslate.types.js +36 -0
  243. package/dist/views/TranslationHub/BulkTranslateFailureDrawer.d.ts +19 -0
  244. package/dist/views/TranslationHub/BulkTranslateFailureDrawer.js +332 -0
  245. package/dist/views/TranslationHub/BulkTranslateMonitor.d.ts +28 -0
  246. package/dist/views/TranslationHub/BulkTranslateMonitor.js +305 -0
  247. package/dist/views/TranslationHub/BulkTranslateNarrowViewportBanner.d.ts +3 -0
  248. package/dist/views/TranslationHub/BulkTranslateNarrowViewportBanner.js +42 -0
  249. package/dist/views/TranslationHub/BulkTranslatePostEnqueueTransition.d.ts +26 -0
  250. package/dist/views/TranslationHub/BulkTranslatePostEnqueueTransition.js +95 -0
  251. package/dist/views/TranslationHub/BulkTranslatePreflightModal.d.ts +22 -0
  252. package/dist/views/TranslationHub/BulkTranslatePreflightModal.js +879 -0
  253. package/dist/views/TranslationHub/BulkTranslateTerminalCard.d.ts +29 -0
  254. package/dist/views/TranslationHub/BulkTranslateTerminalCard.js +445 -0
  255. package/dist/views/TranslationHub/BulkTranslateTrigger.d.ts +66 -0
  256. package/dist/views/TranslationHub/BulkTranslateTrigger.js +161 -0
  257. package/dist/views/TranslationHub/EditorRecentRunsPanel.d.ts +33 -0
  258. package/dist/views/TranslationHub/EditorRecentRunsPanel.js +290 -0
  259. package/dist/views/TranslationHub/Hub.client.d.ts +74 -0
  260. package/dist/views/TranslationHub/Hub.client.js +357 -0
  261. package/dist/views/TranslationHub/ModelCombobox.d.ts +14 -0
  262. package/dist/views/TranslationHub/ModelCombobox.js +415 -0
  263. package/dist/views/TranslationHub/PerCollectionConfig.d.ts +10 -0
  264. package/dist/views/TranslationHub/PerCollectionConfig.helpers.d.ts +16 -0
  265. package/dist/views/TranslationHub/PerCollectionConfig.helpers.js +19 -0
  266. package/dist/views/TranslationHub/PerCollectionConfig.js +759 -0
  267. package/dist/views/TranslationHub/SettingsRail.d.ts +11 -0
  268. package/dist/views/TranslationHub/SettingsRail.js +382 -0
  269. package/dist/views/TranslationHub/StatusStrip.d.ts +6 -0
  270. package/dist/views/TranslationHub/StatusStrip.js +451 -0
  271. package/dist/views/TranslationHub/UsageTable.d.ts +6 -0
  272. package/dist/views/TranslationHub/UsageTable.helpers.d.ts +69 -0
  273. package/dist/views/TranslationHub/UsageTable.helpers.js +49 -0
  274. package/dist/views/TranslationHub/UsageTable.js +1240 -0
  275. package/dist/views/TranslationHub/alertGrouping.d.ts +70 -0
  276. package/dist/views/TranslationHub/alertGrouping.js +99 -0
  277. package/dist/views/TranslationHub/index.d.ts +20 -0
  278. package/dist/views/TranslationHub/index.js +109 -0
  279. package/dist/views/TranslationHub/tabNavigation.d.ts +53 -0
  280. package/dist/views/TranslationHub/tabNavigation.js +74 -0
  281. package/dist/views/TranslationHub/terminalBannerVisibility.d.ts +33 -0
  282. package/dist/views/TranslationHub/terminalBannerVisibility.js +124 -0
  283. package/dist/views/TranslationHub/useBulkTranslateActive.d.ts +49 -0
  284. package/dist/views/TranslationHub/useBulkTranslateActive.js +251 -0
  285. package/dist/views/TranslationHub/useFocusTrap.d.ts +6 -0
  286. package/dist/views/TranslationHub/useFocusTrap.js +81 -0
  287. package/dist/views/TranslationHub/useTranslationHubUsageSummary.d.ts +77 -0
  288. package/dist/views/TranslationHub/useTranslationHubUsageSummary.js +267 -0
  289. package/dist/views/shared/EditorError.d.ts +97 -0
  290. package/dist/views/shared/EditorError.js +205 -0
  291. package/dist/views/shared/ModelCell.d.ts +18 -0
  292. package/dist/views/shared/ModelCell.js +31 -0
  293. package/dist/views/shared/docHref.d.ts +16 -0
  294. package/dist/views/shared/docHref.js +26 -0
  295. package/dist/views/shared/fetch-error-body.d.ts +25 -0
  296. package/dist/views/shared/fetch-error-body.js +42 -0
  297. package/dist/views/shared/filterPillStyle.d.ts +35 -0
  298. package/dist/views/shared/filterPillStyle.js +40 -0
  299. package/dist/views/shared/format.d.ts +75 -0
  300. package/dist/views/shared/format.js +131 -0
  301. package/package.json +141 -0
@@ -0,0 +1,235 @@
1
+ import { DEFAULT_BULK_TRANSLATE_BATCHES_COLLECTION_SLUG } from '../../bulk-translate-batches-collection.js';
2
+ import { DEFAULT_BULK_TRANSLATE_UNITS_COLLECTION_SLUG } from '../../bulk-translate-units-collection.js';
3
+ import { checkAndIncrementDailySpend } from '../../lib/daily-spend-cap.js';
4
+ import { createScopedLogger } from '../../lib/logger.js';
5
+ import { BULK_TRANSLATE_DOC_TASK_SLUG } from '../../tasks/bulk-translate-coordinator.js';
6
+ import { errorResponse, forbiddenOwnershipResponse, getAiTranslateConfig, isEditorOrAdmin, ownsBatch, readJsonBody, unauthorizedResponse } from './_helpers.js';
7
+ import { extractBatchId } from './status.js';
8
+ // ---------------------------------------------------------------------------
9
+ // Handler
10
+ // ---------------------------------------------------------------------------
11
+ const TERMINAL_STATUSES_FOR_RETRY = new Set([
12
+ 'success',
13
+ 'partial',
14
+ 'failed'
15
+ ]);
16
+ /**
17
+ * `POST /api/translation-hub/bulk-translate/:id/retry-failed`
18
+ *
19
+ * Re-enqueues all `failed` units for the batch back to `pending`.
20
+ * Refuses when the batch is non-terminal — there's no semantic way
21
+ * to "retry failed" while units are still running.
22
+ *
23
+ * Daily-cap is re-checked (F-SEC-TOTP-BYPASS — the cap applies to all
24
+ * translate paths, not just first-enqueue).
25
+ */ export const getBulkTranslateRetryFailedHandler = (options = {})=>async (req)=>{
26
+ const batchesSlug = options.batchesCollectionSlug ?? DEFAULT_BULK_TRANSLATE_BATCHES_COLLECTION_SLUG;
27
+ const unitsSlug = options.unitsCollectionSlug ?? DEFAULT_BULK_TRANSLATE_UNITS_COLLECTION_SLUG;
28
+ const workerSlug = options.workerTaskSlug ?? BULK_TRANSLATE_DOC_TASK_SLUG;
29
+ if (!req.user) {
30
+ return unauthorizedResponse(req);
31
+ }
32
+ if (!isEditorOrAdmin(req.user)) {
33
+ return errorResponse('forbidden', "You don't have permission to retry failed units. Contact an admin.", 403);
34
+ }
35
+ const config = getAiTranslateConfig(req.payload);
36
+ const bulkConfig = config?.bulk ?? {
37
+ enabled: false
38
+ };
39
+ const batchId = extractBatchId(req.url ?? '');
40
+ if (!batchId) {
41
+ return errorResponse('invalid_batch_id', 'Batch ID could not be parsed from the request URL.', 400);
42
+ }
43
+ const parsed = await readJsonBody(req);
44
+ if (!parsed.ok) return parsed.res;
45
+ const collectionFilter = typeof parsed.body.collection === 'string' && parsed.body.collection.length > 0 ? parsed.body.collection : null;
46
+ const documentIdFilter = typeof parsed.body.documentId === 'string' && parsed.body.documentId.length > 0 ? parsed.body.documentId : null;
47
+ const skipErrorCodes = Array.isArray(parsed.body.skipErrorCodes) ? new Set(parsed.body.skipErrorCodes.filter((s)=>typeof s === 'string')) : new Set();
48
+ let batch;
49
+ try {
50
+ batch = await req.payload.findByID({
51
+ collection: batchesSlug,
52
+ id: batchId,
53
+ overrideAccess: true,
54
+ depth: 0
55
+ });
56
+ } catch (err) {
57
+ const log = createScopedLogger(req.payload, {
58
+ component: 'hub.retry-failed',
59
+ batchId
60
+ });
61
+ log.event('warn', 'hub.retry-failed.batch-read.failed', {
62
+ err,
63
+ endpoint: 'hub.retry-failed',
64
+ batchId
65
+ });
66
+ batch = undefined;
67
+ }
68
+ if (!batch) {
69
+ return errorResponse('not_found', 'This translation run no longer exists. Refresh the page.', 404);
70
+ }
71
+ // 1.2.8: editor can only retry a run they triggered. Admins pass.
72
+ // See ownership-leak rationale in cancel.ts.
73
+ if (!ownsBatch(req.user, batch)) {
74
+ return forbiddenOwnershipResponse();
75
+ }
76
+ if (!TERMINAL_STATUSES_FOR_RETRY.has(batch.status)) {
77
+ return errorResponse('invalid_state', 'The run is still in progress. Wait for it to finish before retrying failed documents.', 409, {
78
+ status: batch.status
79
+ });
80
+ }
81
+ // ----- Fetch failed units, apply skip + collection + doc filters -----
82
+ const failedUnits = await loadAllFailedUnits(req.payload, unitsSlug, batchId);
83
+ const skipped = [];
84
+ const toRetry = [];
85
+ for (const u of failedUnits){
86
+ // Per-bucket retry: when the caller pinned a collection, exclude
87
+ // failed units in other collections / globals from this retry.
88
+ if (collectionFilter && u.collection !== collectionFilter) {
89
+ continue;
90
+ }
91
+ // Per-doc retry: when the caller pinned a documentId, exclude
92
+ // failed units for other docs in the same collection.
93
+ if (documentIdFilter && u.documentId !== documentIdFilter) {
94
+ continue;
95
+ }
96
+ if (u.failureCode && skipErrorCodes.has(u.failureCode)) {
97
+ skipped.push(String(u.id));
98
+ continue;
99
+ }
100
+ toRetry.push(u);
101
+ }
102
+ if (toRetry.length === 0) {
103
+ return Response.json({
104
+ data: {
105
+ batchId,
106
+ retriedJobs: 0,
107
+ skippedJobs: skipped.length
108
+ }
109
+ });
110
+ }
111
+ // ----- Daily cap re-check -----
112
+ // Sum the per-unit cost estimate. Units carry `costUsd` populated
113
+ // on the previous attempt (0 for ones that failed before billing
114
+ // — those re-run for free per cap arithmetic). When NO unit has
115
+ // a known cost we still call the cap helper with 0; that keeps the
116
+ // accounting honest without forcing a fresh batch-level estimate
117
+ // (Decision #29 doesn't apply to retries — we already have observed
118
+ // history).
119
+ const estimatedRetryCost = toRetry.reduce((sum, u)=>sum + (typeof u.costUsd === 'number' ? u.costUsd : 0), 0);
120
+ const capResult = await checkAndIncrementDailySpend(req.payload, estimatedRetryCost, {
121
+ capUsd: bulkConfig.dailyUsdCap
122
+ });
123
+ if (!capResult.allowed) {
124
+ try {
125
+ await bulkConfig.onCapExceeded?.({
126
+ todaySpentUsd: capResult.todaySpentUsd,
127
+ capUsd: capResult.capUsd,
128
+ rejectedEstimateUsd: estimatedRetryCost,
129
+ requestPath: 'bulk-endpoint'
130
+ });
131
+ } catch {
132
+ // Best-effort hook — never block the response on callback.
133
+ }
134
+ return errorResponse(capResult.reason === 'cap_exceeded' ? 'daily_cap_exceeded' : 'invalid_estimate', capResult.message, 402, {
135
+ todaySpentUsd: String(capResult.todaySpentUsd),
136
+ remainingUsd: String(capResult.remainingUsd),
137
+ capUsd: String(capResult.capUsd)
138
+ });
139
+ }
140
+ // ----- Re-enqueue -----
141
+ let retriedJobs = 0;
142
+ for (const u of toRetry){
143
+ try {
144
+ await req.payload.update({
145
+ collection: unitsSlug,
146
+ id: u.id,
147
+ data: {
148
+ status: 'pending',
149
+ attempts: (u.attempts ?? 0) + 0,
150
+ failureCode: null,
151
+ failureMessage: null,
152
+ completedAt: null
153
+ },
154
+ overrideAccess: true
155
+ });
156
+ await req.payload.jobs.queue({
157
+ task: workerSlug,
158
+ input: {
159
+ unitId: String(u.id)
160
+ }
161
+ });
162
+ retriedJobs += 1;
163
+ } catch (err) {
164
+ req.payload.logger?.warn?.(`[ai-translate] retry-failed: failed to requeue unit ${u.id}: ${err instanceof Error ? err.message : String(err)}`);
165
+ }
166
+ }
167
+ // ----- Reset batch to running so transitions resume -----
168
+ if (retriedJobs > 0) {
169
+ try {
170
+ await req.payload.update({
171
+ collection: batchesSlug,
172
+ id: batchId,
173
+ data: {
174
+ status: 'running',
175
+ completedAt: null
176
+ },
177
+ overrideAccess: true
178
+ });
179
+ } catch (err) {
180
+ req.payload.logger?.warn?.(`[ai-translate] retry-failed: failed to reset batch ${batchId} status: ${err instanceof Error ? err.message : String(err)}`);
181
+ }
182
+ }
183
+ return Response.json({
184
+ data: {
185
+ batchId,
186
+ retriedJobs,
187
+ skippedJobs: skipped.length
188
+ }
189
+ });
190
+ };
191
+ async function loadAllFailedUnits(payload, unitsSlug, batchId) {
192
+ // Paginate to bound memory. 1000 per page is well below Payload's
193
+ // default heap budget and works for realistic batch sizes
194
+ // (~10k units would page 10 times).
195
+ const out = [];
196
+ const limit = 1000;
197
+ let page = 1;
198
+ // eslint-disable-next-line no-constant-condition
199
+ while(true){
200
+ const result = await payload.find({
201
+ collection: unitsSlug,
202
+ where: {
203
+ and: [
204
+ {
205
+ batchId: {
206
+ equals: batchId
207
+ }
208
+ },
209
+ {
210
+ status: {
211
+ equals: 'failed'
212
+ }
213
+ }
214
+ ]
215
+ },
216
+ page,
217
+ limit,
218
+ depth: 0,
219
+ overrideAccess: true
220
+ });
221
+ for (const doc of result.docs){
222
+ out.push({
223
+ id: doc.id,
224
+ attempts: typeof doc.attempts === 'number' ? doc.attempts : 0,
225
+ failureCode: typeof doc.failureCode === 'string' ? doc.failureCode : null,
226
+ costUsd: typeof doc.costUsd === 'number' ? doc.costUsd : 0,
227
+ collection: typeof doc.collection === 'string' ? doc.collection : undefined,
228
+ documentId: typeof doc.documentId === 'string' ? doc.documentId : undefined
229
+ });
230
+ }
231
+ if (!result.hasNextPage) break;
232
+ page += 1;
233
+ }
234
+ return out;
235
+ }
@@ -0,0 +1,88 @@
1
+ import type { PayloadHandler } from 'payload';
2
+ export interface BulkRevertBody {
3
+ /** Required — TOTP is mandatory regardless of plugin config (Decision #33). */
4
+ totpCode?: string;
5
+ /**
6
+ * Bypass the schema-drift refusal when set to `true`. Decision #34
7
+ * + F-DA-REVERT-SCHEMA: revert refuses on schema mismatch unless
8
+ * the admin explicitly opts in. Use with caution — restoring data
9
+ * against a renamed/removed field can corrupt the doc.
10
+ */
11
+ force?: boolean;
12
+ }
13
+ export interface BulkRevertWarning {
14
+ unitId: string;
15
+ collection: string;
16
+ documentId: string;
17
+ locale: string;
18
+ reason: 'schema_drift' | 'no_snapshot' | 'snapshot_locale_mismatch' | 'restore_failed' | 'doc_deleted';
19
+ message?: string;
20
+ }
21
+ export interface BulkRevertHandlerOptions {
22
+ batchesCollectionSlug?: string;
23
+ unitsCollectionSlug?: string;
24
+ /**
25
+ * Wall-clock window after `completedAt` during which revert is
26
+ * permitted (Decision #33). Defaults to 24h.
27
+ */
28
+ windowMs?: number;
29
+ /**
30
+ * Page size for the unit iteration. Default 100 — bounded so the
31
+ * handler stays within serverless memory budgets per F-DA-REVERT-OOM.
32
+ */
33
+ pageSize?: number;
34
+ }
35
+ /**
36
+ * `POST /api/translation-hub/bulk-translate/:id/revert`
37
+ *
38
+ * Restores the pre-run snapshot of every `success` unit in the batch.
39
+ * Decision #22 v2 / F-DA-REVERT: the snapshot is a nested-object
40
+ * payload directly passable to `payload.update({ data: snapshot })`.
41
+ *
42
+ * Refusal cases:
43
+ * - non-revertable batch status (must be `completed | partial`).
44
+ * - batch is currently in-flight (`queued | running | cancelling`).
45
+ * - batch's `completedAt` is more than `windowMs` ago.
46
+ * - TOTP required regardless of plugin config (Decision #33).
47
+ *
48
+ * Per-unit warnings (don't abort the run):
49
+ * - `schema_drift` — schema hash differs and `force !== true`.
50
+ * - `no_snapshot` — unit has no `preRunSnapshot` (older row).
51
+ * - `restore_failed` — `payload.update` threw.
52
+ * - `doc_deleted` — the source doc was deleted post-bulk; nothing
53
+ * to revert against.
54
+ *
55
+ * Writes are flagged via `context: { aiTranslateInternal: true,
56
+ * disableRevalidate: true }` so:
57
+ * - audit-log marks them `source: 'ai-translate'` (avoids confusing
58
+ * editor with 1000 "user updated" entries for a revert).
59
+ * - the plugin's allowlist-spread at translate.ts:483 + the
60
+ * consumer's revalidate hooks honor the disable flag — we'll
61
+ * fire a terminal revalidate sweep at the end.
62
+ */
63
+ export declare const getBulkTranslateRevertHandler: (options?: BulkRevertHandlerOptions) => PayloadHandler;
64
+ export type RevertUnitRow = {
65
+ id: string | number;
66
+ collection: string;
67
+ documentId: string;
68
+ locale: string;
69
+ preRunSnapshot: Record<string, unknown> | null;
70
+ /** Locale whose values `preRunSnapshot` holds (v1.2.12+; null on legacy rows). */
71
+ snapshotLocale: string | null;
72
+ schemaHash: string | null;
73
+ };
74
+ export type RevertOutcome = {
75
+ reverted: true;
76
+ } | {
77
+ reverted: false;
78
+ warning?: BulkRevertWarning;
79
+ };
80
+ export type RevertSingleParams = {
81
+ payload: import('payload').Payload;
82
+ unit: RevertUnitRow;
83
+ unitsSlug: string;
84
+ currentSchemaHashByCollection: Map<string, string>;
85
+ force: boolean;
86
+ pluginGlobals: Set<string>;
87
+ };
88
+ export declare function revertSingleUnit(params: RevertSingleParams): Promise<RevertOutcome>;