@focus-reactive/payload-plugin-translator 0.8.0 → 0.8.2

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 (57) hide show
  1. package/dist/client/entities/translation/api/queries/useDocumentTranslation.d.ts +15 -15
  2. package/dist/client/entities/translation/api/queries/useDocumentTranslation.js +27 -11
  3. package/dist/client/entities/translation/index.d.ts +1 -1
  4. package/dist/client/entities/translation/index.js +1 -1
  5. package/dist/client/entities/translation/model/panelStatus.d.ts +8 -2
  6. package/dist/client/entities/translation/model/panelStatus.js +16 -0
  7. package/dist/client/entities/translation/model/statusRows.d.ts +9 -6
  8. package/dist/client/entities/translation/model/statusRows.js +18 -16
  9. package/dist/client/entities/translation/model/types.d.ts +6 -1
  10. package/dist/client/widgets/translate-document/ui/TranslateDocument.js +4 -4
  11. package/dist/composition/levels/useDocTranslationApi.d.ts +2 -2
  12. package/dist/composition/levels/useDocTranslationApi.js +3 -4
  13. package/dist/plugin.js +21 -74
  14. package/dist/server/features/createTranslationRoutes.d.ts +4 -13
  15. package/dist/server/features/createTranslationRoutes.js +3 -4
  16. package/dist/server/features/get-document-status/handler.js +4 -2
  17. package/dist/server/features/get-document-status/model.d.ts +10 -0
  18. package/dist/server/features/get-document-status/model.js +21 -0
  19. package/dist/server/features/staleness/dismissStaleness.handler.js +6 -2
  20. package/dist/server/features/staleness/getDocumentStaleness.handler.js +2 -2
  21. package/dist/server/features/staleness/index.d.ts +1 -1
  22. package/dist/server/features/staleness/model.d.ts +4 -16
  23. package/dist/server/features/translate-document/handler.d.ts +6 -8
  24. package/dist/server/features/translate-document/handler.js +20 -46
  25. package/dist/server/features/translate-document/index.d.ts +1 -0
  26. package/dist/server/features/translate-document/index.js +1 -0
  27. package/dist/server/features/translate-document/wireTranslateRunner.d.ts +28 -0
  28. package/dist/server/features/translate-document/wireTranslateRunner.js +49 -0
  29. package/dist/server/modules/provenance/{provenanceCollection.d.ts → Provenance.collection.d.ts} +17 -9
  30. package/dist/server/modules/provenance/{provenanceCollection.js → Provenance.collection.js} +23 -9
  31. package/dist/server/modules/provenance/Provenance.service.d.ts +56 -0
  32. package/dist/server/modules/provenance/Provenance.service.js +124 -0
  33. package/dist/server/modules/provenance/Provenance.shapes.d.ts +26 -0
  34. package/dist/server/modules/provenance/Provenance.shapes.js +7 -0
  35. package/dist/server/modules/provenance/{PayloadProvenanceStore.js → Provenance.store.js} +1 -1
  36. package/dist/server/modules/provenance/Provenance.wiring.d.ts +23 -0
  37. package/dist/server/modules/provenance/Provenance.wiring.js +43 -0
  38. package/dist/server/modules/provenance/{provenanceCleanupHook.d.ts → ProvenanceCleanup.hook.d.ts} +4 -3
  39. package/dist/server/modules/provenance/{provenanceCleanupHook.js → ProvenanceCleanup.hook.js} +1 -1
  40. package/dist/server/modules/provenance/index.d.ts +8 -4
  41. package/dist/server/modules/provenance/index.js +6 -4
  42. package/dist/server/modules/task-runner/payload-jobs-runner/PayloadJobsTaskRunner.js +11 -2
  43. package/dist/server/modules/task-runner/sync-runner/SyncTaskRunner.js +5 -3
  44. package/dist/server/modules/translation-levels/PluginConfigBuilder.d.ts +6 -14
  45. package/dist/server/modules/translation-levels/PluginConfigBuilder.js +2 -2
  46. package/dist/server/modules/translation-levels/index.d.ts +1 -1
  47. package/dist/server/modules/translation-levels/types.d.ts +20 -12
  48. package/dist/server/shared/payload/sourceDocument.d.ts +8 -0
  49. package/dist/server/shared/payload/sourceDocument.js +15 -0
  50. package/dist/types/ConfigModifier.d.ts +10 -0
  51. package/dist/types/ConfigModifier.js +10 -0
  52. package/package.json +1 -1
  53. package/dist/server/features/_lib/sourceDocument.d.ts +0 -9
  54. package/dist/server/features/_lib/sourceDocument.js +0 -16
  55. package/dist/server/features/staleness/service.d.ts +0 -15
  56. package/dist/server/features/staleness/service.js +0 -78
  57. /package/dist/server/modules/provenance/{PayloadProvenanceStore.d.ts → Provenance.store.d.ts} +0 -0
