@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,24 @@
1
+ import type { Access, CollectionConfig } from 'payload';
2
+ export declare const DEFAULT_TRANSLATION_DAILY_SPEND_COLLECTION_SLUG = "translation-daily-spend";
3
+ /**
4
+ * One row per (date, consumerKey) tracking aggregate USD spent on
5
+ * translation that day. Backs the daily-cap enforcement gate
6
+ * (Decision #14 + F-BIZ-08 + F-SEC-TOTP-BYPASS in
7
+ * Design-2026-05-27-bulk-translate.md).
8
+ *
9
+ * Rows are upserted atomically by the cap utility before each
10
+ * translation enqueue (bulk + per-doc retry + plugin coalesce paths
11
+ * per Decision #13 v2). Multi-process safe via SQL `INSERT ... ON
12
+ * CONFLICT DO UPDATE`, which is required because the totp + bulk
13
+ * endpoints fan out across Vercel function invocations that each
14
+ * have isolated in-memory state.
15
+ *
16
+ * `consumerKey` is the plugin's `options.usageTracking?.consumerKey
17
+ * ?? 'default'` and lets a shared-DB deployment with multiple
18
+ * plugin consumers (rare) keep separate caps per consumer. Default
19
+ * is `'default'` which works for single-consumer DBs.
20
+ *
21
+ * Read access is admin-only. Writes happen only via the cap
22
+ * utility (Payload `find` + raw SQL upsert via drizzle).
23
+ */
24
+ export declare function createTranslationDailySpendCollection(readAccess?: Access, slug?: string): CollectionConfig;
@@ -0,0 +1,133 @@
1
+ export const DEFAULT_TRANSLATION_DAILY_SPEND_COLLECTION_SLUG = 'translation-daily-spend';
2
+ /**
3
+ * One row per (date, consumerKey) tracking aggregate USD spent on
4
+ * translation that day. Backs the daily-cap enforcement gate
5
+ * (Decision #14 + F-BIZ-08 + F-SEC-TOTP-BYPASS in
6
+ * Design-2026-05-27-bulk-translate.md).
7
+ *
8
+ * Rows are upserted atomically by the cap utility before each
9
+ * translation enqueue (bulk + per-doc retry + plugin coalesce paths
10
+ * per Decision #13 v2). Multi-process safe via SQL `INSERT ... ON
11
+ * CONFLICT DO UPDATE`, which is required because the totp + bulk
12
+ * endpoints fan out across Vercel function invocations that each
13
+ * have isolated in-memory state.
14
+ *
15
+ * `consumerKey` is the plugin's `options.usageTracking?.consumerKey
16
+ * ?? 'default'` and lets a shared-DB deployment with multiple
17
+ * plugin consumers (rare) keep separate caps per consumer. Default
18
+ * is `'default'` which works for single-consumer DBs.
19
+ *
20
+ * Read access is admin-only. Writes happen only via the cap
21
+ * utility (Payload `find` + raw SQL upsert via drizzle).
22
+ */ export function createTranslationDailySpendCollection(readAccess, slug = DEFAULT_TRANSLATION_DAILY_SPEND_COLLECTION_SLUG) {
23
+ const isAdminDefault = ({ req })=>{
24
+ const user = req.user;
25
+ const roles = user?.roles;
26
+ if (Array.isArray(roles) && roles.includes('admin')) return true;
27
+ return false;
28
+ };
29
+ return {
30
+ slug,
31
+ // System rows are never edited in the admin document view, so
32
+ // Payload's document-locking buys nothing here — and its lock check
33
+ // costs a second pool connection inside every update transaction
34
+ // (core's checkDocumentLockStatus runs a find without `req`). Under
35
+ // concurrent updates (e.g. dismiss-all on alerts) that exhausted the
36
+ // pool and deadlocked a consumer in prod on 2026-06-10.
37
+ lockDocuments: false,
38
+ labels: {
39
+ singular: 'Translation Daily Spend',
40
+ plural: 'Translation Daily Spend Counters'
41
+ },
42
+ admin: {
43
+ group: 'Translation',
44
+ useAsTitle: 'date',
45
+ defaultColumns: [
46
+ 'date',
47
+ 'consumerKey',
48
+ 'spendUsd',
49
+ 'capUsd',
50
+ 'requestCount'
51
+ ],
52
+ hidden: ({ user })=>{
53
+ const roles = user?.roles;
54
+ return !Array.isArray(roles) || !roles.includes('admin');
55
+ },
56
+ description: 'Daily aggregate spend counter for the bulk + per-doc translation cost cap. One row per (UTC date, consumer). Updated atomically before each translation enqueue. Read-only in admin; the cap utility owns writes.'
57
+ },
58
+ access: {
59
+ read: readAccess ?? isAdminDefault,
60
+ create: ()=>false,
61
+ update: ()=>false,
62
+ delete: isAdminDefault
63
+ },
64
+ fields: [
65
+ // ISO date string `YYYY-MM-DD` keyed in UTC. Storing as text
66
+ // (not Date) keeps the equality check trivial for the upsert
67
+ // and avoids time-zone drift across function invocations.
68
+ {
69
+ name: 'date',
70
+ type: 'text',
71
+ required: true,
72
+ index: true,
73
+ admin: {
74
+ readOnly: true
75
+ }
76
+ },
77
+ {
78
+ name: 'consumerKey',
79
+ type: 'text',
80
+ required: true,
81
+ index: true,
82
+ defaultValue: 'default',
83
+ admin: {
84
+ readOnly: true
85
+ }
86
+ },
87
+ {
88
+ name: 'spendUsd',
89
+ type: 'number',
90
+ required: true,
91
+ defaultValue: 0,
92
+ admin: {
93
+ readOnly: true
94
+ }
95
+ },
96
+ {
97
+ name: 'capUsd',
98
+ type: 'number',
99
+ admin: {
100
+ readOnly: true,
101
+ description: 'Cap value snapshotted at row creation. If the env-var cap changes after a day has started, the snapshotted value still applies for that day so admins can predict whether further requests will be allowed.'
102
+ }
103
+ },
104
+ {
105
+ name: 'requestCount',
106
+ type: 'number',
107
+ defaultValue: 0,
108
+ admin: {
109
+ readOnly: true
110
+ }
111
+ },
112
+ {
113
+ name: 'lastRequestAt',
114
+ type: 'date',
115
+ admin: {
116
+ readOnly: true
117
+ }
118
+ }
119
+ ],
120
+ indexes: [
121
+ // Composite uniqueness for the atomic upsert. Hand-written
122
+ // migration also adds a Postgres UNIQUE constraint on
123
+ // (date, consumer_key) to enforce at the DB level.
124
+ {
125
+ fields: [
126
+ 'date',
127
+ 'consumerKey'
128
+ ],
129
+ unique: true
130
+ }
131
+ ]
132
+ };
133
+ }
@@ -0,0 +1,30 @@
1
+ import type { Access, CollectionConfig } from 'payload';
2
+ export declare const DEFAULT_TRANSLATION_RATE_LIMITS_COLLECTION_SLUG = "translation-rate-limits";
3
+ /**
4
+ * One row per (provider, model) tracking a Postgres-backed leaky
5
+ * token bucket against upstream LLM RPM caps. Backs the bulk +
6
+ * per-doc + coalesce translate paths per Risk R8 + architect §4 in
7
+ * Design-2026-05-27-bulk-translate.md.
8
+ *
9
+ * The legacy in-memory limiter (`lib/rate-limiter.ts`) is per-process
10
+ * and invisible across Vercel function invocations and server-driven
11
+ * job workers — multiple instances each get their own bucket, so the
12
+ * aggregate burst against the upstream provider can be N× the
13
+ * per-process cap. This collection moves the bucket into Postgres so
14
+ * every entry point shares the same state.
15
+ *
16
+ * Rows are lazily created on first `acquireToken` call for a given
17
+ * (provider, model). Defaults: capacity=60, refillRatePerSec=1.0
18
+ * (60 RPM), tunable per-row by admins post-creation. Env-var
19
+ * overrides (`TRANSLATION_RATE_LIMIT_CAPACITY`,
20
+ * `TRANSLATION_RATE_LIMIT_REFILL_PER_SEC`) seed the defaults at
21
+ * lazy-create time only — mid-life rate changes are admin-edits on
22
+ * the row itself.
23
+ *
24
+ * Read access is admin-only. Writes happen only via the token-bucket
25
+ * utility (Payload `find` + `update` / `create`). Mutability on the
26
+ * `tokens` / `lastRefillAt` fields is intentional — admins can edit
27
+ * `capacity` and `refillRatePerSec` to tune limits, but the bucket
28
+ * state itself is owned by the utility.
29
+ */
30
+ export declare function createTranslationRateLimitsCollection(readAccess?: Access, slug?: string): CollectionConfig;
@@ -0,0 +1,144 @@
1
+ export const DEFAULT_TRANSLATION_RATE_LIMITS_COLLECTION_SLUG = 'translation-rate-limits';
2
+ /**
3
+ * One row per (provider, model) tracking a Postgres-backed leaky
4
+ * token bucket against upstream LLM RPM caps. Backs the bulk +
5
+ * per-doc + coalesce translate paths per Risk R8 + architect §4 in
6
+ * Design-2026-05-27-bulk-translate.md.
7
+ *
8
+ * The legacy in-memory limiter (`lib/rate-limiter.ts`) is per-process
9
+ * and invisible across Vercel function invocations and server-driven
10
+ * job workers — multiple instances each get their own bucket, so the
11
+ * aggregate burst against the upstream provider can be N× the
12
+ * per-process cap. This collection moves the bucket into Postgres so
13
+ * every entry point shares the same state.
14
+ *
15
+ * Rows are lazily created on first `acquireToken` call for a given
16
+ * (provider, model). Defaults: capacity=60, refillRatePerSec=1.0
17
+ * (60 RPM), tunable per-row by admins post-creation. Env-var
18
+ * overrides (`TRANSLATION_RATE_LIMIT_CAPACITY`,
19
+ * `TRANSLATION_RATE_LIMIT_REFILL_PER_SEC`) seed the defaults at
20
+ * lazy-create time only — mid-life rate changes are admin-edits on
21
+ * the row itself.
22
+ *
23
+ * Read access is admin-only. Writes happen only via the token-bucket
24
+ * utility (Payload `find` + `update` / `create`). Mutability on the
25
+ * `tokens` / `lastRefillAt` fields is intentional — admins can edit
26
+ * `capacity` and `refillRatePerSec` to tune limits, but the bucket
27
+ * state itself is owned by the utility.
28
+ */ export function createTranslationRateLimitsCollection(readAccess, slug = DEFAULT_TRANSLATION_RATE_LIMITS_COLLECTION_SLUG) {
29
+ const isAdminDefault = ({ req })=>{
30
+ const user = req.user;
31
+ const roles = user?.roles;
32
+ if (Array.isArray(roles) && roles.includes('admin')) return true;
33
+ return false;
34
+ };
35
+ return {
36
+ slug,
37
+ // System rows are never edited in the admin document view, so
38
+ // Payload's document-locking buys nothing here — and its lock check
39
+ // costs a second pool connection inside every update transaction
40
+ // (core's checkDocumentLockStatus runs a find without `req`). Under
41
+ // concurrent updates (e.g. dismiss-all on alerts) that exhausted the
42
+ // pool and deadlocked a consumer in prod on 2026-06-10.
43
+ lockDocuments: false,
44
+ labels: {
45
+ singular: 'Translation Rate Limit',
46
+ plural: 'Translation Rate Limits'
47
+ },
48
+ admin: {
49
+ group: 'Translation',
50
+ useAsTitle: 'model',
51
+ defaultColumns: [
52
+ 'provider',
53
+ 'model',
54
+ 'tokens',
55
+ 'capacity',
56
+ 'refillRatePerSec',
57
+ 'lastRefillAt'
58
+ ],
59
+ hidden: ({ user })=>{
60
+ const roles = user?.roles;
61
+ return !Array.isArray(roles) || !roles.includes('admin');
62
+ },
63
+ description: 'Postgres-backed leaky token bucket per (provider, model). Throttles bulk + per-doc + coalesce translation paths against upstream LLM RPM caps. Rows are created lazily on first request; admins may tune capacity / refillRatePerSec to match provider plan limits.'
64
+ },
65
+ access: {
66
+ read: readAccess ?? isAdminDefault,
67
+ create: ()=>false,
68
+ update: ()=>false,
69
+ delete: isAdminDefault
70
+ },
71
+ fields: [
72
+ {
73
+ name: 'provider',
74
+ type: 'text',
75
+ required: true,
76
+ index: true,
77
+ admin: {
78
+ readOnly: true,
79
+ description: 'Provider key — e.g. "openrouter", "anthropic", "openai". Matches the plugin provider id.'
80
+ }
81
+ },
82
+ {
83
+ name: 'model',
84
+ type: 'text',
85
+ required: true,
86
+ index: true,
87
+ admin: {
88
+ readOnly: true,
89
+ description: 'Model id within the provider — e.g. "google/gemini-2.5-flash". The (provider, model) pair is the bucket key.'
90
+ }
91
+ },
92
+ {
93
+ name: 'tokens',
94
+ type: 'number',
95
+ required: true,
96
+ defaultValue: 0,
97
+ admin: {
98
+ readOnly: true,
99
+ description: 'Current remaining tokens in the bucket. Refilled on each acquire based on (now - lastRefillAt) * refillRatePerSec, capped at capacity. Owned by the token-bucket utility — do not hand-edit.'
100
+ }
101
+ },
102
+ {
103
+ name: 'capacity',
104
+ type: 'number',
105
+ required: true,
106
+ defaultValue: 60,
107
+ admin: {
108
+ description: 'Maximum bucket size. Tune to match the provider plan RPM cap (e.g. 60 for a 60 RPM plan, 600 for a 600 RPM plan). Admins may edit; the utility respects the current value.'
109
+ }
110
+ },
111
+ {
112
+ name: 'refillRatePerSec',
113
+ type: 'number',
114
+ required: true,
115
+ defaultValue: 1,
116
+ admin: {
117
+ description: 'Tokens added per second. For a 60 RPM cap use 1.0; for 600 RPM use 10.0. Admins may edit; the utility respects the current value.'
118
+ }
119
+ },
120
+ {
121
+ name: 'lastRefillAt',
122
+ type: 'date',
123
+ required: true,
124
+ admin: {
125
+ readOnly: true,
126
+ description: 'Timestamp of the last refill calculation. Used by the utility to compute elapsed time since the last acquire. Owned by the utility.'
127
+ }
128
+ }
129
+ ],
130
+ indexes: [
131
+ // Composite uniqueness for the (provider, model) bucket key.
132
+ // Hand-written migration should also add a Postgres UNIQUE
133
+ // constraint at the DB level to enforce concurrent-insert
134
+ // safety.
135
+ {
136
+ fields: [
137
+ 'provider',
138
+ 'model'
139
+ ],
140
+ unique: true
141
+ }
142
+ ]
143
+ };
144
+ }