@focus-reactive/payload-plugin-translator 0.7.2 → 0.8.1

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 (157) hide show
  1. package/README.md +18 -3
  2. package/dist/client/entities/translation/api/mutations/useDismissStaleness.d.ts +12 -0
  3. package/dist/client/entities/translation/api/mutations/useDismissStaleness.js +40 -0
  4. package/dist/client/entities/translation/api/mutations/useQueueDocumentTranslation.js +6 -0
  5. package/dist/client/entities/translation/api/mutations/useRunDocumentTranslation.js +6 -0
  6. package/dist/client/entities/translation/api/queries/useDocumentStaleness.d.ts +17 -0
  7. package/dist/client/entities/translation/api/queries/useDocumentStaleness.js +35 -0
  8. package/dist/client/entities/translation/api/queries/useDocumentTranslation.js +23 -1
  9. package/dist/client/entities/translation/index.d.ts +12 -5
  10. package/dist/client/entities/translation/index.js +10 -5
  11. package/dist/client/entities/translation/model/panelStatus.d.ts +60 -0
  12. package/dist/client/entities/translation/model/panelStatus.js +101 -0
  13. package/dist/client/entities/translation/model/statusRows.d.ts +42 -0
  14. package/dist/client/entities/translation/model/statusRows.js +79 -0
  15. package/dist/client/entities/translation/model/types.d.ts +11 -0
  16. package/dist/client/entities/translation/model/types.js +1 -1
  17. package/dist/client/entities/translation/ui/ActionButton/ActionButton.d.ts +14 -0
  18. package/dist/client/entities/translation/ui/ActionButton/ActionButton.js +28 -0
  19. package/dist/client/entities/translation/ui/ActionButton/index.d.ts +1 -0
  20. package/dist/client/entities/translation/ui/ActionButton/index.js +3 -0
  21. package/dist/client/entities/translation/ui/ActionButton/styles.module.scss +11 -0
  22. package/dist/client/entities/translation/ui/PanelStatusMarker/PanelStatusMarker.d.ts +12 -0
  23. package/dist/client/entities/translation/ui/PanelStatusMarker/PanelStatusMarker.js +26 -0
  24. package/dist/client/entities/translation/ui/PanelStatusMarker/index.d.ts +1 -0
  25. package/dist/client/entities/translation/ui/PanelStatusMarker/index.js +3 -0
  26. package/dist/client/entities/translation/ui/PanelStatusMarker/styles.module.scss +12 -0
  27. package/dist/client/entities/translation/ui/TranslationDirection/styles.module.scss +2 -2
  28. package/dist/client/entities/translation/ui/TranslationStatusList/StatusBadge.d.ts +10 -0
  29. package/dist/client/entities/translation/ui/TranslationStatusList/StatusBadge.js +27 -0
  30. package/dist/client/entities/translation/ui/TranslationStatusList/TranslationStatusList.d.ts +14 -0
  31. package/dist/client/entities/translation/ui/TranslationStatusList/TranslationStatusList.js +194 -0
  32. package/dist/client/entities/translation/ui/TranslationStatusList/index.d.ts +1 -0
  33. package/dist/client/entities/translation/ui/TranslationStatusList/index.js +3 -0
  34. package/dist/client/entities/translation/ui/TranslationStatusList/styles.module.scss +72 -0
  35. package/dist/client/features/collection-translation-form/ui/CollectionTranslationForm.js +17 -21
  36. package/dist/client/features/collection-translation-form/ui/styles.module.scss +14 -14
  37. package/dist/client/features/collection-translation-popup/ui/CollectionTranslationPopup.d.ts +4 -2
  38. package/dist/client/features/collection-translation-popup/ui/CollectionTranslationPopup.js +9 -9
  39. package/dist/client/features/collection-translation-popup/ui/styles.module.scss +5 -3
  40. package/dist/client/features/collection-translation-progress/ui/CollectionTranslationProgress.d.ts +5 -1
  41. package/dist/client/features/collection-translation-progress/ui/CollectionTranslationProgress.js +100 -93
  42. package/dist/client/features/collection-translation-progress/ui/styles.module.scss +52 -3
  43. package/dist/client/features/open-document-translation-popup/ui/OpenDocumentTranslationPopup.d.ts +4 -1
  44. package/dist/client/features/open-document-translation-popup/ui/OpenDocumentTranslationPopup.js +16 -5
  45. package/dist/client/features/open-document-translation-popup/ui/styles.module.scss +5 -3
  46. package/dist/client/features/translate-document-form/ui/DocumentTranslationForm.js +5 -2
  47. package/dist/client/features/translate-document-form/ui/styles.module.scss +8 -2
  48. package/dist/client/shared/lib/assets/icons/AlertTriangleIcon.d.ts +1 -0
  49. package/dist/client/shared/lib/assets/icons/AlertTriangleIcon.js +14 -0
  50. package/dist/client/shared/lib/assets/icons/CheckIcon.d.ts +1 -0
  51. package/dist/client/shared/lib/assets/icons/CheckIcon.js +17 -0
  52. package/dist/client/shared/lib/assets/icons/CloseIcon.d.ts +1 -0
  53. package/dist/client/shared/lib/assets/icons/CloseIcon.js +16 -0
  54. package/dist/client/shared/ui/Button/Button.js +2 -1
  55. package/dist/client/shared/ui/ColorIndicator/ColorIndicator.d.ts +1 -1
  56. package/dist/client/shared/ui/ColorIndicator/styles.module.scss +4 -0
  57. package/dist/client/shared/ui/Label/Label.d.ts +2 -0
  58. package/dist/client/shared/ui/Label/Label.js +3 -2
  59. package/dist/client/shared/ui/Label/styles.module.scss +11 -0
  60. package/dist/client/shared/ui/Loading/Loading.d.ts +6 -2
  61. package/dist/client/shared/ui/Loading/Loading.js +4 -2
  62. package/dist/client/shared/ui/Loading/barCount.d.ts +3 -0
  63. package/dist/client/shared/ui/Loading/barCount.js +11 -0
  64. package/dist/client/shared/ui/Loading/styles.module.scss +20 -27
  65. package/dist/client/shared/ui/Popup/Popup.js +3 -0
  66. package/dist/client/shared/ui/Select/Select.js +1 -1
  67. package/dist/client/shared/ui/form/FormSelect/FormSelect.js +1 -0
  68. package/dist/client/widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.js +37 -17
  69. package/dist/client/widgets/bulk-translation-dashboard/ui/styles.module.scss +27 -0
  70. package/dist/client/widgets/translate-document/ui/TranslateDocument.js +53 -27
  71. package/dist/client/widgets/translate-document/ui/styles.module.scss +27 -0
  72. package/dist/composition/levels/useDocTranslationApi.d.ts +3 -2
  73. package/dist/composition/levels/useDocTranslationApi.js +5 -3
  74. package/dist/core/provenance/ProvenanceStore.interface.d.ts +18 -0
  75. package/dist/core/provenance/index.d.ts +1 -0
  76. package/dist/core/provenance/index.js +1 -1
  77. package/dist/core/provenance/staleness.d.ts +19 -0
  78. package/dist/core/provenance/staleness.js +21 -0
  79. package/dist/plugin.js +22 -74
  80. package/dist/server/features/createTranslationRoutes.d.ts +8 -9
  81. package/dist/server/features/createTranslationRoutes.js +14 -5
  82. package/dist/server/features/get-document-status/model.js +7 -2
  83. package/dist/server/features/staleness/dismissStaleness.handler.d.ts +8 -0
  84. package/dist/server/features/staleness/dismissStaleness.handler.js +31 -0
  85. package/dist/server/features/staleness/getDocumentStaleness.handler.d.ts +12 -0
  86. package/dist/server/features/staleness/getDocumentStaleness.handler.js +43 -0
  87. package/dist/server/features/staleness/index.d.ts +2 -0
  88. package/dist/server/features/staleness/index.js +3 -0
  89. package/dist/server/features/staleness/model.d.ts +40 -0
  90. package/dist/server/features/staleness/model.js +17 -0
  91. package/dist/server/features/staleness/route.d.ts +7 -0
  92. package/dist/server/features/staleness/route.js +21 -0
  93. package/dist/server/features/translate-document/handler.d.ts +6 -8
  94. package/dist/server/features/translate-document/handler.js +21 -33
  95. package/dist/server/features/translate-document/index.d.ts +1 -0
  96. package/dist/server/features/translate-document/index.js +1 -0
  97. package/dist/server/features/translate-document/wireTranslateRunner.d.ts +28 -0
  98. package/dist/server/features/translate-document/wireTranslateRunner.js +49 -0
  99. package/dist/server/modules/provenance/{provenanceCollection.d.ts → Provenance.collection.d.ts} +17 -9
  100. package/dist/server/modules/provenance/{provenanceCollection.js → Provenance.collection.js} +23 -9
  101. package/dist/server/modules/provenance/Provenance.service.d.ts +56 -0
  102. package/dist/server/modules/provenance/Provenance.service.js +124 -0
  103. package/dist/server/modules/provenance/Provenance.shapes.d.ts +26 -0
  104. package/dist/server/modules/provenance/Provenance.shapes.js +7 -0
  105. package/dist/server/modules/provenance/{PayloadProvenanceStore.d.ts → Provenance.store.d.ts} +2 -0
  106. package/dist/server/modules/provenance/{PayloadProvenanceStore.js → Provenance.store.js} +38 -15
  107. package/dist/server/modules/provenance/Provenance.wiring.d.ts +23 -0
  108. package/dist/server/modules/provenance/Provenance.wiring.js +43 -0
  109. package/dist/server/modules/provenance/{provenanceCleanupHook.d.ts → ProvenanceCleanup.hook.d.ts} +4 -3
  110. package/dist/server/modules/provenance/{provenanceCleanupHook.js → ProvenanceCleanup.hook.js} +1 -1
  111. package/dist/server/modules/provenance/index.d.ts +8 -4
  112. package/dist/server/modules/provenance/index.js +6 -4
  113. package/dist/server/modules/translation-levels/PluginConfigBuilder.d.ts +6 -11
  114. package/dist/server/modules/translation-levels/PluginConfigBuilder.js +2 -0
  115. package/dist/server/modules/translation-levels/index.d.ts +1 -1
  116. package/dist/server/modules/translation-levels/types.d.ts +20 -9
  117. package/dist/server/shared/http/index.d.ts +1 -0
  118. package/dist/server/shared/http/index.js +1 -0
  119. package/dist/server/shared/http/toClientErrorMessage.d.ts +15 -0
  120. package/dist/server/shared/http/toClientErrorMessage.js +26 -0
  121. package/dist/server/shared/index.d.ts +1 -1
  122. package/dist/server/shared/index.js +1 -1
  123. package/dist/server/shared/payload/sourceDocument.d.ts +8 -0
  124. package/dist/server/shared/payload/sourceDocument.js +15 -0
  125. package/dist/types/ConfigModifier.d.ts +10 -0
  126. package/dist/types/ConfigModifier.js +10 -0
  127. package/package.json +1 -1
  128. package/dist/client/entities/translation/ui/CompletedTranslationStatus/CompletedTranslationStatus.d.ts +0 -7
  129. package/dist/client/entities/translation/ui/CompletedTranslationStatus/CompletedTranslationStatus.js +0 -45
  130. package/dist/client/entities/translation/ui/CompletedTranslationStatus/index.d.ts +0 -1
  131. package/dist/client/entities/translation/ui/CompletedTranslationStatus/index.js +0 -3
  132. package/dist/client/entities/translation/ui/CompletedTranslationStatus/styles.module.scss +0 -3
  133. package/dist/client/entities/translation/ui/FailedTranslationStatus/FailedTranslationStatus.d.ts +0 -9
  134. package/dist/client/entities/translation/ui/FailedTranslationStatus/FailedTranslationStatus.js +0 -54
  135. package/dist/client/entities/translation/ui/FailedTranslationStatus/index.d.ts +0 -1
  136. package/dist/client/entities/translation/ui/FailedTranslationStatus/index.js +0 -3
  137. package/dist/client/entities/translation/ui/FailedTranslationStatus/styles.module.scss +0 -3
  138. package/dist/client/entities/translation/ui/PendingTranslationStatus/PendingTranslationStatus.d.ts +0 -11
  139. package/dist/client/entities/translation/ui/PendingTranslationStatus/PendingTranslationStatus.js +0 -83
  140. package/dist/client/entities/translation/ui/PendingTranslationStatus/index.d.ts +0 -1
  141. package/dist/client/entities/translation/ui/PendingTranslationStatus/index.js +0 -3
  142. package/dist/client/entities/translation/ui/RunningTranslationStatus/RunningTranslationStatus.d.ts +0 -10
  143. package/dist/client/entities/translation/ui/RunningTranslationStatus/RunningTranslationStatus.js +0 -66
  144. package/dist/client/entities/translation/ui/RunningTranslationStatus/index.d.ts +0 -1
  145. package/dist/client/entities/translation/ui/RunningTranslationStatus/index.js +0 -3
  146. package/dist/client/features/document-translation-progress-failed/index.d.ts +0 -1
  147. package/dist/client/features/document-translation-progress-failed/index.js +0 -3
  148. package/dist/client/features/document-translation-progress-failed/ui/DocumentTranslationProgressFailed.d.ts +0 -6
  149. package/dist/client/features/document-translation-progress-failed/ui/DocumentTranslationProgressFailed.js +0 -28
  150. package/dist/client/features/document-translation-progress-pending/index.d.ts +0 -1
  151. package/dist/client/features/document-translation-progress-pending/index.js +0 -3
  152. package/dist/client/features/document-translation-progress-pending/ui/DocumentTranslationProgressPending.d.ts +0 -6
  153. package/dist/client/features/document-translation-progress-pending/ui/DocumentTranslationProgressPending.js +0 -44
  154. package/dist/client/features/document-translation-progress-running/index.d.ts +0 -1
  155. package/dist/client/features/document-translation-progress-running/index.js +0 -3
  156. package/dist/client/features/document-translation-progress-running/ui/DocumentTranslationProgressRunning.d.ts +0 -6
  157. package/dist/client/features/document-translation-progress-running/ui/DocumentTranslationProgressRunning.js +0 -28
