@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
@@ -1,23 +1,22 @@
1
1
  import type { CollectionSlug, Endpoint } from "payload";
2
- import type { AccessGuard } from "../shared";
3
- import type { TaskRunnerFactory } from "../modules/task-runner";
4
- export type TranslationRoutesDeps = {
5
- taskRunnerFactory: TaskRunnerFactory;
2
+ import type { TranslationContext } from "../modules/translation-levels";
3
+ export type TranslationRoutesDeps = Pick<TranslationContext, "taskRunnerFactory" | "access" | "provenanceServiceFactory"> & {
6
4
  /** Collections the plugin manages — gates which slugs the routes accept. */
7
5
  collectionConfig: {
8
6
  availableCollections: Set<CollectionSlug>;
9
7
  };
10
- access?: AccessGuard;
11
8
  basePath?: string;
12
9
  };
13
10
  /**
14
- * The translator's HTTP surface as one shared bundle: the 6 job-API endpoints
11
+ * The translator's HTTP surface as one shared bundle: the job-API endpoints
15
12
  * (enqueue / run / cancel / cancel-by-collection / document-status /
16
- * collection-status), all wired to the same runner factory and access guard.
13
+ * collection-status) plus the provenance-backed staleness endpoints
14
+ * (document-staleness / dismiss), all wired to the same access guard.
17
15
  *
18
16
  * Extracted from the plugin's `init()` so registration is a single unit. The
19
17
  * document and collection levels each contribute this bundle via the level
20
18
  * context; the plugin deduplicates by method + path, so it registers exactly
21
- * once. Behaviour-preserving: same endpoints, same order, same wiring.
19
+ * once. The staleness endpoints always register; when provenance is disabled
20
+ * (`provenanceServiceFactory` absent) they simply report no staleness.
22
21
  */
23
- export declare function createTranslationRoutes({ taskRunnerFactory, collectionConfig, access, basePath, }: TranslationRoutesDeps): Endpoint[];
22
+ export declare function createTranslationRoutes({ taskRunnerFactory, collectionConfig, access, basePath, provenanceServiceFactory, }: TranslationRoutesDeps): Endpoint[];
@@ -4,23 +4,32 @@ import { createCancelRoute } from "./cancel";
4
4
  import { createCancelByCollectionRoute } from "./cancel-by-collection";
5
5
  import { createGetDocumentStatusRoute } from "./get-document-status";
6
6
  import { createGetCollectionStatusRoute } from "./get-collection-status";
7
+ import { createGetDocumentStalenessRoute, createDismissStalenessRoute } from "./staleness";
7
8
  /**
8
- * The translator's HTTP surface as one shared bundle: the 6 job-API endpoints
9
+ * The translator's HTTP surface as one shared bundle: the job-API endpoints
9
10
  * (enqueue / run / cancel / cancel-by-collection / document-status /
10
- * collection-status), all wired to the same runner factory and access guard.
11
+ * collection-status) plus the provenance-backed staleness endpoints
12
+ * (document-staleness / dismiss), all wired to the same access guard.
11
13
  *
12
14
  * Extracted from the plugin's `init()` so registration is a single unit. The
13
15
  * document and collection levels each contribute this bundle via the level
14
16
  * context; the plugin deduplicates by method + path, so it registers exactly
15
- * once. Behaviour-preserving: same endpoints, same order, same wiring.
16
- */ export function createTranslationRoutes({ taskRunnerFactory, collectionConfig, access, basePath }) {
17
+ * once. The staleness endpoints always register; when provenance is disabled
18
+ * (`provenanceServiceFactory` absent) they simply report no staleness.
19
+ */ export function createTranslationRoutes({ taskRunnerFactory, collectionConfig, access, basePath, provenanceServiceFactory }) {
20
+ const stalenessConfig = {
21
+ availableCollections: collectionConfig.availableCollections,
22
+ provenanceServiceFactory
23
+ };
17
24
  return [
18
25
  createEnqueueRoute(taskRunnerFactory, collectionConfig, access, basePath),
19
26
  createRunRoute(taskRunnerFactory, access, basePath),
20
27
  createCancelRoute(taskRunnerFactory, access, basePath),
21
28
  createCancelByCollectionRoute(collectionConfig, taskRunnerFactory, access, basePath),
22
29
  createGetDocumentStatusRoute(collectionConfig, taskRunnerFactory, access, basePath),
23
- createGetCollectionStatusRoute(collectionConfig, taskRunnerFactory, access, basePath)
30
+ createGetCollectionStatusRoute(collectionConfig, taskRunnerFactory, access, basePath),
31
+ createGetDocumentStalenessRoute(stalenessConfig, access, basePath),
32
+ createDismissStalenessRoute(stalenessConfig, access, basePath)
24
33
  ];
25
34
  }
26
35
 
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { JobIdSchema } from "../../shared";
2
+ import { JobIdSchema, toClientErrorMessage } from "../../shared";
3
3
  /**
4
4
  * Input validation schema.
5
5
  *
@@ -28,7 +28,12 @@ import { JobIdSchema } from "../../shared";
28
28
  target_lng: task.input.targetLng,
29
29
  strategy: task.input.strategy
30
30
  },
31
- error: task.error,
31
+ // Never ship the raw provider/runtime error to the browser outside development — it can leak
32
+ // implementation detail and secrets (e.g. a partial API key). The full error stays in the job
33
+ // record + server logs.
34
+ error: task.error ? {
35
+ message: toClientErrorMessage(task.error.message)
36
+ } : undefined,
32
37
  cancelled: task.cancelled
33
38
  };
34
39
  }
@@ -0,0 +1,8 @@
1
+ import type { PayloadRequest } from "payload";
2
+ import type { StalenessConfig } from "./model";
3
+ /** Dismisses (acknowledges) staleness of one target locale for a document. */
4
+ export declare class DismissStalenessHandler {
5
+ private readonly config;
6
+ constructor(config: StalenessConfig);
7
+ handle(req: PayloadRequest): Promise<Response>;
8
+ }
@@ -0,0 +1,31 @@
1
+ import { ServerResponse } from "../../shared";
2
+ import { isCollectionAvailable } from "../_lib/collection-utils";
3
+ import { DismissStalenessInputSchema } from "./model";
4
+ /** Dismisses (acknowledges) staleness of one target locale for a document. */ export class DismissStalenessHandler {
5
+ config;
6
+ constructor(config){
7
+ this.config = config;
8
+ }
9
+ async handle(req) {
10
+ const validationResult = DismissStalenessInputSchema.safeParse(await req.json?.());
11
+ if (validationResult.error) {
12
+ return ServerResponse.validationError(validationResult.error.issues);
13
+ }
14
+ const { collection_slug, collection_id, target_lng } = validationResult.data;
15
+ const collectionSlug = isCollectionAvailable(collection_slug, this.config.availableCollections);
16
+ if (!collectionSlug) {
17
+ return ServerResponse.badRequest("Collection not available for translation");
18
+ }
19
+ const service = this.config.provenanceServiceFactory?.(req.payload);
20
+ await service?.dismiss({
21
+ collectionSlug: collectionSlug,
22
+ documentId: collection_id,
23
+ targetLocale: target_lng
24
+ });
25
+ return ServerResponse.success({
26
+ success: true
27
+ });
28
+ }
29
+ }
30
+
31
+ //# sourceMappingURL=dismissStaleness.handler.js.map
@@ -0,0 +1,12 @@
1
+ import type { PayloadRequest } from "payload";
2
+ import type { StalenessConfig } from "./model";
3
+ /**
4
+ * Reads per-locale staleness for a single document. Best-effort: a recompute failure (e.g. the
5
+ * consumer enabled provenance but has not run the SQL migration, so the sidecar table is missing)
6
+ * degrades to an empty result and a log line — it must never 500 the document panel.
7
+ */
8
+ export declare class GetDocumentStalenessHandler {
9
+ private readonly config;
10
+ constructor(config: StalenessConfig);
11
+ handle(req: PayloadRequest): Promise<Response>;
12
+ }
@@ -0,0 +1,43 @@
1
+ import { ServerResponse } from "../../shared";
2
+ import { isCollectionAvailable } from "../_lib/collection-utils";
3
+ import { GetDocumentStalenessInputSchema } from "./model";
4
+ /**
5
+ * Reads per-locale staleness for a single document. Best-effort: a recompute failure (e.g. the
6
+ * consumer enabled provenance but has not run the SQL migration, so the sidecar table is missing)
7
+ * degrades to an empty result and a log line — it must never 500 the document panel.
8
+ */ export class GetDocumentStalenessHandler {
9
+ config;
10
+ constructor(config){
11
+ this.config = config;
12
+ }
13
+ async handle(req) {
14
+ const validationResult = GetDocumentStalenessInputSchema.safeParse(req.routeParams);
15
+ if (validationResult.error) {
16
+ return ServerResponse.validationError(validationResult.error.issues);
17
+ }
18
+ const { collection_slug, collection_id } = validationResult.data;
19
+ const collectionSlug = isCollectionAvailable(collection_slug, this.config.availableCollections);
20
+ if (!collectionSlug) {
21
+ return ServerResponse.badRequest("Collection not available for translation");
22
+ }
23
+ try {
24
+ const service = this.config.provenanceServiceFactory?.(req.payload);
25
+ const locales = service ? await service.getStaleness(collectionSlug, collection_id) : [];
26
+ return ServerResponse.success({
27
+ locales
28
+ });
29
+ } catch (error) {
30
+ req.payload.logger.error({
31
+ err: error,
32
+ collection: collectionSlug,
33
+ documentId: collection_id,
34
+ msg: "translator: failed to compute translation staleness"
35
+ });
36
+ return ServerResponse.success({
37
+ locales: []
38
+ });
39
+ }
40
+ }
41
+ }
42
+
43
+ //# sourceMappingURL=getDocumentStaleness.handler.js.map
@@ -0,0 +1,2 @@
1
+ export { createGetDocumentStalenessRoute, createDismissStalenessRoute } from "./route";
2
+ export type { StalenessConfig } from "./model";
@@ -0,0 +1,3 @@
1
+ export { createGetDocumentStalenessRoute, createDismissStalenessRoute } from "./route";
2
+
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,40 @@
1
+ import { z } from "zod";
2
+ import type { CollectionSlug } from "payload";
3
+ import type { TranslationContext } from "../../modules/translation-levels";
4
+ /**
5
+ * Route params for reading a document's per-locale staleness.
6
+ *
7
+ * `collection_id` accepts any shape Payload allows as a document id — see {@link JobIdSchema}.
8
+ */
9
+ export declare const GetDocumentStalenessInputSchema: z.ZodObject<{
10
+ collection_id: z.ZodEffects<z.ZodUnion<[z.ZodEffects<z.ZodString, string, string>, z.ZodNumber]>, string, string | number>;
11
+ collection_slug: z.ZodString;
12
+ }, "strip", z.ZodTypeAny, {
13
+ collection_slug: string;
14
+ collection_id: string;
15
+ }, {
16
+ collection_slug: string;
17
+ collection_id: string | number;
18
+ }>;
19
+ /** Body for dismissing (acknowledging) staleness of one target locale. */
20
+ export declare const DismissStalenessInputSchema: z.ZodObject<{
21
+ collection_id: z.ZodEffects<z.ZodUnion<[z.ZodEffects<z.ZodString, string, string>, z.ZodNumber]>, string, string | number>;
22
+ collection_slug: z.ZodString;
23
+ target_lng: z.ZodString;
24
+ }, "strip", z.ZodTypeAny, {
25
+ collection_slug: string;
26
+ collection_id: string;
27
+ target_lng: string;
28
+ }, {
29
+ collection_slug: string;
30
+ collection_id: string | number;
31
+ target_lng: string;
32
+ }>;
33
+ /**
34
+ * Handler configuration. `provenanceServiceFactory` is absent when provenance is disabled — the
35
+ * handlers then report no staleness (empty), so the endpoint contract stays stable either way.
36
+ * The fingerprint policy + schema live inside {@link ProvenanceService}, so no `schemaMap` here.
37
+ */
38
+ export type StalenessConfig = Pick<TranslationContext, "provenanceServiceFactory"> & {
39
+ availableCollections: Set<CollectionSlug>;
40
+ };
@@ -0,0 +1,17 @@
1
+ import { z } from "zod";
2
+ import { JobIdSchema } from "../../shared";
3
+ /**
4
+ * Route params for reading a document's per-locale staleness.
5
+ *
6
+ * `collection_id` accepts any shape Payload allows as a document id — see {@link JobIdSchema}.
7
+ */ export const GetDocumentStalenessInputSchema = z.object({
8
+ collection_id: JobIdSchema,
9
+ collection_slug: z.string().nonempty()
10
+ });
11
+ /** Body for dismissing (acknowledging) staleness of one target locale. */ export const DismissStalenessInputSchema = z.object({
12
+ collection_id: JobIdSchema,
13
+ collection_slug: z.string().nonempty(),
14
+ target_lng: z.string().nonempty()
15
+ });
16
+
17
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1,7 @@
1
+ import type { Endpoint } from "payload";
2
+ import type { AccessGuard } from "../../shared";
3
+ import type { StalenessConfig } from "./model";
4
+ /** GET per-locale staleness for one document. */
5
+ export declare function createGetDocumentStalenessRoute(config: StalenessConfig, access?: AccessGuard, basePath?: string): Endpoint;
6
+ /** POST to dismiss (acknowledge) staleness of one target locale. */
7
+ export declare function createDismissStalenessRoute(config: StalenessConfig, access?: AccessGuard, basePath?: string): Endpoint;
@@ -0,0 +1,21 @@
1
+ import { withErrorHandler, withAccessCheck } from "../../shared";
2
+ import { GetDocumentStalenessHandler } from "./getDocumentStaleness.handler";
3
+ import { DismissStalenessHandler } from "./dismissStaleness.handler";
4
+ /** GET per-locale staleness for one document. */ export function createGetDocumentStalenessRoute(config, access, basePath = "/translate") {
5
+ const handler = new GetDocumentStalenessHandler(config);
6
+ return {
7
+ path: `${basePath}/stale/:collection_slug/:collection_id`,
8
+ method: "get",
9
+ handler: withAccessCheck(withErrorHandler(handler.handle.bind(handler)), access)
10
+ };
11
+ }
12
+ /** POST to dismiss (acknowledge) staleness of one target locale. */ export function createDismissStalenessRoute(config, access, basePath = "/translate") {
13
+ const handler = new DismissStalenessHandler(config);
14
+ return {
15
+ path: `${basePath}/stale/dismiss`,
16
+ method: "post",
17
+ handler: withAccessCheck(withErrorHandler(handler.handle.bind(handler)), access)
18
+ };
19
+ }
20
+
21
+ //# sourceMappingURL=route.js.map
@@ -1,21 +1,19 @@
1
1
  import type { Payload } from "payload";
2
2
  import type { Handler } from "../../shared";
3
3
  import type { TranslationProvider } from "../../../core/translation-providers";
4
- import type { ProvenanceStoreFactory } from "../../modules/provenance";
4
+ import type { ProvenanceServiceFactory } from "../../modules/provenance";
5
5
  import type { CollectionSchemaMap } from "../../../types/CollectionSchemaMap";
6
6
  import type { TranslateDocumentInput, TranslateDocumentOutput } from "./model";
7
- export type TranslateDocumentDependencies = {
8
- translationProvider: TranslationProvider;
9
- schemaMap: CollectionSchemaMap;
10
- };
11
7
  /**
12
- * Translates a single document from source language to target language
8
+ * Translates a single document from source language to target language. Provenance is delegated to
9
+ * {@link ProvenanceService}: this handler only decides *when* to capture the source fingerprint
10
+ * (before the pipeline mutates the source in place) and *when* to record it (after the save).
13
11
  */
14
12
  export declare class TranslateDocumentHandler implements Handler<TranslateDocumentInput, TranslateDocumentOutput> {
15
13
  private readonly translationProvider;
16
14
  private readonly schemaMap;
17
- private readonly provenanceStoreFactory?;
18
- constructor(translationProvider: TranslationProvider, schemaMap: CollectionSchemaMap, provenanceStoreFactory?: ProvenanceStoreFactory);
15
+ private readonly provenanceServiceFactory?;
16
+ constructor(translationProvider: TranslationProvider, schemaMap: CollectionSchemaMap, provenanceServiceFactory?: ProvenanceServiceFactory);
19
17
  handle(payload: Payload, input: TranslateDocumentInput): Promise<TranslateDocumentOutput>;
20
18
  private saveTranslatedDocument;
21
19
  }
@@ -1,28 +1,31 @@
1
1
  import { APIError } from "payload";
2
2
  import { translateContent } from "../../../core/translation-pipeline";
3
- import { computeSourceFingerprint } from "../../../core/content-projection/computeSourceFingerprint";
3
+ import { fetchSourceDocument } from "../../shared/payload/sourceDocument";
4
4
  /**
5
- * Translates a single document from source language to target language
5
+ * Translates a single document from source language to target language. Provenance is delegated to
6
+ * {@link ProvenanceService}: this handler only decides *when* to capture the source fingerprint
7
+ * (before the pipeline mutates the source in place) and *when* to record it (after the save).
6
8
  */ export class TranslateDocumentHandler {
7
9
  translationProvider;
8
10
  schemaMap;
9
- provenanceStoreFactory;
10
- constructor(translationProvider, schemaMap, provenanceStoreFactory){
11
+ provenanceServiceFactory;
12
+ constructor(translationProvider, schemaMap, provenanceServiceFactory){
11
13
  this.translationProvider = translationProvider;
12
14
  this.schemaMap = schemaMap;
13
- this.provenanceStoreFactory = provenanceStoreFactory;
15
+ this.provenanceServiceFactory = provenanceServiceFactory;
14
16
  }
15
17
  async handle(payload, input) {
16
18
  const { collection, collectionId, sourceLng, targetLng, strategy, publishOnTranslation } = input;
17
19
  // Get original schema (preserves localized: true on nested fields)
18
20
  const schema = this.schemaMap.get(collection);
19
21
  if (!schema) throw new APIError(`Collection "${collection}" not found in schemaMap`, 400);
20
- const sourceData = await payload.findByID({
21
- collection,
22
- id: collectionId,
23
- locale: sourceLng,
24
- depth: 0
25
- });
22
+ const sourceData = await fetchSourceDocument(payload, collection, collectionId, sourceLng);
23
+ // Capture the staleness baseline from the PRISTINE source NOW, before the pipeline runs — it
24
+ // translates in place and shares object-valued source leaves (e.g. richText nodes) by reference,
25
+ // so fingerprinting after `translateContent` would hash the target translation and make every
26
+ // fresh translation look instantly stale. The service is best-effort (a failure returns null).
27
+ const provenance = this.provenanceServiceFactory?.(payload);
28
+ const sourceFingerprint = provenance?.captureFingerprint(collection, sourceData) ?? null;
26
29
  const targetData = await payload.findByID({
27
30
  collection,
28
31
  id: collectionId,
@@ -44,28 +47,13 @@ import { computeSourceFingerprint } from "../../../core/content-projection/compu
44
47
  };
45
48
  const collectionConfig = payload.collections[collection].config;
46
49
  await this.saveTranslatedDocument(payload, collection, collectionId, translatedData, targetLng, sourceLng, collectionConfig, publishOnTranslation);
47
- if (this.provenanceStoreFactory) {
48
- const store = this.provenanceStoreFactory(payload);
49
- try {
50
- await store.upsert({
51
- collectionSlug: collection,
52
- documentId: String(collectionId),
53
- targetLocale: targetLng,
54
- sourceLocale: sourceLng,
55
- sourceFingerprint: computeSourceFingerprint(sourceData, schema),
56
- translatedAt: new Date().toISOString(),
57
- dismissedFingerprint: null
58
- });
59
- } catch (error) {
60
- payload.logger.error({
61
- err: error,
62
- collection,
63
- documentId: String(collectionId),
64
- targetLocale: targetLng,
65
- sourceLocale: sourceLng,
66
- msg: "translator: failed to record translation provenance"
67
- });
68
- }
50
+ if (provenance && sourceFingerprint !== null) {
51
+ await provenance.record({
52
+ collectionSlug: collection,
53
+ documentId: String(collectionId),
54
+ targetLocale: targetLng,
55
+ sourceLocale: sourceLng
56
+ }, sourceFingerprint);
69
57
  }
70
58
  return {
71
59
  success: true
@@ -1,2 +1,3 @@
1
1
  export { TranslateDocumentHandler } from "./handler";
2
+ export { wireTranslateRunner } from "./wireTranslateRunner";
2
3
  export type { CollectionSchemaMap } from "../../../types/CollectionSchemaMap";
@@ -1,3 +1,4 @@
1
1
  export { TranslateDocumentHandler } from "./handler";
2
+ export { wireTranslateRunner } from "./wireTranslateRunner";
2
3
 
3
4
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,28 @@
1
+ import type { CollectionSlug } from "payload";
2
+ import type { TranslationProvider } from "../../../core/translation-providers";
3
+ import type { CollectionSchemaMap } from "../../../types/CollectionSchemaMap";
4
+ import type { ConfigModifier } from "../../../types/ConfigModifier";
5
+ import type { ProvenanceServiceFactory } from "../../modules/provenance";
6
+ import type { TranslationLifecycleCallbacks } from "../../modules/lifecycle";
7
+ import type { TaskRunnerFactory, TaskRunnerProvider } from "../../modules/task-runner";
8
+ type WireTranslateRunnerParams = {
9
+ translationProvider: TranslationProvider;
10
+ schemaMap: CollectionSchemaMap;
11
+ provenanceServiceFactory?: ProvenanceServiceFactory;
12
+ runner: TaskRunnerProvider;
13
+ lifecycle: TranslationLifecycleCallbacks;
14
+ collections: CollectionSlug[];
15
+ };
16
+ /**
17
+ * Assemble the document-translation task pipeline: the {@link TranslateDocumentHandler}, the runner
18
+ * context that wraps each task with lifecycle notifications, the runner's config modifier, and the
19
+ * per-request {@link TaskRunnerFactory} (decorated with `onQueued` notification when configured).
20
+ *
21
+ * Extracted from the plugin's `init()` so the composition root stays a flat list — `plugin.ts` calls
22
+ * this once and registers the returned `configModifier` through the shared builder.
23
+ */
24
+ export declare function wireTranslateRunner({ translationProvider, schemaMap, provenanceServiceFactory, runner, lifecycle, collections, }: WireTranslateRunnerParams): {
25
+ taskRunnerFactory: TaskRunnerFactory;
26
+ configModifier: ConfigModifier;
27
+ };
28
+ export {};
@@ -0,0 +1,49 @@
1
+ import { LifecycleNotifier, taskFromHandlerInput, withQueuedNotification } from "../../modules/lifecycle";
2
+ import { TranslateDocumentHandler } from "./handler";
3
+ /**
4
+ * Assemble the document-translation task pipeline: the {@link TranslateDocumentHandler}, the runner
5
+ * context that wraps each task with lifecycle notifications, the runner's config modifier, and the
6
+ * per-request {@link TaskRunnerFactory} (decorated with `onQueued` notification when configured).
7
+ *
8
+ * Extracted from the plugin's `init()` so the composition root stays a flat list — `plugin.ts` calls
9
+ * this once and registers the returned `configModifier` through the shared builder.
10
+ */ export function wireTranslateRunner({ translationProvider, schemaMap, provenanceServiceFactory, runner, lifecycle, collections }) {
11
+ const translateHandler = new TranslateDocumentHandler(translationProvider, schemaMap, provenanceServiceFactory);
12
+ const runnerContext = {
13
+ handler: async (payload, input)=>{
14
+ const notifier = new LifecycleNotifier(lifecycle, payload.logger);
15
+ const task = taskFromHandlerInput(input);
16
+ try {
17
+ await translateHandler.handle(payload, {
18
+ collection: input.collection,
19
+ collectionId: input.collectionId,
20
+ sourceLng: input.sourceLng,
21
+ targetLng: input.targetLng,
22
+ strategy: input.strategy,
23
+ publishOnTranslation: input.publishOnTranslation
24
+ });
25
+ } catch (error) {
26
+ await notifier.failed(task, error);
27
+ throw error; // rethrow so the runner marks the job failed
28
+ }
29
+ await notifier.completed(task);
30
+ },
31
+ collections
32
+ };
33
+ // Bind the context once so routes receive a self-sufficient factory: the runner needs no mutable
34
+ // per-instance handler state, and create() has no "configure() must run first" ordering coupling.
35
+ // When an `onQueued` callback is set, decorate the runner so `enqueue` fires it.
36
+ const taskRunnerFactory = {
37
+ create: (payload)=>{
38
+ const taskRunner = runner.create(payload, runnerContext.handler);
39
+ if (!lifecycle.onQueued) return taskRunner;
40
+ return withQueuedNotification(taskRunner, new LifecycleNotifier(lifecycle, payload.logger));
41
+ }
42
+ };
43
+ return {
44
+ taskRunnerFactory,
45
+ configModifier: runner.configure(runnerContext)
46
+ };
47
+ }
48
+
49
+ //# sourceMappingURL=wireTranslateRunner.js.map
@@ -1,14 +1,7 @@
1
1
  import type { CollectionConfig } from "payload";
2
+ import type { ManagedCollectionsConfig } from "./Provenance.shapes";
2
3
  /** Default slug for the provenance sidecar collection. Overridable via `provenance.slug`. */
3
4
  export declare const DEFAULT_PROVENANCE_SLUG = "translator-provenance";
4
- /**
5
- * True for the plugin's own provenance sidecar collection. Recognised by the {@link PROVENANCE_MARKER}
6
- * on `custom` (not the slug, which is consumer-configurable), so plugin wiring can stay idempotent on
7
- * a repeated run and the slug-collision guard can ignore an already-added sidecar.
8
- */
9
- export declare function isProvenanceCollection(collection: {
10
- custom?: unknown;
11
- }): boolean;
12
5
  /**
13
6
  * Build the Payload config for the provenance sidecar collection.
14
7
  *
@@ -18,6 +11,21 @@ export declare function isProvenanceCollection(collection: {
18
11
  * content. The composite index on `(collectionSlug, documentId, targetLocale)` is the upsert key.
19
12
  *
20
13
  * The plugin ships only this config; the consumer's Payload creates the table (see the design doc's
21
- * SQL-migration note). Enabled only when the consumer opts in via `provenance` (wired in slice B).
14
+ * SQL-migration note). Enabled only when the consumer opts in via `provenance`.
22
15
  */
23
16
  export declare function makeProvenanceCollection(slug?: string): CollectionConfig;
17
+ /**
18
+ * True for the plugin's own provenance sidecar collection. Recognised by the {@link PROVENANCE_MARKER}
19
+ * on `custom` (not the slug, which is consumer-configurable), so plugin wiring can stay idempotent on
20
+ * a repeated run and the slug-collision guard can ignore an already-added sidecar.
21
+ */
22
+ export declare function isProvenanceCollection(collection: {
23
+ custom?: unknown;
24
+ }): boolean;
25
+ /**
26
+ * Idempotently register the provenance sidecar collection on `host`: add it once, skipping when a
27
+ * sidecar with this slug is already present, so a repeated `init()` never stacks a duplicate. Takes
28
+ * only the narrow {@link ManagedCollectionsConfig} slice — a real Payload `Config` is structurally
29
+ * assignable, and a test passes a plain `{ collections: [...] }` literal.
30
+ */
31
+ export declare function ensureProvenanceCollectionRegistered(host: ManagedCollectionsConfig, slug: string): void;
@@ -1,12 +1,5 @@
1
1
  /** Default slug for the provenance sidecar collection. Overridable via `provenance.slug`. */ export const DEFAULT_PROVENANCE_SLUG = "translator-provenance";
2
2
  /** The `custom` marker tagging the plugin's own provenance sidecar (set + read in this module). */ const PROVENANCE_MARKER = "translatorProvenance";
3
- /**
4
- * True for the plugin's own provenance sidecar collection. Recognised by the {@link PROVENANCE_MARKER}
5
- * on `custom` (not the slug, which is consumer-configurable), so plugin wiring can stay idempotent on
6
- * a repeated run and the slug-collision guard can ignore an already-added sidecar.
7
- */ export function isProvenanceCollection(collection) {
8
- return collection.custom?.[PROVENANCE_MARKER] === true;
9
- }
10
3
  /**
11
4
  * Build the Payload config for the provenance sidecar collection.
12
5
  *
@@ -16,7 +9,7 @@
16
9
  * content. The composite index on `(collectionSlug, documentId, targetLocale)` is the upsert key.
17
10
  *
18
11
  * The plugin ships only this config; the consumer's Payload creates the table (see the design doc's
19
- * SQL-migration note). Enabled only when the consumer opts in via `provenance` (wired in slice B).
12
+ * SQL-migration note). Enabled only when the consumer opts in via `provenance`.
20
13
  */ export function makeProvenanceCollection(slug = DEFAULT_PROVENANCE_SLUG) {
21
14
  return {
22
15
  slug,
@@ -78,5 +71,26 @@
78
71
  ]
79
72
  };
80
73
  }
74
+ /**
75
+ * True for the plugin's own provenance sidecar collection. Recognised by the {@link PROVENANCE_MARKER}
76
+ * on `custom` (not the slug, which is consumer-configurable), so plugin wiring can stay idempotent on
77
+ * a repeated run and the slug-collision guard can ignore an already-added sidecar.
78
+ */ export function isProvenanceCollection(collection) {
79
+ return collection.custom?.[PROVENANCE_MARKER] === true;
80
+ }
81
+ /**
82
+ * Idempotently register the provenance sidecar collection on `host`: add it once, skipping when a
83
+ * sidecar with this slug is already present, so a repeated `init()` never stacks a duplicate. Takes
84
+ * only the narrow {@link ManagedCollectionsConfig} slice — a real Payload `Config` is structurally
85
+ * assignable, and a test passes a plain `{ collections: [...] }` literal.
86
+ */ export function ensureProvenanceCollectionRegistered(host, slug) {
87
+ const alreadyAdded = host.collections?.some((collection)=>collection.slug === slug && isProvenanceCollection(collection));
88
+ if (!alreadyAdded) {
89
+ host.collections = [
90
+ ...host.collections ?? [],
91
+ makeProvenanceCollection(slug)
92
+ ];
93
+ }
94
+ }
81
95
 
82
- //# sourceMappingURL=provenanceCollection.js.map
96
+ //# sourceMappingURL=Provenance.collection.js.map
@@ -0,0 +1,56 @@
1
+ import type { CollectionSlug, Payload } from "payload";
2
+ import type { ProvenanceKey, ProvenanceStore } from "../../../core/provenance";
3
+ import type { CollectionSchemaMap } from "../../../types/CollectionSchemaMap";
4
+ /** Per-locale staleness for one document (snake_case, matching the other translation endpoints). */
5
+ export type StalenessLocale = {
6
+ target_lng: string;
7
+ source_lng: string;
8
+ is_stale: boolean;
9
+ translated_at: string;
10
+ };
11
+ /** Builds a {@link ProvenanceService} bound to a Payload instance; absent when provenance is disabled. */
12
+ export type ProvenanceServiceFactory = (payload: Payload) => ProvenanceService;
13
+ /**
14
+ * The single owner of provenance fingerprint policy — how the source is hashed on write, re-hashed on
15
+ * read, and compared for staleness. The write path and the read path go through this one class, so
16
+ * they can never drift (the biggest correctness trap in staleness detection). Sits above the CRUD
17
+ * {@link ProvenanceStore} port; the port + `computeSourceFingerprint` + `isRecordStale` stay
18
+ * framework-agnostic in the core.
19
+ *
20
+ * Best-effort by contract: fingerprint/record failures log and no-op rather than failing a translation.
21
+ */
22
+ export declare class ProvenanceService {
23
+ private readonly payload;
24
+ private readonly store;
25
+ private readonly schemaMap;
26
+ constructor(payload: Payload, store: ProvenanceStore, schemaMap: CollectionSchemaMap);
27
+ /**
28
+ * Hash the PRISTINE source. The caller MUST pass source fetched **before** the translation pipeline
29
+ * runs — the pipeline mutates object-valued leaves (e.g. richText nodes) in place, so hashing after
30
+ * it would capture the target translation and make every fresh translation look instantly stale.
31
+ * Returns `null` on any failure (no schema, hashing error) so provenance is skipped, not the translation.
32
+ */
33
+ captureFingerprint(collection: CollectionSlug, sourceData: Record<string, unknown>): string | null;
34
+ /** Persist a translation receipt (best-effort; a store failure logs and no-ops). */
35
+ record(key: ProvenanceKey & {
36
+ sourceLocale: string;
37
+ }, sourceFingerprint: string): Promise<void>;
38
+ /**
39
+ * Per-locale staleness for one document: read every receipt, recompute the current source
40
+ * fingerprint (write-path-identical), and mark each locale stale on undismissed drift. Returns `[]`
41
+ * when the collection has no schema. Isolates per-locale failures so one bad record can't blank the rest.
42
+ */
43
+ getStaleness(collection: CollectionSlug, documentId: string): Promise<StalenessLocale[]>;
44
+ /**
45
+ * Acknowledge the current source drift for one target locale: persist the current fingerprint as the
46
+ * dismissed one, so the indicator hides until the source changes again. No-op when the collection has
47
+ * no schema or the locale has no record.
48
+ */
49
+ dismiss(key: ProvenanceKey): Promise<void>;
50
+ /**
51
+ * Recompute the current source fingerprint the same way the write path does (shared fetch shape +
52
+ * hash). Cached per source locale so a document translated from one source into N locales fetches
53
+ * the source once.
54
+ */
55
+ private makeCurrentFingerprint;
56
+ }