@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,153 @@
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 { DEFAULT_TRANSLATION_DAILY_SPEND_COLLECTION_SLUG } from '../translation-daily-spend-collection.js';
4
+ export async function ensureBulkTranslateSchema(payload, opts = {}) {
5
+ const unitsSlug = opts.bulkTranslateUnitsSlug ?? DEFAULT_BULK_TRANSLATE_UNITS_COLLECTION_SLUG;
6
+ const dailySpendSlug = opts.translationDailySpendSlug ?? DEFAULT_TRANSLATION_DAILY_SPEND_COLLECTION_SLUG;
7
+ // Convert slug to table name. Payload's Postgres adapter snake-cases
8
+ // collection slugs into table names: `bulk-translate-units` →
9
+ // `bulk_translate_units`. If a consumer customizes the slug, the
10
+ // same transform applies.
11
+ const unitsTable = slugToTable(unitsSlug);
12
+ const dailySpendTable = slugToTable(dailySpendSlug);
13
+ const db = getDrizzle(payload);
14
+ if (!db) {
15
+ payload.logger?.warn?.('[ai-translate] ensureBulkTranslateSchema: payload.db.drizzle unavailable. Skipping. ' + 'Hand-write the indexes in your consumer migration if your adapter is not Postgres.');
16
+ return;
17
+ }
18
+ // Partial unique index on bulk-translate-units. The CREATE INDEX
19
+ // IF NOT EXISTS keyword is Postgres-specific; this helper is
20
+ // explicitly Postgres-only (per the plugin's existing adapter
21
+ // assumption in `utilities/helpers.ts:atomicUpdateLastCounter`).
22
+ const unitsIndexSql = `
23
+ CREATE UNIQUE INDEX IF NOT EXISTS ${unitsTable}_active_dedup
24
+ ON ${unitsTable} (collection, document_id, locale)
25
+ WHERE status IN ('pending','running');
26
+ `;
27
+ const dailySpendUniqueSql = `
28
+ CREATE UNIQUE INDEX IF NOT EXISTS ${dailySpendTable}_date_consumer_uniq
29
+ ON ${dailySpendTable} (date, consumer_key);
30
+ `;
31
+ await runIdempotent(payload, db, unitsIndexSql, `${unitsTable}_active_dedup`);
32
+ await runIdempotent(payload, db, dailySpendUniqueSql, `${dailySpendTable}_date_consumer_uniq`);
33
+ }
34
+ /**
35
+ * One-off data migration: prefix existing `ai-translate-meta`
36
+ * source-hash rows with the `v1:` version tag (R2 / F-ENG-07).
37
+ *
38
+ * Without this, the first bulk run after a plugin upgrade would treat
39
+ * every previously-translated field as "source changed" — burning
40
+ * tokens to re-translate everything that was already in sync.
41
+ *
42
+ * Idempotent: rows that already start with `v1:` are skipped. Safe
43
+ * to run repeatedly. Consumer should run ONCE per deploy that bumps
44
+ * past 1.1.16. Returns a count of rows updated.
45
+ */ export async function migrateHashesToV1Prefix(payload, opts = {}) {
46
+ const metaSlug = opts.metaSlug ?? 'ai-translate-meta';
47
+ const metaTable = slugToTable(metaSlug);
48
+ const db = getDrizzle(payload);
49
+ if (!db) {
50
+ throw new Error('[ai-translate] migrateHashesToV1Prefix: payload.db.drizzle unavailable. Non-Postgres adapters must roll their own migration.');
51
+ }
52
+ const scanResult = await db.execute(`SELECT count(*) AS count FROM ${metaTable} WHERE last_source_hash IS NOT NULL AND last_source_hash NOT LIKE 'v1:%'`);
53
+ const needsMigration = Number(scanResult.rows[0]?.count ?? 0);
54
+ const alreadyResult = await db.execute(`SELECT count(*) AS count FROM ${metaTable} WHERE last_source_hash LIKE 'v1:%'`);
55
+ const alreadyMigrated = Number(alreadyResult.rows[0]?.count ?? 0);
56
+ if (opts.dryRun) {
57
+ return {
58
+ scanned: needsMigration,
59
+ updated: 0,
60
+ alreadyMigrated
61
+ };
62
+ }
63
+ if (needsMigration === 0) {
64
+ return {
65
+ scanned: 0,
66
+ updated: 0,
67
+ alreadyMigrated
68
+ };
69
+ }
70
+ await db.execute(`UPDATE ${metaTable} SET last_source_hash = 'v1:' || last_source_hash WHERE last_source_hash IS NOT NULL AND last_source_hash NOT LIKE 'v1:%'`);
71
+ payload.logger?.info?.(`[ai-translate] migrateHashesToV1Prefix: prefixed ${needsMigration} rows; ${alreadyMigrated} already v1.`);
72
+ return {
73
+ scanned: needsMigration,
74
+ updated: needsMigration,
75
+ alreadyMigrated
76
+ };
77
+ }
78
+ /**
79
+ * v1.2.4 data migration: align `bulk_translate_batches.status` vocabulary
80
+ * on `'success'` (matching the units collection + the
81
+ * `BulkTranslateBatchStatus` TS union). Pre-1.2.4 the schema declared
82
+ * `'completed'` as the terminal-success value while the type union used
83
+ * `'success'`, so badges, filter chips, and the Hub's "recently
84
+ * terminal" query were all silently dead for successful runs. Worker
85
+ * + schema now write `'success'`; this helper backfills existing rows.
86
+ *
87
+ * Idempotent: only updates rows where status = 'completed'. Safe to run
88
+ * repeatedly; subsequent calls are no-ops.
89
+ *
90
+ * Consumer wiring:
91
+ * - Add to a forward migration's up() OR
92
+ * - Call from plugin onInit() once per deploy (boot-time self-heal).
93
+ */ export async function migrateBatchStatusVocabulary(payload, opts = {}) {
94
+ const batchesSlug = opts.batchesSlug ?? DEFAULT_BULK_TRANSLATE_BATCHES_COLLECTION_SLUG;
95
+ const batchesTable = slugToTable(batchesSlug);
96
+ const db = getDrizzle(payload);
97
+ if (!db) {
98
+ throw new Error('[ai-translate] migrateBatchStatusVocabulary: payload.db.drizzle unavailable. Non-Postgres adapters must roll their own migration.');
99
+ }
100
+ const scanResult = await db.execute(`SELECT count(*) AS count FROM ${batchesTable} WHERE status = 'completed'`);
101
+ const needs = Number(scanResult.rows[0]?.count ?? 0);
102
+ const alreadyResult = await db.execute(`SELECT count(*) AS count FROM ${batchesTable} WHERE status = 'success'`);
103
+ const already = Number(alreadyResult.rows[0]?.count ?? 0);
104
+ if (opts.dryRun) {
105
+ return {
106
+ scanned: needs,
107
+ updated: 0,
108
+ alreadyAligned: already
109
+ };
110
+ }
111
+ if (needs === 0) {
112
+ return {
113
+ scanned: 0,
114
+ updated: 0,
115
+ alreadyAligned: already
116
+ };
117
+ }
118
+ await db.execute(`UPDATE ${batchesTable} SET status = 'success', updated_at = now() WHERE status = 'completed'`);
119
+ payload.logger?.info?.(`[ai-translate] migrateBatchStatusVocabulary: updated ${needs} rows from 'completed' to 'success'; ${already} already aligned.`);
120
+ return {
121
+ scanned: needs,
122
+ updated: needs,
123
+ alreadyAligned: already
124
+ };
125
+ }
126
+ export function slugToTable(slug) {
127
+ // Match Payload's snake_case table-naming. Slugs are usually
128
+ // already kebab-case; replace dashes with underscores. Validate
129
+ // identifier shape to avoid SQL injection via consumer-provided
130
+ // slug overrides (AUDIT-H-06).
131
+ if (!/^[a-z][a-z0-9_-]{0,62}$/.test(slug)) {
132
+ throw new Error(`[ai-translate] slugToTable: invalid collection slug "${slug}" (must match /^[a-z][a-z0-9_-]{0,62}$/)`);
133
+ }
134
+ return slug.replace(/-/g, '_');
135
+ }
136
+ export function getDrizzle(payload) {
137
+ const db = payload.db?.drizzle;
138
+ if (!db || typeof db.execute !== 'function') return null;
139
+ return db;
140
+ }
141
+ async function runIdempotent(payload, db, sql, label) {
142
+ try {
143
+ await db.execute(sql);
144
+ payload.logger?.info?.(`[ai-translate] schema: ensured ${label}.`);
145
+ } catch (err) {
146
+ // The IF NOT EXISTS guard should prevent most "already exists"
147
+ // errors, but if the table itself is missing (plugin booting
148
+ // before consumer ran their initial migrations) this can throw.
149
+ // Log as warn — boot proceeds, the engine will fail at first
150
+ // bulk run with a clear error.
151
+ payload.logger?.warn?.(`[ai-translate] schema: failed to ensure ${label}: ${err instanceof Error ? err.message : String(err)}`);
152
+ }
153
+ }
@@ -0,0 +1,38 @@
1
+ import type { Payload, PayloadRequest } from 'payload';
2
+ export type CoalescingEntry = {
3
+ payload: Payload;
4
+ /**
5
+ * Discriminator so the queue worker dispatches to `translateDocument`
6
+ * for collections and `translateGlobal` for globals. Defaults to
7
+ * `'collection'` for backwards compatibility with consumers that
8
+ * haven't yet started passing globals through this queue.
9
+ */
10
+ kind?: 'collection' | 'global';
11
+ /** Collection slug or global slug, depending on `kind`. */
12
+ collection: string;
13
+ id: string | number;
14
+ doc: Record<string, unknown>;
15
+ previousDoc: Record<string, unknown>;
16
+ /**
17
+ * Optional jobId so the queue worker reuses an already-created progress job
18
+ * (the after-change hook seeds one for instant UI feedback).
19
+ */
20
+ jobId?: string;
21
+ /** Originating request — threaded into locale writes for audit attribution. */
22
+ req?: PayloadRequest;
23
+ /**
24
+ * Effective locale list for this dispatch — overrides config.targetLocales
25
+ * downstream. The after-change hook computes this from the
26
+ * translation-settings global so admin opt-outs are honored. Omitted =
27
+ * fall back to config.targetLocales in the translate API.
28
+ */
29
+ targetLocales?: string[];
30
+ };
31
+ export type CoalescingCallback = (entry: CoalescingEntry) => Promise<void>;
32
+ export type CoalescingQueue = {
33
+ enqueue(entry: CoalescingEntry): void;
34
+ cancel(collection: string, id: string | number): void;
35
+ pending(): number;
36
+ flush(): Promise<void>;
37
+ };
38
+ export declare function createCoalescingQueue(windowMs: number, callback: CoalescingCallback): CoalescingQueue;
@@ -0,0 +1,69 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Implementation
3
+ // ---------------------------------------------------------------------------
4
+ export function createCoalescingQueue(windowMs, callback) {
5
+ const entries = new Map();
6
+ function makeKey(collection, id) {
7
+ // Globals key on `<slug>:<slug>` — same pattern collections use, just
8
+ // with id == slug since globals are singletons.
9
+ return `${collection}:${String(id)}`;
10
+ }
11
+ async function fire(key) {
12
+ const pending = entries.get(key);
13
+ if (!pending) return;
14
+ entries.delete(key);
15
+ try {
16
+ await callback(pending.entry);
17
+ } catch (error) {
18
+ const msg = error instanceof Error ? error.message : 'Unknown error';
19
+ console.error(`[ai-translate] Coalescing callback failed for ${key}: ${msg}`);
20
+ }
21
+ }
22
+ return {
23
+ enqueue (entry) {
24
+ const key = makeKey(entry.collection, entry.id);
25
+ // Cancel existing pending entry for this key
26
+ const existing = entries.get(key);
27
+ if (existing) {
28
+ clearTimeout(existing.timer);
29
+ // Keep the original previousDoc so field-diff captures all changes
30
+ // from the first save, not just the latest delta
31
+ entry = {
32
+ ...entry,
33
+ previousDoc: existing.entry.previousDoc
34
+ };
35
+ }
36
+ // Set new timer with updated entry
37
+ const timer = setTimeout(()=>{
38
+ void fire(key);
39
+ }, windowMs);
40
+ entries.set(key, {
41
+ entry,
42
+ timer
43
+ });
44
+ },
45
+ cancel (collection, id) {
46
+ const key = makeKey(collection, id);
47
+ const existing = entries.get(key);
48
+ if (existing) {
49
+ clearTimeout(existing.timer);
50
+ entries.delete(key);
51
+ }
52
+ },
53
+ pending () {
54
+ return entries.size;
55
+ },
56
+ async flush () {
57
+ const keys = [
58
+ ...entries.keys()
59
+ ];
60
+ for (const key of keys){
61
+ const existing = entries.get(key);
62
+ if (existing) {
63
+ clearTimeout(existing.timer);
64
+ }
65
+ await fire(key);
66
+ }
67
+ }
68
+ };
69
+ }
@@ -0,0 +1,16 @@
1
+ import type { Block, Field } from 'payload';
2
+ import type { TranslatableField, TranslationUnit } from '../types.js';
3
+ /**
4
+ * Build a `blockType → Block` map from a Payload collection / global's
5
+ * raw `fields` array. Walks `type: 'blocks'` fields anywhere in the tree
6
+ * (including under tabs, groups, arrays) and gathers their registered
7
+ * `blocks: Block[]` configurations.
8
+ *
9
+ * The result lets the extractor gate which block-internal keys are
10
+ * translatable based on the SCHEMA rather than the heuristic
11
+ * "every-string-is-text" walk that produces false positives for select
12
+ * values, color codes, etc. (the BUG-04 family).
13
+ */
14
+ export declare function collectBlocksConfig(fields: Field[]): Map<string, Block>;
15
+ export declare function extractTranslationUnits(doc: Record<string, unknown>, fields: TranslatableField[], excludePatterns: string[], blocksConfig?: Map<string, Block> | null): TranslationUnit[];
16
+ export declare function getValueAtPath(obj: Record<string, unknown>, path: string): unknown;