@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,109 @@
1
+ import { translateGlobal } from '../api.js';
2
+ import { REENTRY_FLAG, SKIP_FLAG } from '../defaults.js';
3
+ import { getDocAutoLocaleOverride, getDocOptOut, getEffectiveAutoLocales, getGlobalKillSwitches } from '../lib/effective-locales.js';
4
+ import { completeJobNow, getActiveJobForDoc, getOrCreateJobForDoc } from '../lib/progress-store.js';
5
+ // ---------------------------------------------------------------------------
6
+ // Hook factory
7
+ // ---------------------------------------------------------------------------
8
+ export function createTranslateGlobalAfterChangeHook(globalSlug, pluginOptions, queue) {
9
+ const diffOnly = pluginOptions.automation?.diffOnly !== false; // default true
10
+ return async ({ req, doc, previousDoc })=>{
11
+ // Re-entrancy guard — plugin's own writes and bulk imports skip translation
12
+ const ctx = req.context ?? {};
13
+ if (ctx[REENTRY_FLAG] || ctx[SKIP_FLAG]) {
14
+ return doc;
15
+ }
16
+ // Only translate source-locale saves
17
+ if (req.locale && req.locale !== pluginOptions.sourceLocale) {
18
+ return doc;
19
+ }
20
+ // Plugin-wide auto-translate kill switch (v1.2.8). See after-change.ts
21
+ // for the full reasoning; same gate, mirrored for globals.
22
+ const killSwitches = await getGlobalKillSwitches(req.payload, pluginOptions);
23
+ if (!killSwitches.autoEnabled) {
24
+ return doc;
25
+ }
26
+ // Early-return helper that closes out any stranded job seeded by a
27
+ // parent flow. Mirrors the pattern in after-change.ts.
28
+ const earlyReturn = ()=>{
29
+ const stranded = getActiveJobForDoc(globalSlug, globalSlug);
30
+ if (stranded) completeJobNow(stranded.jobId);
31
+ return doc;
32
+ };
33
+ // Per-doc opt-out gate. Editor explicitly checked "skip
34
+ // auto-translate for this global save" — bail before any other work.
35
+ if (getDocOptOut(doc)) {
36
+ return earlyReturn();
37
+ }
38
+ // Layered locale resolution: plugin → site-wide → per-global →
39
+ // per-doc narrow. Empty result skips dispatch entirely. See
40
+ // `getEffectiveAutoLocales` for the full stack.
41
+ const decision = await getEffectiveAutoLocales(req.payload, pluginOptions, globalSlug);
42
+ if (decision.skipAutoOnPublish || decision.locales.length === 0) {
43
+ return earlyReturn();
44
+ }
45
+ // Per-doc widget narrows further when set. Empty array = inherit;
46
+ // opt-out is the separate boolean flag handled above.
47
+ const docOverride = getDocAutoLocaleOverride(doc);
48
+ let effectiveLocales = decision.locales;
49
+ if (docOverride !== undefined) {
50
+ const allowed = new Set(docOverride);
51
+ effectiveLocales = effectiveLocales.filter((l)=>allowed.has(l));
52
+ }
53
+ if (effectiveLocales.length === 0) {
54
+ return earlyReturn();
55
+ }
56
+ // Atomic get-or-create — see after-change.ts for the rationale.
57
+ // When `created: false`, another fire is already dispatching the
58
+ // translation; this fire returns early to avoid duplicate LLM
59
+ // calls and locale-write races.
60
+ const { jobId, created } = getOrCreateJobForDoc(globalSlug, globalSlug, effectiveLocales);
61
+ if (!created) {
62
+ return doc;
63
+ }
64
+ // Honor diffOnly:false — don't pass previousDoc downstream, so the
65
+ // per-unit diff filter in translateGlobal stays disabled.
66
+ const previousDocForTranslate = diffOnly ? previousDoc : undefined;
67
+ // When async mode is configured, route through the coalescing queue so a
68
+ // burst of saves on the same global collapses into one translation pass.
69
+ // Without the queue (inline mode, or queue not provided), fire-and-forget.
70
+ //
71
+ // Wrap dispatch in try/catch so a queue/translate failure can never
72
+ // propagate up to Payload and 500 the original save. Mirrors the
73
+ // pattern in after-change.ts.
74
+ try {
75
+ if (queue) {
76
+ queue.enqueue({
77
+ payload: req.payload,
78
+ kind: 'global',
79
+ collection: globalSlug,
80
+ id: globalSlug,
81
+ doc: doc,
82
+ previousDoc: previousDocForTranslate ?? {},
83
+ jobId,
84
+ req,
85
+ targetLocales: effectiveLocales
86
+ });
87
+ return doc;
88
+ }
89
+ // Fire-and-forget: never block the save.
90
+ // Awaiting here would deadlock the request transaction against the
91
+ // hook's own payload.updateGlobal calls on the locale rows we just
92
+ // wrote. Let the request commit, then translate in the background.
93
+ void translateGlobal(req.payload, {
94
+ global: globalSlug,
95
+ previousDoc: previousDocForTranslate,
96
+ jobId,
97
+ req,
98
+ targetLocales: effectiveLocales
99
+ }).catch((err)=>{
100
+ const msg = err instanceof Error ? err.message : 'Unknown error';
101
+ req.payload.logger.error(`[ai-translate] Auto-translation failed for global "${globalSlug}": ${msg}`);
102
+ });
103
+ } catch (error) {
104
+ const msg = error instanceof Error ? error.message : 'Unknown error';
105
+ req.payload.logger.error(`[ai-translate] Auto-translation dispatch failed for global "${globalSlug}": ${msg}`);
106
+ }
107
+ return doc;
108
+ };
109
+ }
@@ -0,0 +1,16 @@
1
+ import type { CollectionAfterChangeHook } from 'payload';
2
+ import type { CoalescingQueue } from '../lib/coalescing-queue.js';
3
+ import type { AITranslatePluginConfig } from '../types.js';
4
+ export type CreateAfterChangeHookOptions = {
5
+ collectionSlug: string;
6
+ pluginOptions: AITranslatePluginConfig;
7
+ queue: CoalescingQueue | null;
8
+ /**
9
+ * Whether the collection has draft/publish versioning. When false the
10
+ * `_status` field doesn't exist on the doc, so the on-publish trigger gate
11
+ * is skipped and every source-locale save is translated.
12
+ */
13
+ hasDrafts: boolean;
14
+ };
15
+ export declare function createTranslateAfterChangeHook(options: CreateAfterChangeHookOptions): CollectionAfterChangeHook;
16
+ export declare function createTranslateAfterChangeHook(collectionSlug: string, pluginOptions: AITranslatePluginConfig, queue: CoalescingQueue | null): CollectionAfterChangeHook;
@@ -0,0 +1,205 @@
1
+ import { translateDocument } from '../api.js';
2
+ import { REENTRY_FLAG, SKIP_FLAG } from '../defaults.js';
3
+ import { getDocAutoLocaleOverride, getDocOptOut, getEffectiveAutoLocales, getGlobalKillSwitches } from '../lib/effective-locales.js';
4
+ import { diffFields } from '../lib/field-diff.js';
5
+ import { resolveTranslatableFields } from '../lib/field-resolver.js';
6
+ import { completeJobNow, getActiveJobForDoc, getOrCreateJobForDoc } from '../lib/progress-store.js';
7
+ export function createTranslateAfterChangeHook(arg1, pluginOptionsArg, queueArg) {
8
+ const opts = typeof arg1 === 'string' ? {
9
+ collectionSlug: arg1,
10
+ pluginOptions: pluginOptionsArg,
11
+ queue: queueArg ?? null,
12
+ hasDrafts: true
13
+ } : arg1;
14
+ const { collectionSlug, pluginOptions, queue, hasDrafts } = opts;
15
+ const automation = pluginOptions.automation;
16
+ const trigger = automation?.trigger ?? 'on-change';
17
+ const diffOnly = automation?.diffOnly !== false; // default true
18
+ return async ({ req, doc, previousDoc, operation })=>{
19
+ // Re-entrancy guard — plugin's own writes and bulk imports skip translation
20
+ const ctx = req.context ?? {};
21
+ if (ctx[REENTRY_FLAG] || ctx[SKIP_FLAG]) {
22
+ return doc;
23
+ }
24
+ // Only translate source-locale saves
25
+ if (req.locale && req.locale !== pluginOptions.sourceLocale) {
26
+ return doc;
27
+ }
28
+ // Plugin-wide auto-translate kill switch (v1.2.8). When admin flipped
29
+ // `globalAutoTranslateEnabled` off in the settings global, this hook
30
+ // short-circuits before any other work — no progress job, no LLM
31
+ // dispatch, no restore-version recovery. Manual Translate and Bulk
32
+ // Translate still work because they don't route through this hook.
33
+ // Fail-safe-CLOSED on settings read failure: skip auto. See
34
+ // `getGlobalKillSwitches` for the per-flag reasoning.
35
+ const killSwitches = await getGlobalKillSwitches(req.payload, pluginOptions);
36
+ if (!killSwitches.autoEnabled) {
37
+ return doc;
38
+ }
39
+ // BUG-23 fix: when Payload's restoreVersion fires, target-locale rows
40
+ // get reset because the version snapshot only captured source content.
41
+ // Detect this via the `operation` hook arg (Payload sets it to
42
+ // 'restoreVersion' for restore writes) and FORCE auto-translate to
43
+ // re-populate target locales — even if `trigger: 'on-publish'` and
44
+ // the restore landed on a draft. Without this, editors lose all
45
+ // target-locale content silently and must remember to click Translate.
46
+ const isRestoreVersion = operation === 'restoreVersion';
47
+ if (isRestoreVersion) {
48
+ req.payload.logger?.warn?.(`[ai-translate] Version restore detected for ${collectionSlug}/${doc?.id}. Target-locale rows are being reset by Payload — the plugin will auto-translate to repopulate them. Editors who had manually refined target content should review after the auto-translate completes.`);
49
+ }
50
+ // On-publish trigger: only enforced when the collection actually has
51
+ // drafts. Without versioning, _status doesn't exist on the doc and we
52
+ // would gate forever, never translating. Treat non-versioned collections
53
+ // as on-change.
54
+ //
55
+ // BUG-23: restoreVersion bypasses the publish gate. Target locales
56
+ // get wiped regardless of whether the restored version was a draft
57
+ // or published — recovery must run either way.
58
+ let isPublishTransition = false;
59
+ if (trigger === 'on-publish' && hasDrafts && !isRestoreVersion) {
60
+ const currStatus = doc?._status;
61
+ if (currStatus !== 'published') {
62
+ return doc;
63
+ }
64
+ const prevStatus = previousDoc?._status;
65
+ isPublishTransition = prevStatus !== 'published';
66
+ }
67
+ // Field diff: skip if no translatable fields changed.
68
+ // EXCEPT on a publish-transition (draft → published) where the autosave
69
+ // baseline already matches the published content. In that case the diff
70
+ // is empty but the document was never translated yet — force translation
71
+ // by clearing previousDoc downstream.
72
+ //
73
+ // ALSO except on restoreVersion (BUG-23): the restore reset target
74
+ // locales so we MUST re-translate everything to recover. The diff
75
+ // path would skip because the source content matches a past version.
76
+ let forceTranslateAll = isRestoreVersion;
77
+ if (diffOnly && previousDoc && !isRestoreVersion) {
78
+ const fields = getCollectionFields(req.payload, collectionSlug);
79
+ if (fields) {
80
+ const translatableFields = resolveTranslatableFields(fields);
81
+ const fieldPaths = translatableFields.map((f)=>f.path);
82
+ const changedPaths = diffFields(previousDoc, doc, fieldPaths);
83
+ if (changedPaths.length === 0) {
84
+ if (!isPublishTransition) {
85
+ return doc;
86
+ }
87
+ // Publish-transition with identical content → translate all
88
+ forceTranslateAll = true;
89
+ }
90
+ }
91
+ }
92
+ const documentId = doc?.id;
93
+ if (!documentId) {
94
+ return doc;
95
+ }
96
+ // Helper: an early-return path that ALSO closes out any active job
97
+ // a parent flow may have seeded. Without this, a stranded `running`
98
+ // job hangs in the UI for ~2 minutes (TTL sweep) showing
99
+ // "Translating…" that never completes.
100
+ const earlyReturn = ()=>{
101
+ const stranded = getActiveJobForDoc(collectionSlug, documentId);
102
+ if (stranded) completeJobNow(stranded.jobId);
103
+ return doc;
104
+ };
105
+ // Per-doc opt-out gate. When the editor explicitly checked
106
+ // "skip auto-translate for this doc," bail before any other work.
107
+ // Manual Translate from the dialog is NOT gated by this — same
108
+ // scope as the collection-level `enabled` flag.
109
+ if (getDocOptOut(doc)) {
110
+ return earlyReturn();
111
+ }
112
+ // Resolve effective locales via the layered stack:
113
+ // plugin config → site-wide → per-collection → per-doc narrow
114
+ // Empty result = admin opted out of every locale (or the per-collection
115
+ // widget unchecked everything), so skip dispatch entirely (no ghost
116
+ // progress job, no LLM cost). Manual Translate button still works
117
+ // against config.targetLocales unaffected — these gates apply to the
118
+ // automation path only.
119
+ const decision = await getEffectiveAutoLocales(req.payload, pluginOptions, collectionSlug);
120
+ if (decision.skipAutoOnPublish || decision.locales.length === 0) {
121
+ return earlyReturn();
122
+ }
123
+ // Per-doc widget narrows the effective set further when set:
124
+ // - undefined / empty → inherit collection-level decision (no narrow)
125
+ // - [...] → restrict to these locales (intersected with decision)
126
+ // Opt-out is expressed via the separate `_aiTranslateOptOut` flag
127
+ // (checked above) — not via empty array here.
128
+ // Stale locales (codes no longer in plugin config) are silently
129
+ // dropped here so a stored ['de','fr','XX-removed'] still narrows
130
+ // to ['de','fr'] rather than failing the intersection.
131
+ const docOverride = getDocAutoLocaleOverride(doc);
132
+ let effectiveLocales = decision.locales;
133
+ if (docOverride !== undefined) {
134
+ const allowed = new Set(docOverride);
135
+ effectiveLocales = effectiveLocales.filter((l)=>allowed.has(l));
136
+ }
137
+ if (effectiveLocales.length === 0) {
138
+ return earlyReturn();
139
+ }
140
+ // When diffOnly is OFF, force a full retranslation by passing undefined —
141
+ // otherwise translateDocument's per-unit diff filter would still narrow
142
+ // to changed fields, which is the opposite of what diffOnly:false means.
143
+ const previousDocForTranslate = forceTranslateAll || !diffOnly ? undefined : previousDoc;
144
+ // Reuse an active job for this doc if one exists (e.g. queued
145
+ // translation still running) — otherwise create a fresh one so the
146
+ // UI can show "Translating..." immediately. The same jobId is
147
+ // threaded through to translateDocument so updateJob ticks land on
148
+ // the job the UI is polling.
149
+ //
150
+ // `getOrCreateJobForDoc` is atomic from the event-loop's
151
+ // perspective: two concurrent fires for the same doc can't both
152
+ // pass the existence check and both create a job. When `created`
153
+ // is false the second caller joins the in-flight job and the
154
+ // duplicate dispatch below becomes a redundant no-op
155
+ // (translateDocument is idempotent w.r.t. an already-completed
156
+ // job because updateJob silently no-ops on terminal status).
157
+ const { jobId, created } = getOrCreateJobForDoc(collectionSlug, documentId, effectiveLocales);
158
+ if (!created) {
159
+ // Another fire already created the job and is dispatching the
160
+ // translation. Don't fire again — the second LLM call would
161
+ // double-bill and race on locale writes.
162
+ return doc;
163
+ }
164
+ // Dispatch translation — never block the save
165
+ try {
166
+ if (queue) {
167
+ // Async mode: coalesce rapid saves
168
+ queue.enqueue({
169
+ payload: req.payload,
170
+ collection: collectionSlug,
171
+ id: documentId,
172
+ doc: doc,
173
+ previousDoc: previousDocForTranslate ?? {},
174
+ jobId,
175
+ req,
176
+ targetLocales: effectiveLocales
177
+ });
178
+ } else {
179
+ // Inline mode: fire-and-forget to avoid transaction deadlock
180
+ // (the hook holds a DB transaction, and translateDocument reads the same doc)
181
+ void translateDocument(req.payload, {
182
+ collection: collectionSlug,
183
+ id: documentId,
184
+ previousDoc: previousDocForTranslate,
185
+ jobId,
186
+ req,
187
+ targetLocales: effectiveLocales
188
+ }).catch((err)=>{
189
+ req.payload.logger.error(`[ai-translate] Inline translation failed for ${collectionSlug}/${String(documentId)}: ${err instanceof Error ? err.message : 'Unknown'}`);
190
+ });
191
+ }
192
+ } catch (error) {
193
+ const msg = error instanceof Error ? error.message : 'Unknown error';
194
+ req.payload.logger.error(`[ai-translate] Auto-translation failed for ${collectionSlug}/${String(documentId)}: ${msg}`);
195
+ }
196
+ return doc;
197
+ };
198
+ }
199
+ // ---------------------------------------------------------------------------
200
+ // Helpers
201
+ // ---------------------------------------------------------------------------
202
+ function getCollectionFields(payload, slug) {
203
+ const collection = payload.config?.collections?.find((c)=>c.slug === slug);
204
+ return collection?.fields;
205
+ }
@@ -0,0 +1,30 @@
1
+ import type { CollectionAfterDeleteHook } from 'payload';
2
+ import type { AITranslatePluginConfig } from '../types.js';
3
+ /**
4
+ * After-delete cleanup hook (BUG-20 + BUG-26).
5
+ *
6
+ * When a tracked-collection document is deleted, two plugin-owned tables
7
+ * still hold rows referencing the doc:
8
+ *
9
+ * - `ai_translate_jobs` — visible "Translating..." rows in the admin
10
+ * Jobs page. Without cleanup, deleted docs leave ghost-running jobs
11
+ * here that the UI shows forever (BUG-26 stuck-running on bulk
12
+ * delete during active translation).
13
+ *
14
+ * - `ai_translate_meta` — manual-edit fingerprints. Without cleanup,
15
+ * stale fingerprints accumulate across the table's lifetime; if a
16
+ * new doc reuses the same numeric id (rare but possible on
17
+ * short-cycle test DBs), the stale fingerprint blocks the first
18
+ * translation. Storage just keeps growing unboundedly (BUG-20).
19
+ *
20
+ * The hook runs AFTER Payload's delete operation completes — best-
21
+ * effort. If cleanup fails the doc deletion still succeeded; we log
22
+ * but don't throw. Brief orphan windows are tolerated; the next save
23
+ * on a different doc isn't affected.
24
+ *
25
+ * In-memory progress-store cleanup: also marks any active in-memory
26
+ * job for this doc as completed (with reason: 'document deleted')
27
+ * so SSE subscribers see the terminal state immediately rather than
28
+ * waiting for the 2-minute TTL sweep.
29
+ */
30
+ export declare function createAiTranslateAfterDeleteHook(collectionSlug: string, pluginOptions: AITranslatePluginConfig): CollectionAfterDeleteHook;
@@ -0,0 +1,95 @@
1
+ import { DEFAULT_JOBS_COLLECTION_SLUG } from '../jobs-collection.js';
2
+ import { completeJobNow, getActiveJobForDoc } from '../lib/progress-store.js';
3
+ import { DEFAULT_MANUAL_EDIT_COLLECTION_SLUG } from '../manual-edit-collection.js';
4
+ /**
5
+ * After-delete cleanup hook (BUG-20 + BUG-26).
6
+ *
7
+ * When a tracked-collection document is deleted, two plugin-owned tables
8
+ * still hold rows referencing the doc:
9
+ *
10
+ * - `ai_translate_jobs` — visible "Translating..." rows in the admin
11
+ * Jobs page. Without cleanup, deleted docs leave ghost-running jobs
12
+ * here that the UI shows forever (BUG-26 stuck-running on bulk
13
+ * delete during active translation).
14
+ *
15
+ * - `ai_translate_meta` — manual-edit fingerprints. Without cleanup,
16
+ * stale fingerprints accumulate across the table's lifetime; if a
17
+ * new doc reuses the same numeric id (rare but possible on
18
+ * short-cycle test DBs), the stale fingerprint blocks the first
19
+ * translation. Storage just keeps growing unboundedly (BUG-20).
20
+ *
21
+ * The hook runs AFTER Payload's delete operation completes — best-
22
+ * effort. If cleanup fails the doc deletion still succeeded; we log
23
+ * but don't throw. Brief orphan windows are tolerated; the next save
24
+ * on a different doc isn't affected.
25
+ *
26
+ * In-memory progress-store cleanup: also marks any active in-memory
27
+ * job for this doc as completed (with reason: 'document deleted')
28
+ * so SSE subscribers see the terminal state immediately rather than
29
+ * waiting for the 2-minute TTL sweep.
30
+ */ export function createAiTranslateAfterDeleteHook(collectionSlug, pluginOptions) {
31
+ return async ({ id, req })=>{
32
+ if (id === undefined || id === null) return;
33
+ // 1. Close out the in-memory progress-store job (UI feedback)
34
+ try {
35
+ const active = getActiveJobForDoc(collectionSlug, id);
36
+ if (active) {
37
+ completeJobNow(active.jobId);
38
+ }
39
+ } catch (err) {
40
+ req.payload.logger?.warn?.(`[ai-translate] In-memory job cleanup failed for ${collectionSlug}/${String(id)}: ${err instanceof Error ? err.message : 'Unknown'}`);
41
+ }
42
+ // 2. Delete persisted `ai_translate_jobs` rows for this doc
43
+ if (pluginOptions.persistJobs) {
44
+ const jobsSlug = pluginOptions.jobsCollectionSlug ?? DEFAULT_JOBS_COLLECTION_SLUG;
45
+ try {
46
+ await req.payload.delete({
47
+ collection: jobsSlug,
48
+ where: {
49
+ and: [
50
+ {
51
+ collection: {
52
+ equals: collectionSlug
53
+ }
54
+ },
55
+ {
56
+ documentId: {
57
+ equals: String(id)
58
+ }
59
+ }
60
+ ]
61
+ },
62
+ overrideAccess: true
63
+ });
64
+ } catch (err) {
65
+ req.payload.logger?.warn?.(`[ai-translate] ai_translate_jobs cleanup failed for ${collectionSlug}/${String(id)}: ${err instanceof Error ? err.message : 'Unknown'}`);
66
+ }
67
+ }
68
+ // 3. Delete `ai_translate_meta` fingerprints for this doc
69
+ if (pluginOptions.preserveManualEdits) {
70
+ const metaSlug = pluginOptions.manualEditCollectionSlug ?? DEFAULT_MANUAL_EDIT_COLLECTION_SLUG;
71
+ try {
72
+ await req.payload.delete({
73
+ collection: metaSlug,
74
+ where: {
75
+ and: [
76
+ {
77
+ collection: {
78
+ equals: collectionSlug
79
+ }
80
+ },
81
+ {
82
+ documentId: {
83
+ equals: String(id)
84
+ }
85
+ }
86
+ ]
87
+ },
88
+ overrideAccess: true
89
+ });
90
+ } catch (err) {
91
+ req.payload.logger?.warn?.(`[ai-translate] ai_translate_meta cleanup failed for ${collectionSlug}/${String(id)}: ${err instanceof Error ? err.message : 'Unknown'}`);
92
+ }
93
+ }
94
+ };
95
+ }
@@ -0,0 +1,5 @@
1
+ export { translateDocument } from './api.js';
2
+ export { ensureBulkTranslateSchema, migrateBatchStatusVocabulary, migrateHashesToV1Prefix, } from './lib/bulk-translate-migrations.js';
3
+ export { createScopedLogger, type LogContext, type LogEventFields, type LogLevel, type ScopedLogger, type SerializedError, serializeErr, } from './lib/logger.js';
4
+ export { type AcquireTokenResult, acquireToken, getBucketStatus, type TokenBucketOptions, } from './lib/translation-token-bucket.js';
5
+ export { aiTranslatePlugin } from './plugin.js';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { translateDocument } from './api.js';
2
+ export { ensureBulkTranslateSchema, migrateBatchStatusVocabulary, migrateHashesToV1Prefix } from './lib/bulk-translate-migrations.js';
3
+ export { createScopedLogger, serializeErr } from './lib/logger.js';
4
+ export { acquireToken, getBucketStatus } from './lib/translation-token-bucket.js';
5
+ export { aiTranslatePlugin } from './plugin.js';
@@ -0,0 +1,17 @@
1
+ import type { Access, CollectionConfig } from 'payload';
2
+ export declare const DEFAULT_JOBS_COLLECTION_SLUG = "ai-translate-jobs";
3
+ /**
4
+ * Optional sidecar collection used by `persistJobs`. Mirrors the in-
5
+ * memory progress-store state so admin views, debug pages, or post-
6
+ * restart inspection can see which translations were in flight.
7
+ *
8
+ * NOT a full work-queue — the actual translation closures (req, doc,
9
+ * previousDoc) stay in process memory. A server restart still loses
10
+ * the in-flight LLM call. This collection persists the STATUS only so
11
+ * admins can spot "ghost" jobs that stopped progressing and re-trigger
12
+ * them manually.
13
+ *
14
+ * For full work resumption, see the follow-up ticket on migrating to
15
+ * Payload's native jobs queue (`payload.jobs.queue`).
16
+ */
17
+ export declare function createJobsCollection(readAccess?: Access, slug?: string, deleteAccess?: Access): CollectionConfig;
@@ -0,0 +1,139 @@
1
+ export const DEFAULT_JOBS_COLLECTION_SLUG = 'ai-translate-jobs';
2
+ /**
3
+ * Optional sidecar collection used by `persistJobs`. Mirrors the in-
4
+ * memory progress-store state so admin views, debug pages, or post-
5
+ * restart inspection can see which translations were in flight.
6
+ *
7
+ * NOT a full work-queue — the actual translation closures (req, doc,
8
+ * previousDoc) stay in process memory. A server restart still loses
9
+ * the in-flight LLM call. This collection persists the STATUS only so
10
+ * admins can spot "ghost" jobs that stopped progressing and re-trigger
11
+ * them manually.
12
+ *
13
+ * For full work resumption, see the follow-up ticket on migrating to
14
+ * Payload's native jobs queue (`payload.jobs.queue`).
15
+ */ export function createJobsCollection(readAccess, slug = DEFAULT_JOBS_COLLECTION_SLUG, deleteAccess) {
16
+ const isAdminDefault = ({ req })=>{
17
+ const user = req.user;
18
+ const roles = user?.roles;
19
+ if (Array.isArray(roles) && roles.includes('admin')) return true;
20
+ return false;
21
+ };
22
+ return {
23
+ slug,
24
+ // System rows are never edited in the admin document view, so
25
+ // Payload's document-locking buys nothing here — and its lock check
26
+ // costs a second pool connection inside every update transaction
27
+ // (core's checkDocumentLockStatus runs a find without `req`). Under
28
+ // concurrent updates (e.g. dismiss-all on alerts) that exhausted the
29
+ // pool and deadlocked a consumer in prod on 2026-06-10.
30
+ lockDocuments: false,
31
+ labels: {
32
+ singular: 'AI Translate Job (in-flight)',
33
+ plural: 'AI Translate Jobs — in-flight queue'
34
+ },
35
+ admin: {
36
+ group: 'System',
37
+ useAsTitle: 'jobId',
38
+ defaultColumns: [
39
+ 'jobId',
40
+ 'collection',
41
+ 'documentId',
42
+ 'status',
43
+ 'startedAt'
44
+ ],
45
+ hidden: ({ user })=>{
46
+ const roles = user?.roles;
47
+ return !Array.isArray(roles) || !roles.includes('admin');
48
+ },
49
+ description: 'IN-FLIGHT QUEUE ONLY — not a permanent log. Each row mirrors a currently-running or just-finished translation job. Rows auto-expire ~2 minutes after the job completes. If this view is EMPTY, no translations are currently running — that is the normal idle state. For permanent translation history + cost data, see "Translation Usage".'
50
+ },
51
+ access: {
52
+ read: readAccess ?? isAdminDefault,
53
+ create: ()=>false,
54
+ update: ()=>false,
55
+ delete: deleteAccess ?? isAdminDefault
56
+ },
57
+ fields: [
58
+ {
59
+ name: 'jobId',
60
+ type: 'text',
61
+ required: true,
62
+ index: true,
63
+ admin: {
64
+ readOnly: true
65
+ }
66
+ },
67
+ {
68
+ name: 'collection',
69
+ type: 'text',
70
+ required: true,
71
+ index: true,
72
+ admin: {
73
+ readOnly: true
74
+ }
75
+ },
76
+ {
77
+ name: 'documentId',
78
+ type: 'text',
79
+ required: true,
80
+ index: true,
81
+ admin: {
82
+ readOnly: true
83
+ }
84
+ },
85
+ {
86
+ name: 'status',
87
+ type: 'select',
88
+ required: true,
89
+ options: [
90
+ {
91
+ label: 'Running',
92
+ value: 'running'
93
+ },
94
+ {
95
+ label: 'Completed',
96
+ value: 'completed'
97
+ },
98
+ {
99
+ label: 'Failed',
100
+ value: 'failed'
101
+ }
102
+ ],
103
+ admin: {
104
+ readOnly: true
105
+ }
106
+ },
107
+ {
108
+ name: 'totalLocales',
109
+ type: 'number',
110
+ required: true,
111
+ admin: {
112
+ readOnly: true
113
+ }
114
+ },
115
+ {
116
+ name: 'completedLocales',
117
+ type: 'json',
118
+ admin: {
119
+ readOnly: true
120
+ }
121
+ },
122
+ {
123
+ name: 'failedLocales',
124
+ type: 'json',
125
+ admin: {
126
+ readOnly: true,
127
+ description: 'Array of `{ locale, error }` for failed locales.'
128
+ }
129
+ },
130
+ {
131
+ name: 'startedAt',
132
+ type: 'date',
133
+ admin: {
134
+ readOnly: true
135
+ }
136
+ }
137
+ ]
138
+ };
139
+ }
@@ -0,0 +1,3 @@
1
+ import type { LexicalNodeRegistration } from '../types.js';
2
+ import type { ClassifiedNode, LexicalNode } from './types.js';
3
+ export declare function classifyNode(node: LexicalNode, registeredNodes?: LexicalNodeRegistration[]): ClassifiedNode;