@@ -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
@@ -1,4 +1,8 @@
1
1
  import { normalizeJob } from "./normalizeJob";
2
+ // A translation job's supersession identity: same document AND same target locale. IDs are
3
+ // String()-normalized to match the stored (string) form, so a number id compares equal to its
4
+ // persisted job.
5
+ const documentLocaleKey = (collectionId, targetLng)=>`${String(collectionId)}:${targetLng}`;
2
6
  /**
3
7
  * TaskRunner implementation using Payload Jobs.
4
8
  *
@@ -15,8 +19,13 @@ import { normalizeJob } from "./normalizeJob";
15
19
  for (const [collectionSlug, items] of byCollection){
16
20
  const documentIds = items.map((t)=>t.collectionId);
17
21
  const existing = await this.findByCollection(collectionSlug, documentIds);
18
- if (existing.length > 0) {
19
- await this.cancelInternal(existing.map((t)=>t.id));
22
+ // Supersede only jobs for the SAME (document, target locale) being re-enqueued — never a
23
+ // concurrent job for a *different* locale of the same document. Cancelling per-document would
24
+ // kill an in-flight translation of another locale (the concurrent re-translate bug).
25
+ const supersededKeys = new Set(items.map((t)=>documentLocaleKey(t.collectionId, t.targetLng)));
26
+ const toCancel = existing.filter((t)=>supersededKeys.has(documentLocaleKey(t.input.collectionId, t.input.targetLng)));
27
+ if (toCancel.length > 0) {
28
+ await this.cancelInternal(toCancel.map((t)=>t.id));
20
29
  }
21
30
  }
22
31
  await Promise.all(tasks.map((task)=>this.payload.jobs.queue({
@@ -14,7 +14,7 @@
14
14
  }
15
15
  async enqueue(inputs) {
16
16
  for (const input of inputs){
17
- const key = this.getKey(input.collectionSlug, input.collectionId);
17
+ const key = this.getKey(input.collectionSlug, input.collectionId, input.targetLng);
18
18
  const now = new Date().toISOString();
19
19
  const task = {
20
20
  id: crypto.randomUUID(),
@@ -65,8 +65,10 @@
65
65
  }
66
66
  return results;
67
67
  }
68
- getKey(collectionSlug, collectionId) {
69
- return `${collectionSlug}:${collectionId}`;
68
+ // Keyed by (document, target locale) so translating a second locale of the same document does not
69
+ // evict the first — findByCollection must be able to return one task per locale.
70
+ getKey(collectionSlug, collectionId, targetLng) {
71
+ return `${collectionSlug}:${collectionId}:${targetLng}`;
70
72
  }
71
73
  }
72
74
 
@@ -2,20 +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 { ProvenanceStoreFactory } from "../provenance";
8
- import type { CollectionAdminSlot, LevelContext } from "./types";
9
- type ConfigModifier = (config: Config) => Config;
10
- export type PluginConfigBuilderDeps = {
11
- collections: CollectionConfig[];
12
- basePath: string;
13
- access?: AccessGuard;
14
- taskRunnerFactory: TaskRunnerFactory;
15
- schemaMap: CollectionSchemaMap;
16
- translationProvider: TranslationProvider;
17
- provenanceStoreFactory?: ProvenanceStoreFactory;
18
- };
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;
19
12
  /**
20
13
  * The single place that mutates the Payload `config`. Levels (through the narrow
21
14
  * {@link LevelContext}) and the plugin (through `addAdminProvider` /
@@ -34,7 +27,7 @@ export declare class PluginConfigBuilder implements LevelContext {
34
27
  readonly taskRunnerFactory: TaskRunnerFactory;
35
28
  readonly schemaMap: CollectionSchemaMap;
36
29
  readonly translationProvider: TranslationProvider;
37
- readonly provenanceStoreFactory?: ProvenanceStoreFactory;
30
+ readonly provenanceServiceFactory?: ProvenanceServiceFactory;
38
31
  private readonly endpoints;
39
32
  private readonly collectionComponents;
40
33
  private readonly adminProviders;
@@ -52,4 +45,3 @@ export declare class PluginConfigBuilder implements LevelContext {
52
45
  private attachCollectionComponents;
53
46
  private registerEndpoints;
54
47
  }
55
- export {};
@@ -34,7 +34,7 @@ function attachToSlot(collection, slot, component) {
34
34
  taskRunnerFactory;
35
35
  schemaMap;
36
36
  translationProvider;
37
- provenanceStoreFactory;
37
+ provenanceServiceFactory;
38
38
  endpoints = [];
39
39
  collectionComponents = [];
40
40
  adminProviders = [];
@@ -46,7 +46,7 @@ function attachToSlot(collection, slot, component) {
46
46
  this.taskRunnerFactory = deps.taskRunnerFactory;
47
47
  this.schemaMap = deps.schemaMap;
48
48
  this.translationProvider = deps.translationProvider;
49
- this.provenanceStoreFactory = deps.provenanceStoreFactory;
49
+ this.provenanceServiceFactory = deps.provenanceServiceFactory;
50
50
  }
51
51
  addEndpoints(endpoints) {
52
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,8 +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 { ProvenanceStoreFactory } from "../provenance";
7
+ import type { ProvenanceServiceFactory } from "../provenance";
8
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
+ };
9
27
  /**
10
28
  * A composable translation surface (document / collection / field).
11
29
  *
@@ -32,17 +50,7 @@ export interface TranslationLevel {
32
50
  * Payload config. The plugin deduplicates endpoints by method + path on apply.
33
51
  * @internal
34
52
  */
