@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,30 @@
1
+ /**
2
+ * Check whether a given doc has any per-locale row in
3
+ * `<collection>_locales` for the target locale.
4
+ *
5
+ * Why this exists: Payload's `findByIdNoFallback(locale: X)` returns
6
+ * source-locale field values for SHARED parent tables (block parents,
7
+ * array parents) when the target locale has no row in `<col>_locales`.
8
+ * Downstream code in the translate flow then treats those source-fallback
9
+ * values as "real target data," merges translations on top, and writes
10
+ * them back with the source-locale row IDs — colliding with the source's
11
+ * own rows in the shared parent tables (PK violation surfacing as
12
+ * "Value must be unique: id" on the page-level update).
13
+ *
14
+ * The fix path: before merging, probe `<collection>_locales` directly
15
+ * via the pg pool. If zero rows for (parent=docId, locale=targetLocale),
16
+ * force the merge to take the "fresh target" branch so it strips array
17
+ * row ids and lets Payload generate fresh ones for the new locale.
18
+ *
19
+ * Postgres-only — Payload's other adapter (mongodb) handles this
20
+ * differently and doesn't hit the same race. Returns `true` (assume
21
+ * row exists) on non-Postgres or on any error so we never block a
22
+ * translation that would otherwise succeed.
23
+ */
24
+ import type { Payload } from 'payload';
25
+ /**
26
+ * Probe `<collection>_locales` for (parent=documentId, _locale=locale).
27
+ * Returns `true` when at least one row exists, `false` when zero rows,
28
+ * and `true` (defensive) when the check can't run.
29
+ */
30
+ export declare function checkLocaleRowExists(payload: Payload, collection: string, documentId: string | number, locale: string): Promise<boolean>;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Check whether a given doc has any per-locale row in
3
+ * `<collection>_locales` for the target locale.
4
+ *
5
+ * Why this exists: Payload's `findByIdNoFallback(locale: X)` returns
6
+ * source-locale field values for SHARED parent tables (block parents,
7
+ * array parents) when the target locale has no row in `<col>_locales`.
8
+ * Downstream code in the translate flow then treats those source-fallback
9
+ * values as "real target data," merges translations on top, and writes
10
+ * them back with the source-locale row IDs — colliding with the source's
11
+ * own rows in the shared parent tables (PK violation surfacing as
12
+ * "Value must be unique: id" on the page-level update).
13
+ *
14
+ * The fix path: before merging, probe `<collection>_locales` directly
15
+ * via the pg pool. If zero rows for (parent=docId, locale=targetLocale),
16
+ * force the merge to take the "fresh target" branch so it strips array
17
+ * row ids and lets Payload generate fresh ones for the new locale.
18
+ *
19
+ * Postgres-only — Payload's other adapter (mongodb) handles this
20
+ * differently and doesn't hit the same race. Returns `true` (assume
21
+ * row exists) on non-Postgres or on any error so we never block a
22
+ * translation that would otherwise succeed.
23
+ */ /**
24
+ * Probe `<collection>_locales` for (parent=documentId, _locale=locale).
25
+ * Returns `true` when at least one row exists, `false` when zero rows,
26
+ * and `true` (defensive) when the check can't run.
27
+ */ export async function checkLocaleRowExists(payload, collection, documentId, locale) {
28
+ const dbHandle = payload.db;
29
+ const pool = dbHandle?.pool;
30
+ if (!pool || typeof pool.connect !== 'function') {
31
+ // Non-Postgres or no pool — fall back to "assume present" so we
32
+ // don't block writes. The id-collision bug this guards against is
33
+ // Postgres-specific anyway.
34
+ return true;
35
+ }
36
+ // Sanitize the table name. Payload's collection slug → table name
37
+ // conversion replaces `-` with `_`; same here. Single underscore-only
38
+ // alphanumeric guard prevents injection even though we control all
39
+ // call sites today.
40
+ const tableBase = collection.replace(/-/g, '_');
41
+ if (!/^[a-zA-Z0-9_]+$/.test(tableBase)) {
42
+ return true;
43
+ }
44
+ const localesTable = `${tableBase}_locales`;
45
+ let client;
46
+ try {
47
+ client = await pool.connect();
48
+ const res = await client.query(// `_locale` is the per-locale row's discriminator column;
49
+ // `_parent_id` joins back to the parent doc.
50
+ `SELECT 1 FROM "${localesTable}" WHERE "_parent_id" = $1 AND "_locale" = $2 LIMIT 1`, [
51
+ documentId,
52
+ locale
53
+ ]);
54
+ return res.rows.length > 0;
55
+ } catch (err) {
56
+ // Table doesn't exist (no localized fields at all), pool failure,
57
+ // SQL error — any of these should not block a translate. Return
58
+ // `true` so the merge uses its existing targetValue branch.
59
+ payload.logger?.debug?.(`[ai-translate] checkLocaleRowExists(${collection}, ${String(documentId)}, ${locale}) probe failed (assuming present): ${String(err)}`);
60
+ return true;
61
+ } finally{
62
+ if (client) client.release();
63
+ }
64
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Structured logger wrapper around `payload.logger` (pino). Emits every
3
+ * line as `(object, msg)` so transports / Grafana can index fields like
4
+ * `event`, `batchId`, `err.name`, `err.cause.name`. The plugin previously
5
+ * used the `(msg)` form everywhere, throwing structure away.
6
+ *
7
+ * Usage:
8
+ *
9
+ * const log = createScopedLogger(payload, {
10
+ * component: 'bulk.worker', batchId, unitId,
11
+ * });
12
+ * log.event('error', 'bulk.worker.unit.failed', { err, durationMs });
13
+ *
14
+ * Levels:
15
+ * - error: terminal failure, batch → failed, audit-write failed
16
+ * - warn: transient retry, fallback / degradation, unknown classifier
17
+ * - info: lifecycle (unit start/end, batch transition, provider resolved)
18
+ * - debug: per-retry state, per-unit heartbeat
19
+ */
20
+ import type { Payload } from 'payload';
21
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
22
+ export interface LogContext {
23
+ module?: string;
24
+ component?: string;
25
+ batchId?: string | number;
26
+ unitId?: string | number;
27
+ jobId?: string | number;
28
+ collection?: string;
29
+ documentId?: string | number;
30
+ locale?: string;
31
+ provider?: string;
32
+ model?: string;
33
+ [key: string]: unknown;
34
+ }
35
+ export interface LogEventFields {
36
+ err?: unknown;
37
+ durationMs?: number;
38
+ attempt?: number;
39
+ maxAttempts?: number;
40
+ failureCode?: string;
41
+ [key: string]: unknown;
42
+ }
43
+ export interface ScopedLogger {
44
+ event(level: LogLevel, eventName: string, fields?: LogEventFields): void;
45
+ child(extraContext: LogContext): ScopedLogger;
46
+ }
47
+ interface PinoLikeLogger {
48
+ debug?: (...args: unknown[]) => void;
49
+ info?: (...args: unknown[]) => void;
50
+ warn?: (...args: unknown[]) => void;
51
+ error?: (...args: unknown[]) => void;
52
+ }
53
+ type PayloadLike = Pick<Payload, 'logger'> | {
54
+ logger?: PinoLikeLogger;
55
+ };
56
+ export declare function createScopedLogger(payload: PayloadLike, context?: LogContext): ScopedLogger;
57
+ export interface SerializedError {
58
+ name: string;
59
+ message: string;
60
+ stack?: string;
61
+ code?: string | number;
62
+ status?: number;
63
+ body?: unknown;
64
+ cause?: SerializedError;
65
+ [key: string]: unknown;
66
+ }
67
+ /**
68
+ * Recursively serialise an error-like value. Captures `name`, `message`,
69
+ * `stack`, `cause`, plus any extra enumerable own properties (HTTP-client
70
+ * errors typically expose `status`, `body`, `code`). Mirrors pino's
71
+ * default `err` serializer so Grafana queries on `err.cause.status` work.
72
+ */
73
+ export declare function serializeErr(err: unknown): SerializedError | undefined;
74
+ export {};
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Structured logger wrapper around `payload.logger` (pino). Emits every
3
+ * line as `(object, msg)` so transports / Grafana can index fields like
4
+ * `event`, `batchId`, `err.name`, `err.cause.name`. The plugin previously
5
+ * used the `(msg)` form everywhere, throwing structure away.
6
+ *
7
+ * Usage:
8
+ *
9
+ * const log = createScopedLogger(payload, {
10
+ * component: 'bulk.worker', batchId, unitId,
11
+ * });
12
+ * log.event('error', 'bulk.worker.unit.failed', { err, durationMs });
13
+ *
14
+ * Levels:
15
+ * - error: terminal failure, batch → failed, audit-write failed
16
+ * - warn: transient retry, fallback / degradation, unknown classifier
17
+ * - info: lifecycle (unit start/end, batch transition, provider resolved)
18
+ * - debug: per-retry state, per-unit heartbeat
19
+ */ export function createScopedLogger(payload, context = {}) {
20
+ const baseContext = {
21
+ module: 'ai-translate',
22
+ ...context
23
+ };
24
+ function emit(level, eventName, fields) {
25
+ const logger = payload.logger;
26
+ if (!logger) return;
27
+ const fn = logger[level];
28
+ if (typeof fn !== 'function') return;
29
+ const merged = {
30
+ event: eventName,
31
+ ...baseContext
32
+ };
33
+ if (fields) {
34
+ for (const [k, v] of Object.entries(fields)){
35
+ if (k === 'err') {
36
+ const serialized = serializeErr(v);
37
+ if (serialized) merged.err = serialized;
38
+ } else if (v !== undefined) {
39
+ merged[k] = v;
40
+ }
41
+ }
42
+ }
43
+ try {
44
+ fn.call(logger, merged, eventName);
45
+ } catch {
46
+ // Logger itself failed; nothing recoverable.
47
+ }
48
+ }
49
+ return {
50
+ event (level, eventName, fields) {
51
+ emit(level, eventName, fields);
52
+ },
53
+ child (extraContext) {
54
+ return createScopedLogger(payload, {
55
+ ...baseContext,
56
+ ...extraContext
57
+ });
58
+ }
59
+ };
60
+ }
61
+ /**
62
+ * Recursively serialise an error-like value. Captures `name`, `message`,
63
+ * `stack`, `cause`, plus any extra enumerable own properties (HTTP-client
64
+ * errors typically expose `status`, `body`, `code`). Mirrors pino's
65
+ * default `err` serializer so Grafana queries on `err.cause.status` work.
66
+ */ export function serializeErr(err) {
67
+ if (err == null) return undefined;
68
+ if (err instanceof Error) {
69
+ const out = {
70
+ name: err.name,
71
+ message: err.message
72
+ };
73
+ if (err.stack) out.stack = err.stack;
74
+ for (const key of Object.keys(err)){
75
+ if (key === 'name' || key === 'message' || key === 'stack' || key === 'cause') {
76
+ continue;
77
+ }
78
+ out[key] = err[key];
79
+ }
80
+ const cause = err.cause;
81
+ if (cause != null) {
82
+ out.cause = serializeErr(cause);
83
+ }
84
+ return out;
85
+ }
86
+ if (typeof err === 'object') {
87
+ return {
88
+ name: 'NonError',
89
+ message: 'Non-Error thrown',
90
+ ...err
91
+ };
92
+ }
93
+ return {
94
+ name: 'NonError',
95
+ message: String(err)
96
+ };
97
+ }
@@ -0,0 +1,128 @@
1
+ import type { Payload, PayloadRequest } from 'payload';
2
+ /**
3
+ * SHA-1 of a stable JSON serialization of a value. Used to fingerprint
4
+ * field values for the `preserveManualEdits` feature so we can detect
5
+ * when a target locale row has drifted from the last plugin-written
6
+ * shape.
7
+ *
8
+ * Stable serialization: object keys sorted, undefined dropped. Same
9
+ * value → same hash across processes / machines.
10
+ */
11
+ export declare function hashFieldValue(value: unknown): string;
12
+ /**
13
+ * Fetch every (fieldPath → lastWrittenHash) row for one
14
+ * (collection, documentId, locale) tuple. Used by the writer before
15
+ * issuing the locale-update so it can filter out fields that have
16
+ * drifted from the stored hash.
17
+ */
18
+ export declare function loadFieldHashes(payload: Payload, metaCollection: string, collection: string, documentId: string | number, locale: string): Promise<Map<string, string>>;
19
+ /**
20
+ * Inspect the current target doc and decide which top-level fields in
21
+ * `writeData` would overwrite a manually edited value. Returns the set
22
+ * of field names to DROP from `writeData` before issuing the update.
23
+ *
24
+ * Detection: for each top-level field path that has a stored hash, if
25
+ * the current target value's hash differs from the stored one, the
26
+ * field has been edited since the plugin's last write — skip it.
27
+ */
28
+ export declare function pickManualEditedFields(writeData: Record<string, unknown>, targetDoc: Record<string, unknown> | null | undefined, storedHashes: Map<string, string>): {
29
+ fieldsToDrop: string[];
30
+ skipReasons: Map<string, string>;
31
+ };
32
+ /**
33
+ * Upsert per-field hashes after a successful locale write. One row per
34
+ * (collection, documentId, locale, fieldPath). Best-effort — failures
35
+ * here do NOT roll back the translation, they just mean the next write
36
+ * can't detect divergence for that field.
37
+ *
38
+ * `sourceValues` (BUG-21): map of `fieldPath → source value at time of
39
+ * write`. When provided, the upsert also stores `lastSourceHash` so the
40
+ * next translate can compare current-source-hash to stored-source-hash
41
+ * and skip the LLM call entirely if both source AND target are
42
+ * unchanged. Omitted when source values aren't available (rare).
43
+ */
44
+ export declare function recordFieldHashes(payload: Payload, metaCollection: string, collection: string, documentId: string | number, locale: string, writeData: Record<string, unknown>, sourceValues?: Record<string, unknown>,
45
+ /**
46
+ * Optional request context to thread into the inner find/update/create
47
+ * calls. When the caller wraps the locale-write in a fresh transaction,
48
+ * passing the same `req` here keeps the bookkeeping rows on the same
49
+ * connection lifecycle. Omit for ad-hoc / out-of-band invocations.
50
+ */
51
+ req?: PayloadRequest): Promise<{
52
+ written: number;
53
+ failed: number;
54
+ }>;
55
+ /**
56
+ * Fetch every `(fieldPath → { writtenHash, sourceHash })` row for one
57
+ * (collection, documentId, locale) tuple. Variant of `loadFieldHashes`
58
+ * that also returns the BUG-21 source hash so the LLM-skip check can
59
+ * compare current source content to what was translated last time.
60
+ *
61
+ * Returns an empty map on read failure (caller treats as "no prior data,
62
+ * proceed with full translate"). Rows pre-BUG-21 have `sourceHash:
63
+ * undefined` — those are treated as "can't safely skip; run translate."
64
+ */
65
+ export declare function loadFieldHashesWithSource(payload: Payload, metaCollection: string, collection: string, documentId: string | number, locale: string): Promise<Map<string, {
66
+ writtenHash: string;
67
+ sourceHash?: string;
68
+ }>>;
69
+ import type { FieldLocaleResult, TranslationUnit } from '../types.js';
70
+ /**
71
+ * Result of a hash-skip preflight.
72
+ * - `remainingUnits`: units the caller should still send to the LLM.
73
+ * - `hashSkipped`: synthetic FieldLocaleResults to add to the success
74
+ * bucket so the per-field event stream and counters reflect that
75
+ * these fields were processed (just not via the LLM).
76
+ */
77
+ export type HashSkipResult = {
78
+ remainingUnits: TranslationUnit[];
79
+ hashSkipped: FieldLocaleResult[];
80
+ };
81
+ /**
82
+ * Read stored source+target hashes for `(surfaceSlug, documentId,
83
+ * targetLocale)`, compare against current source + target, and remove
84
+ * units whose top-level field is unchanged on both sides. Returns the
85
+ * remaining units the caller still needs to translate plus synthetic
86
+ * `status: 'success'` results for the skipped units (characterCount = 0,
87
+ * durationMs = 0 — these are the "fieldsHashSkipped" rows in usage).
88
+ *
89
+ * Surface-agnostic: pass a `loadTargetDoc` closure so callers can supply
90
+ * `findByIdNoFallback(payload, collection, docId, locale)` for collections
91
+ * or `findGlobalNoFallback(payload, globalSlug, locale)` for globals.
92
+ *
93
+ * Wrapped in try/catch internally — best-effort optimization, never throws.
94
+ */
95
+ export declare function applyHashSkip(args: {
96
+ payload: Payload;
97
+ metaCollection: string;
98
+ surfaceSlug: string;
99
+ documentId: string | number;
100
+ targetLocale: string;
101
+ units: TranslationUnit[];
102
+ sourceDoc: Record<string, unknown>;
103
+ loadTargetDoc: () => Promise<Record<string, unknown> | null>;
104
+ }): Promise<HashSkipResult>;
105
+ /**
106
+ * Persist per-field source+target hashes after a successful locale
107
+ * write. Surface-agnostic — `surfaceSlug` is the collection slug for
108
+ * collection writes, or the global slug for global writes (matching the
109
+ * existing `bulk-translate` persist contract).
110
+ *
111
+ * Builds the `sourceValues` map from `sourceDoc` for exactly the fields
112
+ * in `writeData`, then defers to `recordFieldHashes`. Wraps the call so
113
+ * upstream errors are logged via the payload logger but never re-thrown
114
+ * — translation succeeded already, bookkeeping failure shouldn't surface
115
+ * to the editor.
116
+ */
117
+ export declare function recordHashesAfterWrite(args: {
118
+ payload: Payload;
119
+ metaCollection: string;
120
+ surfaceSlug: string;
121
+ documentId: string | number;
122
+ targetLocale: string;
123
+ writeData: Record<string, unknown>;
124
+ sourceDoc: Record<string, unknown>;
125
+ }): Promise<{
126
+ written: number;
127
+ failed: number;
128
+ }>;