@@ -0,0 +1,124 @@
1
+ import { computeSourceFingerprint } from "../../../core/content-projection/computeSourceFingerprint";
2
+ import { isRecordStale } from "../../../core/provenance";
3
+ import { fetchSourceDocument } from "../../shared/payload/sourceDocument";
4
+ /**
5
+ * The single owner of provenance fingerprint policy — how the source is hashed on write, re-hashed on
6
+ * read, and compared for staleness. The write path and the read path go through this one class, so
7
+ * they can never drift (the biggest correctness trap in staleness detection). Sits above the CRUD
8
+ * {@link ProvenanceStore} port; the port + `computeSourceFingerprint` + `isRecordStale` stay
9
+ * framework-agnostic in the core.
10
+ *
11
+ * Best-effort by contract: fingerprint/record failures log and no-op rather than failing a translation.
12
+ */ export class ProvenanceService {
13
+ payload;
14
+ store;
15
+ schemaMap;
16
+ constructor(payload, store, schemaMap){
17
+ this.payload = payload;
18
+ this.store = store;
19
+ this.schemaMap = schemaMap;
20
+ }
21
+ /**
22
+ * Hash the PRISTINE source. The caller MUST pass source fetched **before** the translation pipeline
23
+ * runs — the pipeline mutates object-valued leaves (e.g. richText nodes) in place, so hashing after
24
+ * it would capture the target translation and make every fresh translation look instantly stale.
25
+ * Returns `null` on any failure (no schema, hashing error) so provenance is skipped, not the translation.
26
+ */ captureFingerprint(collection, sourceData) {
27
+ const schema = this.schemaMap.get(collection);
28
+ if (!schema) return null;
29
+ try {
30
+ return computeSourceFingerprint(sourceData, schema);
31
+ } catch (error) {
32
+ this.payload.logger.error({
33
+ err: error,
34
+ collection,
35
+ msg: "translator: failed to fingerprint source for provenance"
36
+ });
37
+ return null;
38
+ }
39
+ }
40
+ /** Persist a translation receipt (best-effort; a store failure logs and no-ops). */ async record(key, sourceFingerprint) {
41
+ try {
42
+ await this.store.upsert({
43
+ collectionSlug: key.collectionSlug,
44
+ documentId: key.documentId,
45
+ targetLocale: key.targetLocale,
46
+ sourceLocale: key.sourceLocale,
47
+ sourceFingerprint,
48
+ translatedAt: new Date().toISOString(),
49
+ dismissedFingerprint: null
50
+ });
51
+ } catch (error) {
52
+ this.payload.logger.error({
53
+ err: error,
54
+ collection: key.collectionSlug,
55
+ documentId: key.documentId,
56
+ targetLocale: key.targetLocale,
57
+ sourceLocale: key.sourceLocale,
58
+ msg: "translator: failed to record translation provenance"
59
+ });
60
+ }
61
+ }
62
+ /**
63
+ * Per-locale staleness for one document: read every receipt, recompute the current source
64
+ * fingerprint (write-path-identical), and mark each locale stale on undismissed drift. Returns `[]`
65
+ * when the collection has no schema. Isolates per-locale failures so one bad record can't blank the rest.
66
+ */ async getStaleness(collection, documentId) {
67
+ const schema = this.schemaMap.get(collection);
68
+ if (!schema) return [];
69
+ const records = await this.store.findByDocument(collection, documentId);
70
+ if (records.length === 0) return [];
71
+ const currentFingerprint = this.makeCurrentFingerprint(collection, documentId, schema);
72
+ const locales = [];
73
+ for (const record of records){
74
+ try {
75
+ const current = await currentFingerprint(record.sourceLocale);
76
+ locales.push({
77
+ target_lng: record.targetLocale,
78
+ source_lng: record.sourceLocale,
79
+ is_stale: isRecordStale(record, current),
80
+ translated_at: record.translatedAt
81
+ });
82
+ } catch (error) {
83
+ this.payload.logger.error({
84
+ err: error,
85
+ collection,
86
+ documentId,
87
+ targetLocale: record.targetLocale,
88
+ sourceLocale: record.sourceLocale,
89
+ msg: "translator: failed to compute staleness for locale"
90
+ });
91
+ }
92
+ }
93
+ return locales;
94
+ }
95
+ /**
96
+ * Acknowledge the current source drift for one target locale: persist the current fingerprint as the
97
+ * dismissed one, so the indicator hides until the source changes again. No-op when the collection has
98
+ * no schema or the locale has no record.
99
+ */ async dismiss(key) {
100
+ const schema = this.schemaMap.get(key.collectionSlug);
101
+ if (!schema) return;
102
+ const record = await this.store.find(key);
103
+ if (!record) return;
104
+ const currentFingerprint = this.makeCurrentFingerprint(key.collectionSlug, key.documentId, schema);
105
+ await this.store.dismiss(key, await currentFingerprint(record.sourceLocale));
106
+ }
107
+ /**
108
+ * Recompute the current source fingerprint the same way the write path does (shared fetch shape +
109
+ * hash). Cached per source locale so a document translated from one source into N locales fetches
110
+ * the source once.
111
+ */ makeCurrentFingerprint(collection, documentId, schema) {
112
+ const cache = new Map();
113
+ return async (sourceLocale)=>{
114
+ const cached = cache.get(sourceLocale);
115
+ if (cached !== undefined) return cached;
116
+ const sourceData = await fetchSourceDocument(this.payload, collection, documentId, sourceLocale);
117
+ const fingerprint = computeSourceFingerprint(sourceData, schema);
118
+ cache.set(sourceLocale, fingerprint);
119
+ return fingerprint;
120
+ };
121
+ }
122
+ }
123
+
124
+ //# sourceMappingURL=Provenance.service.js.map
@@ -0,0 +1,26 @@
1
+ import type { CollectionAfterDeleteHook } from "payload";
2
+ /**
3
+ * The minimal slice of a Payload collection that provenance's config-time wiring reads and mutates:
4
+ * its `slug`, the sidecar `custom` marker, and the `afterDelete` hook slot. A real `CollectionConfig`
5
+ * is **structurally assignable** to this — call sites pass the live collection with no adapter, and a
6
+ * test passes a plain `{ slug: "posts" }` literal. Keeps `injectProvenanceCleanup` /
7
+ * `ensureProvenanceCollectionRegistered` off the god-`Config`/`CollectionConfig` types.
8
+ *
9
+ * The only Payload type imported here is `CollectionAfterDeleteHook` — a framework callback contract
10
+ * that legitimately stays framework-typed.
11
+ */
12
+ export type ManagedCollectionEntry = {
13
+ slug: string;
14
+ custom?: unknown;
15
+ hooks?: {
16
+ afterDelete?: CollectionAfterDeleteHook[];
17
+ };
18
+ };
19
+ /**
20
+ * The minimal config host provenance's config-time wiring touches: just a mutable `collections`
21
+ * array. A real Payload `Config` plugs straight in (its `collections?: CollectionConfig[]` satisfies
22
+ * `ManagedCollectionEntry[]`).
23
+ */
24
+ export type ManagedCollectionsConfig = {
25
+ collections?: ManagedCollectionEntry[];
26
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The minimal config host provenance's config-time wiring touches: just a mutable `collections`
3
+ * array. A real Payload `Config` plugs straight in (its `collections?: CollectionConfig[]` satisfies
4
+ * `ManagedCollectionEntry[]`).
5
+ */ export { };
6
+
7
+ //# sourceMappingURL=Provenance.shapes.js.map
@@ -16,6 +16,8 @@ export declare class PayloadProvenanceStore implements ProvenanceStore {
16
16
  constructor(payload: Payload, slug: string);
17
17
  upsert(record: TranslationProvenanceRecord): Promise<void>;
18
18
  find(key: ProvenanceKey): Promise<TranslationProvenanceRecord | null>;
19
+ findByDocument(collectionSlug: string, documentId: string): Promise<TranslationProvenanceRecord[]>;
20
+ dismiss(key: ProvenanceKey, dismissedFingerprint: string): Promise<void>;
19
21
  deleteByDocument(collectionSlug: string, documentId: string): Promise<void>;
20
22
  private findDoc;
21
23
  }
@@ -1,3 +1,19 @@
1
+ function documentWhere(collectionSlug, documentId) {
2
+ return {
3
+ and: [
4
+ {
5
+ collectionSlug: {
6
+ equals: collectionSlug
7
+ }
8
+ },
9
+ {
10
+ documentId: {
11
+ equals: documentId
12
+ }
13
+ }
14
+ ]
15
+ };
16
+ }
1
17
  function keyWhere(key) {
2
18
  return {
3
19
  and: [
@@ -75,23 +91,30 @@ function toRecord(doc) {
75
91
  const doc = await this.findDoc(key);
76
92
  return doc === null ? null : toRecord(doc);
77
93
  }
94
+ async findByDocument(collectionSlug, documentId) {
95
+ const result = await this.payload.find({
96
+ collection: this.collection,
97
+ where: documentWhere(collectionSlug, documentId),
98
+ depth: 0,
99
+ pagination: false
100
+ });
101
+ return result.docs.map(toRecord);
102
+ }
103
+ async dismiss(key, dismissedFingerprint) {
104
+ const existing = await this.findDoc(key);
105
+ if (existing === null) return;
106
+ await this.payload.update({
107
+ collection: this.collection,
108
+ id: existing.id,
109
+ data: {
110
+ dismissedFingerprint
111
+ }
112
+ });
113
+ }
78
114
  async deleteByDocument(collectionSlug, documentId) {
79
115
  await this.payload.delete({
80
116
  collection: this.collection,
81
- where: {
82
- and: [
83
- {
84
- collectionSlug: {
85
- equals: collectionSlug
86
- }
87
- },
88
- {
89
- documentId: {
90
- equals: documentId
91
- }
92
- }
93
- ]
94
- }
117
+ where: documentWhere(collectionSlug, documentId)
95
118
  });
96
119
  }
97
120
  async findDoc(key) {
@@ -106,4 +129,4 @@ function toRecord(doc) {
106
129
  }
107
130
  }
108
131
 
109
- //# sourceMappingURL=PayloadProvenanceStore.js.map
132
+ //# sourceMappingURL=Provenance.store.js.map
@@ -0,0 +1,23 @@
1
+ import type { CollectionSchemaMap } from "../../../types/CollectionSchemaMap";
2
+ import type { ConfigModifier } from "../../../types/ConfigModifier";
3
+ import type { ProvenanceServiceFactory } from "./Provenance.service";
4
+ /** The opt-in `provenance` plugin option (kept local so this module doesn't depend on plugin.ts). */
5
+ export type ProvenanceOption = boolean | {
6
+ slug?: string;
7
+ } | undefined;
8
+ /**
9
+ * Everything the provenance module contributes at config time, in one object (mirrors
10
+ * `TaskRunnerProvider.configure`): the request-scoped {@link ProvenanceService} factory used by the
11
+ * handlers/routes, and the single {@link ConfigModifier} that registers the sidecar collection and
12
+ * the cleanup hook. When provenance is disabled, `serviceFactory` is absent and `configure` is a no-op.
13
+ */
14
+ export type ProvenanceModule = {
15
+ serviceFactory?: ProvenanceServiceFactory;
16
+ configure(managedSlugs: Set<string>): ConfigModifier;
17
+ };
18
+ /**
19
+ * Turn the opt-in `provenance` option into a self-contained {@link ProvenanceModule}. This is the one
20
+ * place provenance's config-time wiring lives — `plugin.ts` only calls `configureProvenance(...)` and
21
+ * registers the returned modifier through the shared builder (no raw `config.collections` mutation).
22
+ */
23
+ export declare function configureProvenance(option: ProvenanceOption, schemaMap: CollectionSchemaMap): ProvenanceModule;
@@ -0,0 +1,43 @@
1
+ import { ProvenanceService } from "./Provenance.service";
2
+ import { PayloadProvenanceStore } from "./Provenance.store";
3
+ import { DEFAULT_PROVENANCE_SLUG, ensureProvenanceCollectionRegistered, isProvenanceCollection } from "./Provenance.collection";
4
+ import { injectProvenanceCleanup } from "./ProvenanceCleanup.hook";
5
+ import { assertProvenanceSlugFree } from "./slugGuard";
6
+ /**
7
+ * Resolve the opt-in `provenance` config to a sidecar slug, or `null` when disabled.
8
+ * `false`/omitted → off; `true` or `{}` → on with the default slug; `{ slug }` → on with that slug.
9
+ */ function resolveProvenanceSlug(option) {
10
+ if (!option) return null;
11
+ if (option === true) return DEFAULT_PROVENANCE_SLUG;
12
+ // `||` (not `??`) so an empty/blank slug falls back to the default instead of silently disabling.
13
+ return option.slug || DEFAULT_PROVENANCE_SLUG;
14
+ }
15
+ const NOOP = (config)=>config;
16
+ /**
17
+ * Turn the opt-in `provenance` option into a self-contained {@link ProvenanceModule}. This is the one
18
+ * place provenance's config-time wiring lives — `plugin.ts` only calls `configureProvenance(...)` and
19
+ * registers the returned modifier through the shared builder (no raw `config.collections` mutation).
20
+ */ export function configureProvenance(option, schemaMap) {
21
+ const slug = resolveProvenanceSlug(option);
22
+ if (!slug) return {
23
+ configure: ()=>NOOP
24
+ };
25
+ const storeFactory = (payload)=>new PayloadProvenanceStore(payload, slug);
26
+ const serviceFactory = (payload)=>new ProvenanceService(payload, storeFactory(payload), schemaMap);
27
+ const configure = (managedSlugs)=>(config)=>{
28
+ // `config` infers as Payload's `Config` from the ConfigModifier return type, so this leaf never
29
+ // names the god-type — it only reads/mutates through narrow helpers below.
30
+ // Fail fast on a slug collision with a consumer collection (ignoring our own sidecar on a repeat
31
+ // init, so an idempotent re-run doesn't false-positive).
32
+ assertProvenanceSlugFree(slug, (config.collections ?? []).filter((collection)=>!isProvenanceCollection(collection)));
33
+ ensureProvenanceCollectionRegistered(config, slug);
34
+ injectProvenanceCleanup(config, managedSlugs, storeFactory, slug);
35
+ return config;
36
+ };
37
+ return {
38
+ serviceFactory,
39
+ configure
40
+ };
41
+ }
42
+
43
+ //# sourceMappingURL=Provenance.wiring.js.map
@@ -1,5 +1,6 @@
1
- import type { CollectionAfterDeleteHook, Config } from "payload";
2
- import type { ProvenanceStoreFactory } from "./PayloadProvenanceStore";
1
+ import type { CollectionAfterDeleteHook } from "payload";
2
+ import type { ManagedCollectionsConfig } from "./Provenance.shapes";
3
+ import type { ProvenanceStoreFactory } from "./Provenance.store";
3
4
  /**
4
5
  * Build the `afterDelete` hook that cascade-deletes a document's provenance rows for `provenanceSlug`.
5
6
  *
@@ -15,4 +16,4 @@ export declare function makeProvenanceCleanupHook(storeFactory: ProvenanceStoreF
15
16
  * yet a second instance with a different slug still attaches its own hook. The sidecar collection is
16
17
  * never in `managedSlugs`, so it is never hooked (no recursion).
17
18
  */
18
- export declare function injectProvenanceCleanup(config: Config, managedSlugs: Set<string>, storeFactory: ProvenanceStoreFactory, provenanceSlug: string): void;
19
+ export declare function injectProvenanceCleanup(config: ManagedCollectionsConfig, managedSlugs: Set<string>, storeFactory: ProvenanceStoreFactory, provenanceSlug: string): void;
@@ -38,4 +38,4 @@
38
38
  }
39
39
  }
40
40
 
41
- //# sourceMappingURL=provenanceCleanupHook.js.map
41
+ //# sourceMappingURL=ProvenanceCleanup.hook.js.map
@@ -1,5 +1,9 @@
1
- export { DEFAULT_PROVENANCE_SLUG, isProvenanceCollection, makeProvenanceCollection, } from "./provenanceCollection";
2
- export { PayloadProvenanceStore } from "./PayloadProvenanceStore";
3
- export type { ProvenanceStoreFactory } from "./PayloadProvenanceStore";
4
- export { injectProvenanceCleanup, makeProvenanceCleanupHook } from "./provenanceCleanupHook";
1
+ export { DEFAULT_PROVENANCE_SLUG, isProvenanceCollection, makeProvenanceCollection, } from "./Provenance.collection";
2
+ export { PayloadProvenanceStore } from "./Provenance.store";
3
+ export type { ProvenanceStoreFactory } from "./Provenance.store";
4
+ export { injectProvenanceCleanup, makeProvenanceCleanupHook } from "./ProvenanceCleanup.hook";
5
5
  export { assertProvenanceSlugFree } from "./slugGuard";
6
+ export { ProvenanceService } from "./Provenance.service";
7
+ export type { ProvenanceServiceFactory, StalenessLocale } from "./Provenance.service";
8
+ export { configureProvenance } from "./Provenance.wiring";
9
+ export type { ProvenanceModule, ProvenanceOption } from "./Provenance.wiring";
@@ -1,8 +1,10 @@
1
1
  // Provenance adapter (Payload-backed). The framework-agnostic port + record types live in the core
2
- // (src/core/provenance); this module is the plugin-side implementation.
3
- export { DEFAULT_PROVENANCE_SLUG, isProvenanceCollection, makeProvenanceCollection } from "./provenanceCollection";
4
- export { PayloadProvenanceStore } from "./PayloadProvenanceStore";
5
- export { injectProvenanceCleanup, makeProvenanceCleanupHook } from "./provenanceCleanupHook";
2
+ // (src/core/provenance); this module is the plugin-side implementation + its config-time wiring.
3
+ export { DEFAULT_PROVENANCE_SLUG, isProvenanceCollection, makeProvenanceCollection } from "./Provenance.collection";
4
+ export { PayloadProvenanceStore } from "./Provenance.store";
5
+ export { injectProvenanceCleanup, makeProvenanceCleanupHook } from "./ProvenanceCleanup.hook";
6
6
  export { assertProvenanceSlugFree } from "./slugGuard";
7
+ export { ProvenanceService } from "./Provenance.service";
8
+ export { configureProvenance } from "./Provenance.wiring";
7
9
 
8
10
  //# sourceMappingURL=index.js.map
@@ -2,18 +2,13 @@ import type { CollectionConfig, Config, Endpoint } from "payload";
2
2
  import type { AccessGuard } from "../../../types/AccessGuard";
3
3
  import type { RawPayloadComponentExport } from "../../../types/PayloadComponentExport";
4
4
  import type { CollectionSchemaMap } from "../../../types/CollectionSchemaMap";
5
+ import type { ConfigModifier } from "../../../types/ConfigModifier";
5
6
  import type { TranslationProvider } from "../../../core/translation-providers";
6
7
  import type { TaskRunnerFactory } from "../task-runner";
7
- import type { CollectionAdminSlot, LevelContext } from "./types";
8
- type ConfigModifier = (config: Config) => Config;
9
- export type PluginConfigBuilderDeps = {
10
- collections: CollectionConfig[];
11
- basePath: string;
12
- access?: AccessGuard;
13
- taskRunnerFactory: TaskRunnerFactory;
14
- schemaMap: CollectionSchemaMap;
15
- translationProvider: TranslationProvider;
16
- };
8
+ import type { ProvenanceServiceFactory } from "../provenance";
9
+ import type { CollectionAdminSlot, LevelContext, TranslationContext } from "./types";
10
+ /** The builder's construction deps are exactly the shared {@link TranslationContext}. */
11
+ export type PluginConfigBuilderDeps = TranslationContext;
17
12
  /**
18
13
  * The single place that mutates the Payload `config`. Levels (through the narrow
19
14
  * {@link LevelContext}) and the plugin (through `addAdminProvider` /
@@ -32,6 +27,7 @@ export declare class PluginConfigBuilder implements LevelContext {
32
27
  readonly taskRunnerFactory: TaskRunnerFactory;
33
28
  readonly schemaMap: CollectionSchemaMap;
34
29
  readonly translationProvider: TranslationProvider;
30
+ readonly provenanceServiceFactory?: ProvenanceServiceFactory;
35
31
  private readonly endpoints;
36
32
  private readonly collectionComponents;
37
33
  private readonly adminProviders;
@@ -49,4 +45,3 @@ export declare class PluginConfigBuilder implements LevelContext {
49
45
  private attachCollectionComponents;
50
46
  private registerEndpoints;
51
47
  }
52
- export {};
@@ -34,6 +34,7 @@ function attachToSlot(collection, slot, component) {
34
34
  taskRunnerFactory;
35
35
  schemaMap;
36
36
  translationProvider;
37
+ provenanceServiceFactory;
37
38
  endpoints = [];
38
39
  collectionComponents = [];
39
40
  adminProviders = [];
@@ -45,6 +46,7 @@ function attachToSlot(collection, slot, component) {
45
46
  this.taskRunnerFactory = deps.taskRunnerFactory;
46
47
  this.schemaMap = deps.schemaMap;
47
48
  this.translationProvider = deps.translationProvider;
49
+ this.provenanceServiceFactory = deps.provenanceServiceFactory;
48
50
  }
49
51
  addEndpoints(endpoints) {
50
52
  this.endpoints.push(...endpoints);
@@ -1 +1 @@
1
- export type { TranslationLevel, LevelContext, CollectionAdminSlot } from "./types";
1
+ export type { TranslationLevel, LevelContext, TranslationContext, CollectionAdminSlot, } from "./types";
@@ -4,7 +4,26 @@ import type { RawPayloadComponentExport } from "../../../types/PayloadComponentE
4
4
  import type { CollectionSchemaMap } from "../../../types/CollectionSchemaMap";
5
5
  import type { TranslationProvider } from "../../../core/translation-providers";
6
6
  import type { TaskRunnerFactory } from "../task-runner";
7
+ import type { ProvenanceServiceFactory } from "../provenance";
7
8
  export type CollectionAdminSlot = "beforeDocumentControls" | "beforeListTable";
9
+ /**
10
+ * The one bundle of config-time dependencies every translation surface / route / handler reads —
11
+ * the single source of truth that {@link LevelContext}, `PluginConfigBuilderDeps`, `StalenessConfig`
12
+ * and `TranslationRoutesDeps` all derive from (via `extends`/`Pick`), so a field can never drift
13
+ * between them.
14
+ */
15
+ export type TranslationContext = {
16
+ readonly collections: CollectionConfig[];
17
+ readonly basePath: string;
18
+ readonly access?: AccessGuard;
19
+ readonly taskRunnerFactory: TaskRunnerFactory;
20
+ /** Deep-cloned localized field schema per managed collection slug. */
21
+ readonly schemaMap: CollectionSchemaMap;
22
+ /** The configured translation backend (used by the synchronous field level). */
23
+ readonly translationProvider: TranslationProvider;
24
+ /** Builds a provenance service; absent when provenance is disabled (staleness then reports empty). */
25
+ readonly provenanceServiceFactory?: ProvenanceServiceFactory;
26
+ };
8
27
  /**
9
28
  * A composable translation surface (document / collection / field).
10
29
  *
@@ -31,15 +50,7 @@ export interface TranslationLevel {
31
50
  * Payload config. The plugin deduplicates endpoints by method + path on apply.
32
51
  * @internal
33
52
  */
34
- export interface LevelContext {
35
- readonly collections: CollectionConfig[];
36
- readonly basePath: string;
37
- readonly access?: AccessGuard;
38
- readonly taskRunnerFactory: TaskRunnerFactory;
39
- /** Deep-cloned localized field schema per managed collection slug. */
40
- readonly schemaMap: CollectionSchemaMap;
41
- /** The configured translation backend (used by the synchronous field level). */
42
- readonly translationProvider: TranslationProvider;
53
+ export interface LevelContext extends TranslationContext {
43
54
  /** Register endpoints. Deduplicated by method + path when applied. */
44
55
  addEndpoints(endpoints: Endpoint[]): void;
45
56
  /** Attach an admin component to a slot on every managed collection. */
@@ -1,3 +1,4 @@
1
1
  export { ServerResponse } from "./ServerResponse";
2
2
  export { withErrorHandler } from "./withErrorHandler";
3
3
  export { withAccessCheck } from "./withAccessCheck";
4
+ export { toClientErrorMessage, GENERIC_TRANSLATION_ERROR } from "./toClientErrorMessage";
@@ -1,5 +1,6 @@
1
1
  export { ServerResponse } from "./ServerResponse";
2
2
  export { withErrorHandler } from "./withErrorHandler";
3
3
  export { withAccessCheck } from "./withAccessCheck";
4
+ export { toClientErrorMessage, GENERIC_TRANSLATION_ERROR } from "./toClientErrorMessage";
4
5
 
5
6
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,15 @@
1
+ /** Shown to the browser instead of a raw provider/runtime error outside development. */
2
+ export declare const GENERIC_TRANSLATION_ERROR = "Translation failed. See the server logs for details.";
3
+ /**
4
+ * Collapse a server-side error message to something safe to send to the browser.
5
+ *
6
+ * Provider/runtime errors (e.g. `401 Incorrect API key provided: sk-proj-…`) leak implementation
7
+ * detail — and sometimes secrets — so the client must not see them in production. The full error
8
+ * still lives in the job record and server logs for debugging; only the client-facing copy is
9
+ * collapsed. Fail-safe: the raw message passes through solely in `development`/`test`; every other
10
+ * environment (including an unset `NODE_ENV`) returns {@link GENERIC_TRANSLATION_ERROR}.
11
+ *
12
+ * @param message - The raw server-side error message, if any.
13
+ * @returns The raw message in a debug environment, otherwise the generic text.
14
+ */
15
+ export declare function toClientErrorMessage(message?: string): string;
@@ -0,0 +1,26 @@
1
+ /** Shown to the browser instead of a raw provider/runtime error outside development. */ export const GENERIC_TRANSLATION_ERROR = "Translation failed. See the server logs for details.";
2
+ // Only these environments get the raw message as a debug aid. Anything else — production, an unset
3
+ // or misconfigured NODE_ENV — is treated as "not debug", so the default is the safe, generic text.
4
+ const DEBUG_ENVS = new Set([
5
+ "development",
6
+ "test"
7
+ ]);
8
+ /**
9
+ * Collapse a server-side error message to something safe to send to the browser.
10
+ *
11
+ * Provider/runtime errors (e.g. `401 Incorrect API key provided: sk-proj-…`) leak implementation
12
+ * detail — and sometimes secrets — so the client must not see them in production. The full error
13
+ * still lives in the job record and server logs for debugging; only the client-facing copy is
14
+ * collapsed. Fail-safe: the raw message passes through solely in `development`/`test`; every other
15
+ * environment (including an unset `NODE_ENV`) returns {@link GENERIC_TRANSLATION_ERROR}.
16
+ *
17
+ * @param message - The raw server-side error message, if any.
18
+ * @returns The raw message in a debug environment, otherwise the generic text.
19
+ */ export function toClientErrorMessage(message) {
20
+ if (DEBUG_ENVS.has(process.env.NODE_ENV ?? "")) {
21
+ return message?.trim() || GENERIC_TRANSLATION_ERROR;
22
+ }
23
+ return GENERIC_TRANSLATION_ERROR;
24
+ }
25
+
26
+ //# sourceMappingURL=toClientErrorMessage.js.map
@@ -1,4 +1,4 @@
1
- export { ServerResponse, withErrorHandler, withAccessCheck } from "./http";
1
+ export { ServerResponse, withErrorHandler, withAccessCheck, toClientErrorMessage, GENERIC_TRANSLATION_ERROR, } from "./http";
2
2
  export { AnyAccessGuard } from "./access";
3
3
  export type { AccessGuard, AccessGuardRequest, Handler } from "./access";
4
4
  export { isEmpty, isObject, normalizePath, pipe, getByPath, setByPath, filterLocalizedFields, } from "./utils";
@@ -1,5 +1,5 @@
1
1
  // HTTP utilities
2
- export { ServerResponse, withErrorHandler, withAccessCheck } from "./http";
2
+ export { ServerResponse, withErrorHandler, withAccessCheck, toClientErrorMessage, GENERIC_TRANSLATION_ERROR } from "./http";
3
3
  // Access control
4
4
  export { AnyAccessGuard } from "./access";
5
5
  // General utilities
@@ -0,0 +1,8 @@
1
+ import type { CollectionSlug, Payload } from "payload";
2
+ /**
3
+ * Fetch a document's source-locale data with the exact shape the provenance fingerprint depends on
4
+ * (`depth: 0`, the given locale). Used by the translation **write** path (fingerprints the source it
5
+ * just translated) and, through {@link ProvenanceService}, by the staleness **read** path
6
+ * (re-fingerprints the live source), so the fingerprint baseline can never drift between the two.
7
+ */
8
+ export declare function fetchSourceDocument(payload: Payload, collection: CollectionSlug, id: string, locale: string): Promise<import("payload").JsonObject & import("payload").TypeWithID>;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Fetch a document's source-locale data with the exact shape the provenance fingerprint depends on
3
+ * (`depth: 0`, the given locale). Used by the translation **write** path (fingerprints the source it
4
+ * just translated) and, through {@link ProvenanceService}, by the staleness **read** path
5
+ * (re-fingerprints the live source), so the fingerprint baseline can never drift between the two.
6
+ */ export function fetchSourceDocument(payload, collection, id, locale) {
7
+ return payload.findByID({
8
+ collection,
9
+ id,
10
+ locale,
11
+ depth: 0
12
+ });
13
+ }
14
+
15
+ //# sourceMappingURL=sourceDocument.js.map
@@ -0,0 +1,10 @@
1
+ import type { Config } from "payload";
2
+ /**
3
+ * A config-time contribution: takes the Payload `config` and returns it (possibly a fresh object).
4
+ * Each translator module exposes a `configure(ctx) → ConfigModifier`; the plugin registers them
5
+ * through `PluginConfigBuilder.addConfigModifier`, which is the one place they are applied.
6
+ *
7
+ * Lives in `types/` (a leaf contract layer) so both `translation-levels` and the `provenance`
8
+ * module can import it without creating a module cycle.
9
+ */
10
+ export type ConfigModifier = (config: Config) => Config;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * A config-time contribution: takes the Payload `config` and returns it (possibly a fresh object).
3
+ * Each translator module exposes a `configure(ctx) → ConfigModifier`; the plugin registers them
4
+ * through `PluginConfigBuilder.addConfigModifier`, which is the one place they are applied.
5
+ *
6
+ * Lives in `types/` (a leaf contract layer) so both `translation-levels` and the `provenance`
7
+ * module can import it without creating a module cycle.
8
+ */ export { };
9
+
10
+ //# sourceMappingURL=ConfigModifier.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@focus-reactive/payload-plugin-translator",
3
- "version": "0.7.2",
3
+ "version": "0.8.1",
4
4
  "description": "Translation plugin for Payload CMS 3.x. Automatically translate your localized content using any translation provider.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,7 +0,0 @@
1
- type CompletedTranslationStatusProps = {
2
- completed_at: string;
3
- sourceLocale: string;
4
- targetLocale: string;
5
- };
6
- export declare function CompletedTranslationStatus({ completed_at, sourceLocale, targetLocale, }: CompletedTranslationStatusProps): import("react/jsx-runtime").JSX.Element;
7
- export {};