35
- export interface LevelContext {
36
- readonly collections: CollectionConfig[];
37
- readonly basePath: string;
38
- readonly access?: AccessGuard;
39
- readonly taskRunnerFactory: TaskRunnerFactory;
40
- /** Deep-cloned localized field schema per managed collection slug. */
41
- readonly schemaMap: CollectionSchemaMap;
42
- /** The configured translation backend (used by the synchronous field level). */
43
- readonly translationProvider: TranslationProvider;
44
- /** Builds a provenance store; absent when provenance is disabled (staleness then reports empty). */
45
- readonly provenanceStoreFactory?: ProvenanceStoreFactory;
53
+ export interface LevelContext extends TranslationContext {
46
54
  /** Register endpoints. Deduplicated by method + path when applied. */
47
55
  addEndpoints(endpoints: Endpoint[]): void;
48
56
  /** Attach an admin component to a slot on every managed collection. */
@@ -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.8.0",
3
+ "version": "0.8.2",
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,9 +0,0 @@
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). Shared by the translation **write** path (which fingerprints the
5
- * source it just translated) and the staleness **read** path (which re-fingerprints the live source),
6
- * so the fingerprint baseline can never drift between the two — the single biggest correctness trap in
7
- * staleness detection is thus enforced structurally, not by a comment.
8
- */
9
- export declare function fetchSourceDocument(payload: Payload, collection: CollectionSlug, id: string, locale: string): Promise<import("payload").JsonObject & import("payload").TypeWithID>;
@@ -1,16 +0,0 @@
1
- /**
2
- * Fetch a document's source-locale data with the exact shape the provenance fingerprint depends on
3
- * (`depth: 0`, the given locale). Shared by the translation **write** path (which fingerprints the
4
- * source it just translated) and the staleness **read** path (which re-fingerprints the live source),
5
- * so the fingerprint baseline can never drift between the two — the single biggest correctness trap in
6
- * staleness detection is thus enforced structurally, not by a comment.
7
- */ export function fetchSourceDocument(payload, collection, id, locale) {
8
- return payload.findByID({
9
- collection,
10
- id,
11
- locale,
12
- depth: 0
13
- });
14
- }
15
-
16
- //# sourceMappingURL=sourceDocument.js.map
@@ -1,15 +0,0 @@
1
- import type { CollectionSlug, Payload } from "payload";
2
- import type { StalenessConfig, StalenessLocaleOutput } from "./model";
3
- /**
4
- * Per-locale staleness for one document: read every provenance record, recompute the current source
5
- * fingerprint, and mark each locale stale when the source drifted and the drift was not dismissed.
6
- * Returns `[]` when provenance is disabled or the collection has no schema — a normal condition, not
7
- * an error (genuine store/fetch failures propagate for the caller to log).
8
- */
9
- export declare function computeDocumentStaleness(payload: Payload, config: StalenessConfig, collection: CollectionSlug, documentId: string): Promise<StalenessLocaleOutput[]>;
10
- /**
11
- * Acknowledge the current source drift for one target locale: recompute the current source
12
- * fingerprint (write-path-identical) and persist it as the dismissed fingerprint, so the indicator
13
- * hides until the source changes again. No-op when provenance is disabled or the locale has no record.
14
- */
15
- export declare function dismissLocaleStaleness(payload: Payload, config: StalenessConfig, collection: CollectionSlug, documentId: string, targetLocale: string): Promise<void>;
@@ -1,78 +0,0 @@
1
- import { computeSourceFingerprint } from "../../../core/content-projection/computeSourceFingerprint";
2
- import { isRecordStale } from "../../../core/provenance";
3
- import { fetchSourceDocument } from "../_lib/sourceDocument";
4
- /**
5
- * Recompute the current source fingerprint the **same way the write path does**. Both sides fetch via
6
- * the shared {@link fetchSourceDocument} (identical `depth`/locale shape) and hash with the shared
7
- * {@link computeSourceFingerprint} + the same `schemaMap` entry, so an untouched document can never
8
- * report stale. Cached per source locale so a document translated from one source into N locales
9
- * fetches the source once.
10
- */ function makeCurrentFingerprint(payload, collection, documentId, schema) {
11
- const cache = new Map();
12
- return async (sourceLocale)=>{
13
- const cached = cache.get(sourceLocale);
14
- if (cached !== undefined) return cached;
15
- const sourceData = await fetchSourceDocument(payload, collection, documentId, sourceLocale);
16
- const fingerprint = computeSourceFingerprint(sourceData, schema);
17
- cache.set(sourceLocale, fingerprint);
18
- return fingerprint;
19
- };
20
- }
21
- /**
22
- * Per-locale staleness for one document: read every provenance record, recompute the current source
23
- * fingerprint, and mark each locale stale when the source drifted and the drift was not dismissed.
24
- * Returns `[]` when provenance is disabled or the collection has no schema — a normal condition, not
25
- * an error (genuine store/fetch failures propagate for the caller to log).
26
- */ export async function computeDocumentStaleness(payload, config, collection, documentId) {
27
- const schema = config.schemaMap.get(collection);
28
- if (!config.provenanceStoreFactory || !schema) return [];
29
- const store = config.provenanceStoreFactory(payload);
30
- const records = await store.findByDocument(collection, documentId);
31
- if (records.length === 0) return [];
32
- const currentFingerprint = makeCurrentFingerprint(payload, collection, documentId, schema);
33
- const locales = [];
34
- for (const record of records){
35
- try {
36
- const current = await currentFingerprint(record.sourceLocale);
37
- locales.push({
38
- target_lng: record.targetLocale,
39
- source_lng: record.sourceLocale,
40
- is_stale: isRecordStale(record, current),
41
- translated_at: record.translatedAt
42
- });
43
- } catch (error) {
44
- // Isolate per-locale failures (e.g. the record's source locale was removed from Payload's
45
- // localization config): skip just this locale so one bad record can't blank the staleness of
46
- // every other, still-valid locale on the document.
47
- payload.logger.error({
48
- err: error,
49
- collection,
50
- documentId,
51
- targetLocale: record.targetLocale,
52
- sourceLocale: record.sourceLocale,
53
- msg: "translator: failed to compute staleness for locale"
54
- });
55
- }
56
- }
57
- return locales;
58
- }
59
- /**
60
- * Acknowledge the current source drift for one target locale: recompute the current source
61
- * fingerprint (write-path-identical) and persist it as the dismissed fingerprint, so the indicator
62
- * hides until the source changes again. No-op when provenance is disabled or the locale has no record.
63
- */ export async function dismissLocaleStaleness(payload, config, collection, documentId, targetLocale) {
64
- const schema = config.schemaMap.get(collection);
65
- if (!config.provenanceStoreFactory || !schema) return;
66
- const store = config.provenanceStoreFactory(payload);
67
- const key = {
68
- collectionSlug: collection,
69
- documentId,
70
- targetLocale
71
- };
72
- const record = await store.find(key);
73
- if (!record) return;
74
- const currentFingerprint = makeCurrentFingerprint(payload, collection, documentId, schema);
75
- await store.dismiss(key, await currentFingerprint(record.sourceLocale));
76
- }
77
-
78
- //# sourceMappingURL=service.js.map