@focus-reactive/payload-plugin-translator 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/client/features/collection-translation-form/model/schema.d.ts +2 -2
  2. package/dist/client/features/translate-document-form/model/schema.d.ts +3 -3
  3. package/dist/server/features/cancel/handler.d.ts +2 -2
  4. package/dist/server/features/cancel/handler.js +3 -3
  5. package/dist/server/features/cancel-by-collection/handler.d.ts +3 -3
  6. package/dist/server/features/cancel-by-collection/handler.js +5 -5
  7. package/dist/server/features/enqueue-translation/handler.d.ts +3 -3
  8. package/dist/server/features/enqueue-translation/handler.js +4 -4
  9. package/dist/server/features/enqueue-translation/model.d.ts +4 -4
  10. package/dist/server/features/get-collection-status/handler.d.ts +3 -3
  11. package/dist/server/features/get-collection-status/handler.js +4 -4
  12. package/dist/server/features/get-document-status/handler.d.ts +3 -3
  13. package/dist/server/features/get-document-status/handler.js +4 -4
  14. package/dist/server/features/get-document-status/model.d.ts +1 -1
  15. package/dist/server/features/run-translation/handler.d.ts +2 -2
  16. package/dist/server/features/run-translation/handler.js +7 -7
  17. package/dist/server/features/translate-document/handler.d.ts +4 -4
  18. package/dist/server/features/translate-document/handler.js +4 -4
  19. package/dist/server/features/translate-document/index.d.ts +2 -3
  20. package/dist/server/features/translate-document/index.js +1 -2
  21. package/dist/server/features/translate-document/model.d.ts +4 -31
  22. package/dist/server/features/translate-document/model.js +3 -27
  23. package/dist/server/modules/task-runner/TaskRunnerProvider.interface.d.ts +6 -4
  24. package/dist/server/modules/task-runner/payload-jobs-runner/PayloadJobsRunnerProvider.d.ts +4 -4
  25. package/dist/server/modules/task-runner/payload-jobs-runner/PayloadJobsRunnerProvider.js +72 -20
  26. package/dist/server/modules/task-runner/payload-jobs-runner/PayloadJobsTaskRunner.d.ts +51 -56
  27. package/dist/server/modules/task-runner/payload-jobs-runner/PayloadJobsTaskRunner.js +146 -91
  28. package/dist/server/modules/task-runner/payload-jobs-runner/normalizeJob.d.ts +2 -2
  29. package/dist/server/modules/task-runner/payload-jobs-runner/normalizeJob.js +14 -12
  30. package/dist/server/modules/task-runner/payload-jobs-runner/readCollectionRef.d.ts +22 -0
  31. package/dist/server/modules/task-runner/payload-jobs-runner/readCollectionRef.js +22 -0
  32. package/dist/server/modules/task-runner/payload-jobs-runner/types.d.ts +30 -5
  33. package/dist/server/modules/task-runner/sync-runner/SyncTaskRunner.d.ts +5 -5
  34. package/dist/server/modules/task-runner/sync-runner/SyncTaskRunner.js +7 -6
  35. package/dist/server/modules/task-runner/types.d.ts +15 -5
  36. package/package.json +1 -1
  37. package/dist/server/features/translate-document/task.d.ts +0 -40
  38. package/dist/server/features/translate-document/task.js +0 -44
@@ -6,16 +6,16 @@ export declare const validationSchema: z.ZodObject<{
6
6
  strategy: z.ZodEnum<["overwrite", "skip_existing"]>;
7
7
  publish_on_translation: z.ZodDefault<z.ZodBoolean>;
8
8
  }, "strip", z.ZodTypeAny, {
9
+ collection_slug: string;
9
10
  source_lng: string;
10
11
  target_lng: string;
11
12
  strategy: "overwrite" | "skip_existing";
12
13
  publish_on_translation: boolean;
13
- collection_slug: string;
14
14
  }, {
15
+ collection_slug: string;
15
16
  source_lng: string;
16
17
  target_lng: string;
17
18
  strategy: "overwrite" | "skip_existing";
18
- collection_slug: string;
19
19
  publish_on_translation?: boolean | undefined;
20
20
  }>;
21
21
  export type FormValues = z.infer<typeof validationSchema>;
@@ -7,17 +7,17 @@ export declare const validationSchema: z.ZodObject<{
7
7
  strategy: z.ZodDefault<z.ZodEnum<["overwrite", "skip_existing"]>>;
8
8
  publish_on_translation: z.ZodDefault<z.ZodBoolean>;
9
9
  }, "strip", z.ZodTypeAny, {
10
+ collection_slug: string;
11
+ collection_id: string;
10
12
  source_lng: string;
11
13
  target_lng: string;
12
14
  strategy: "overwrite" | "skip_existing";
13
15
  publish_on_translation: boolean;
16
+ }, {
14
17
  collection_slug: string;
15
18
  collection_id: string;
16
- }, {
17
19
  source_lng: string;
18
20
  target_lng: string;
19
- collection_slug: string;
20
- collection_id: string;
21
21
  strategy?: "overwrite" | "skip_existing" | undefined;
22
22
  publish_on_translation?: boolean | undefined;
23
23
  }>;
@@ -1,5 +1,5 @@
1
- import type { PayloadRequest } from 'payload';
2
- import type { TaskRunnerProvider } from '../../modules/task-runner';
1
+ import type { PayloadRequest } from "payload";
2
+ import type { TaskRunnerProvider } from "../../modules/task-runner";
3
3
  /**
4
4
  * Cancels and deletes translation tasks by IDs
5
5
  */
@@ -1,5 +1,5 @@
1
- import { ServerResponse } from '../../shared';
2
- import { CancelInputSchema } from './model';
1
+ import { ServerResponse } from "../../shared";
2
+ import { CancelInputSchema } from "./model";
3
3
  /**
4
4
  * Cancels and deletes translation tasks by IDs
5
5
  */ export class CancelHandler {
@@ -9,7 +9,7 @@ import { CancelInputSchema } from './model';
9
9
  }
10
10
  async handle(req) {
11
11
  const validationResult = CancelInputSchema.safeParse(await req.json?.());
12
- if (validationResult.error) return ServerResponse.validationError(validationResult.error.errors);
12
+ if (validationResult.error) return ServerResponse.validationError(validationResult.error.issues);
13
13
  const { ids } = validationResult.data;
14
14
  const runner = this.taskRunnerFactory.create(req.payload);
15
15
  await runner.cancel(ids);
@@ -1,6 +1,6 @@
1
- import type { PayloadRequest } from 'payload';
2
- import type { TaskRunnerProvider } from '../../modules/task-runner';
3
- import { type CancelConfig } from './model';
1
+ import type { PayloadRequest } from "payload";
2
+ import type { TaskRunnerProvider } from "../../modules/task-runner";
3
+ import type { CancelConfig } from './model';
4
4
  /**
5
5
  * Cancels all pending translation tasks for a collection
6
6
  */
@@ -1,5 +1,5 @@
1
- import { ServerResponse } from '../../shared';
2
- import { isCollectionAvailable } from '../_lib/collection-utils';
1
+ import { ServerResponse } from "../../shared";
2
+ import { isCollectionAvailable } from "../_lib/collection-utils";
3
3
  import { CancelByCollectionInputSchema } from './model';
4
4
  /**
5
5
  * Cancels all pending translation tasks for a collection
@@ -12,13 +12,13 @@ import { CancelByCollectionInputSchema } from './model';
12
12
  }
13
13
  async handle(req) {
14
14
  const validationResult = CancelByCollectionInputSchema.safeParse(req.routeParams);
15
- if (validationResult.error) return ServerResponse.validationError(validationResult.error.errors);
15
+ if (validationResult.error) return ServerResponse.validationError(validationResult.error.issues);
16
16
  const collectionSlug = isCollectionAvailable(validationResult.data.collection_slug, this.config.availableCollections);
17
- if (!collectionSlug) return ServerResponse.badRequest('Collection not available for translation');
17
+ if (!collectionSlug) return ServerResponse.badRequest("Collection not available for translation");
18
18
  const runner = this.taskRunnerFactory.create(req.payload);
19
19
  const tasks = await runner.findByCollection(collectionSlug);
20
20
  if (tasks.length === 0) return ServerResponse.noContent();
21
- const pendingTaskIds = tasks.filter((task)=>task.status === 'pending').map((task)=>task.id);
21
+ const pendingTaskIds = tasks.filter((task)=>task.status === "pending").map((task)=>task.id);
22
22
  if (pendingTaskIds.length === 0) return ServerResponse.noContent();
23
23
  await runner.cancel(pendingTaskIds);
24
24
  return ServerResponse.noContent();
@@ -1,6 +1,6 @@
1
- import type { PayloadRequest } from 'payload';
2
- import type { TaskRunnerProvider } from '../../modules/task-runner';
3
- import { type EnqueueConfig } from './model';
1
+ import type { PayloadRequest } from "payload";
2
+ import type { TaskRunnerProvider } from "../../modules/task-runner";
3
+ import type { EnqueueConfig } from './model';
4
4
  /**
5
5
  * Enqueues translation tasks for documents
6
6
  */
@@ -1,5 +1,5 @@
1
- import { ServerResponse } from '../../shared';
2
- import { isCollectionAvailable, getAllCollectionIds } from '../_lib/collection-utils';
1
+ import { ServerResponse } from "../../shared";
2
+ import { isCollectionAvailable, getAllCollectionIds } from "../_lib/collection-utils";
3
3
  import { EnqueueInputSchema } from './model';
4
4
  /**
5
5
  * Enqueues translation tasks for documents
@@ -12,10 +12,10 @@ import { EnqueueInputSchema } from './model';
12
12
  }
13
13
  async handle(req) {
14
14
  const validationResult = EnqueueInputSchema.safeParse(await req.json?.());
15
- if (validationResult.error) return ServerResponse.validationError(validationResult.error.errors);
15
+ if (validationResult.error) return ServerResponse.validationError(validationResult.error.issues);
16
16
  const { source_lng, target_lng, collection_slug, collection_id, select_all, strategy, publish_on_translation } = validationResult.data;
17
17
  const collectionSlug = isCollectionAvailable(collection_slug, this.config.availableCollections);
18
- if (!collectionSlug) return ServerResponse.badRequest('Content of this collection is not available for translation');
18
+ if (!collectionSlug) return ServerResponse.badRequest("Content of this collection is not available for translation");
19
19
  const collectionIds = select_all ? await getAllCollectionIds(req.payload, collectionSlug) : collection_id;
20
20
  const runner = this.taskRunnerFactory.create(req.payload);
21
21
  const tasks = collectionIds.map((id)=>({
@@ -12,18 +12,18 @@ export declare const EnqueueInputSchema: z.ZodObject<{
12
12
  strategy: z.ZodDefault<z.ZodOptional<z.ZodEnum<["overwrite", "skip_existing"]>>>;
13
13
  publish_on_translation: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
14
14
  }, "strip", z.ZodTypeAny, {
15
+ collection_slug: string;
16
+ collection_id: [string, ...string[]];
15
17
  source_lng: string;
16
18
  target_lng: string;
17
19
  strategy: "overwrite" | "skip_existing";
18
20
  publish_on_translation: boolean;
19
- collection_slug: string;
20
- collection_id: [string, ...string[]];
21
21
  select_all?: boolean | undefined;
22
22
  }, {
23
- source_lng: string;
24
- target_lng: string;
25
23
  collection_slug: string;
26
24
  collection_id: [string, ...string[]];
25
+ source_lng: string;
26
+ target_lng: string;
27
27
  strategy?: "overwrite" | "skip_existing" | undefined;
28
28
  publish_on_translation?: boolean | undefined;
29
29
  select_all?: boolean | undefined;
@@ -1,6 +1,6 @@
1
- import type { PayloadRequest } from 'payload';
2
- import type { TaskRunnerProvider } from '../../modules/task-runner';
3
- import { type GetCollectionStatusConfig } from './model';
1
+ import type { PayloadRequest } from "payload";
2
+ import type { TaskRunnerProvider } from "../../modules/task-runner";
3
+ import type { GetCollectionStatusConfig } from './model';
4
4
  /**
5
5
  * Gets translation status for all documents in a collection
6
6
  */
@@ -1,5 +1,5 @@
1
- import { ServerResponse } from '../../shared';
2
- import { isCollectionAvailable } from '../_lib/collection-utils';
1
+ import { ServerResponse } from "../../shared";
2
+ import { isCollectionAvailable } from "../_lib/collection-utils";
3
3
  import { GetCollectionStatusInputSchema } from './model';
4
4
  /**
5
5
  * Gets translation status for all documents in a collection
@@ -12,9 +12,9 @@ import { GetCollectionStatusInputSchema } from './model';
12
12
  }
13
13
  async handle(req) {
14
14
  const validationResult = GetCollectionStatusInputSchema.safeParse(req.routeParams);
15
- if (validationResult.error) return ServerResponse.validationError(validationResult.error.errors);
15
+ if (validationResult.error) return ServerResponse.validationError(validationResult.error.issues);
16
16
  const collectionSlug = isCollectionAvailable(validationResult.data.collection_slug, this.config.availableCollections);
17
- if (!collectionSlug) return ServerResponse.badRequest('Collection not available for translation');
17
+ if (!collectionSlug) return ServerResponse.badRequest("Collection not available for translation");
18
18
  const runner = this.taskRunnerFactory.create(req.payload);
19
19
  const tasks = await runner.findByCollection(collectionSlug);
20
20
  return ServerResponse.success({
@@ -1,6 +1,6 @@
1
- import type { PayloadRequest } from 'payload';
2
- import type { TaskRunnerProvider } from '../../modules/task-runner';
3
- import { type GetDocumentStatusConfig } from './model';
1
+ import type { PayloadRequest } from "payload";
2
+ import type { TaskRunnerProvider } from "../../modules/task-runner";
3
+ import type { GetDocumentStatusConfig } from './model';
4
4
  /**
5
5
  * Gets the translation status for a specific document
6
6
  */
@@ -1,5 +1,5 @@
1
- import { ServerResponse } from '../../shared';
2
- import { isCollectionAvailable } from '../_lib/collection-utils';
1
+ import { ServerResponse } from "../../shared";
2
+ import { isCollectionAvailable } from "../_lib/collection-utils";
3
3
  import { GetDocumentStatusInputSchema, taskToJobStatusOutput } from './model';
4
4
  /**
5
5
  * Gets the translation status for a specific document
@@ -12,10 +12,10 @@ import { GetDocumentStatusInputSchema, taskToJobStatusOutput } from './model';
12
12
  }
13
13
  async handle(req) {
14
14
  const validationResult = GetDocumentStatusInputSchema.safeParse(req.routeParams);
15
- if (validationResult.error) return ServerResponse.validationError(validationResult.error.errors);
15
+ if (validationResult.error) return ServerResponse.validationError(validationResult.error.issues);
16
16
  const { collection_slug, collection_id } = validationResult.data;
17
17
  const collectionSlug = isCollectionAvailable(collection_slug, this.config.availableCollections);
18
- if (!collectionSlug) return ServerResponse.badRequest('Collection not available for translation');
18
+ if (!collectionSlug) return ServerResponse.badRequest("Collection not available for translation");
19
19
  const runner = this.taskRunnerFactory.create(req.payload);
20
20
  const tasks = await runner.findByCollection(collectionSlug, [
21
21
  collection_id
@@ -29,7 +29,7 @@ export type TranslationTaskStatus = TaskStatus;
29
29
  export type JobInputOutput = {
30
30
  collection: {
31
31
  relationTo: CollectionSlug;
32
- value: string | number;
32
+ value: string;
33
33
  };
34
34
  source_lng: string;
35
35
  target_lng: string;
@@ -1,5 +1,5 @@
1
- import type { PayloadRequest } from 'payload';
2
- import type { TaskRunnerProvider } from '../../modules/task-runner';
1
+ import type { PayloadRequest } from "payload";
2
+ import type { TaskRunnerProvider } from "../../modules/task-runner";
3
3
  /**
4
4
  * Runs a translation task by ID
5
5
  */
@@ -1,5 +1,5 @@
1
- import { ServerResponse } from '../../shared';
2
- import { RunInputSchema } from './model';
1
+ import { ServerResponse } from "../../shared";
2
+ import { RunInputSchema } from "./model";
3
3
  /**
4
4
  * Runs a translation task by ID
5
5
  */ export class RunTranslationHandler {
@@ -9,16 +9,16 @@ import { RunInputSchema } from './model';
9
9
  }
10
10
  async handle(req) {
11
11
  const validationResult = RunInputSchema.safeParse(req.routeParams);
12
- if (validationResult.error) return ServerResponse.validationError(validationResult.error.errors);
12
+ if (validationResult.error) return ServerResponse.validationError(validationResult.error.issues);
13
13
  const { id } = validationResult.data;
14
14
  const runner = this.taskRunnerFactory.create(req.payload);
15
15
  const result = await runner.run(id);
16
16
  if (!result.success) {
17
- if (result.error === 'not_found' || result.error === 'already_completed') {
18
- return ServerResponse.notFound('Queued task not found');
17
+ if (result.error === "not_found" || result.error === "already_completed") {
18
+ return ServerResponse.notFound("Queued task not found");
19
19
  }
20
- if (result.error === 'already_running') {
21
- return ServerResponse.tooManyRequests('Translation task is already in progress');
20
+ if (result.error === "already_running") {
21
+ return ServerResponse.tooManyRequests("Translation task is already in progress");
22
22
  }
23
23
  }
24
24
  return ServerResponse.noContent();
@@ -1,7 +1,7 @@
1
- import type { Payload } from 'payload';
2
- import type { Handler } from '../../shared';
3
- import type { TranslationProvider } from '../../modules/translation-providers';
4
- import type { CollectionSchemaMap, TranslateDocumentInput, TranslateDocumentOutput } from './model';
1
+ import type { Payload } from "payload";
2
+ import type { Handler } from "../../shared";
3
+ import type { TranslationProvider } from "../../modules/translation-providers";
4
+ import type { CollectionSchemaMap, TranslateDocumentInput, TranslateDocumentOutput } from "./model";
5
5
  export type TranslateDocumentDependencies = {
6
6
  translationProvider: TranslationProvider;
7
7
  schemaMap: CollectionSchemaMap;
@@ -1,6 +1,6 @@
1
- import { APIError } from 'payload';
2
- import { TranslationPipeline } from '../../modules/translation-pipeline';
3
- import { createTranslationStrategy } from '../../modules/translation-pipeline/strategies';
1
+ import { APIError } from "payload";
2
+ import { TranslationPipeline } from "../../modules/translation-pipeline";
3
+ import { createTranslationStrategy } from "../../modules/translation-pipeline/strategies";
4
4
  /**
5
5
  * Translates a single document from source language to target language
6
6
  */ export class TranslateDocumentHandler {
@@ -53,7 +53,7 @@ import { createTranslationStrategy } from '../../modules/translation-pipeline/st
53
53
  let isAutosaveEnabled = false;
54
54
  const versions = collectionConfig.versions;
55
55
  if (versions && versions.drafts) {
56
- translatedData['_status'] = publishOnTranslation ? 'published' : 'draft';
56
+ translatedData["_status"] = publishOnTranslation ? "published" : "draft";
57
57
  const drafts = versions.drafts;
58
58
  if (!publishOnTranslation && drafts.autosave) isAutosaveEnabled = true;
59
59
  }
@@ -1,3 +1,2 @@
1
- export { createTranslateDocumentTask, createAutoRunConfig, TASK_NAME, QUEUE_NAME } from './task';
2
- export { TranslateDocumentHandler } from './handler';
3
- export type { CollectionSchemaMap, AutoRunConfig } from './model';
1
+ export { TranslateDocumentHandler } from "./handler";
2
+ export type { CollectionSchemaMap } from "./model";
@@ -1,4 +1,3 @@
1
- export { createTranslateDocumentTask, createAutoRunConfig, TASK_NAME, QUEUE_NAME } from './task';
2
- export { TranslateDocumentHandler } from './handler';
1
+ export { TranslateDocumentHandler } from "./handler";
3
2
 
4
3
  //# sourceMappingURL=index.js.map
@@ -1,11 +1,12 @@
1
- import type { CollectionSlug, Field } from 'payload';
2
- import type { TranslationStrategyName } from '../../modules/translation-pipeline/strategies';
1
+ import type { CollectionSlug, Field } from "payload";
2
+ import type { ID } from "../../modules/task-runner/types";
3
+ import type { TranslationStrategyName } from "../../modules/translation-pipeline/strategies";
3
4
  /**
4
5
  * Input for the translate document handler
5
6
  */
6
7
  export type TranslateDocumentInput = {
7
8
  collection: CollectionSlug;
8
- collectionId: string | number;
9
+ collectionId: ID;
9
10
  sourceLng: string;
10
11
  targetLng: string;
11
12
  strategy: TranslationStrategyName;
@@ -22,31 +23,3 @@ export type TranslateDocumentOutput = {
22
23
  * Used to access original schemas before Payload sanitization.
23
24
  */
24
25
  export type CollectionSchemaMap = Map<CollectionSlug, Field[]>;
25
- /**
26
- * Payload task configuration
27
- */
28
- export type TaskConfig = {
29
- collections: CollectionSlug[];
30
- retries?: {
31
- attempts?: number;
32
- backoff?: {
33
- delay?: number;
34
- type: 'exponential' | 'fixed';
35
- };
36
- };
37
- };
38
- /**
39
- * Auto-run configuration for the job queue
40
- */
41
- export type AutoRunConfig = {
42
- cron?: string;
43
- limit?: number;
44
- queue?: string;
45
- };
46
- /**
47
- * Payload input schema for the task
48
- */
49
- export declare const taskInputSchema: Field[];
50
- export declare const TASK_NAME: "translate_document";
51
- export declare const QUEUE_NAME: "translations";
52
- export declare const DEFAULT_AUTO_RUN: AutoRunConfig;
@@ -1,30 +1,6 @@
1
1
  /**
2
- * Payload input schema for the task
3
- */ export const taskInputSchema = [
4
- {
5
- type: 'relationship',
6
- name: 'collection',
7
- relationTo: [],
8
- required: true
9
- },
10
- {
11
- type: 'text',
12
- maxLength: 256,
13
- name: 'source_lng',
14
- required: true
15
- },
16
- {
17
- type: 'text',
18
- maxLength: 256,
19
- name: 'target_lng',
20
- required: true
21
- }
22
- ];
23
- export const TASK_NAME = 'translate_document';
24
- export const QUEUE_NAME = 'translations';
25
- export const DEFAULT_AUTO_RUN = {
26
- cron: '* * * * *',
27
- limit: 50
28
- };
2
+ * Map of collection slug to original field schema.
3
+ * Used to access original schemas before Payload sanitization.
4
+ */ export { };
29
5
 
30
6
  //# sourceMappingURL=model.js.map
@@ -1,12 +1,13 @@
1
- import type { CollectionSlug, Config, Payload } from 'payload';
2
- import type { TaskRunner } from './TaskRunner.interface';
3
- import type { TranslationStrategyName } from '../translation-pipeline/strategies';
1
+ import type { CollectionSlug, Config, Payload } from "payload";
2
+ import type { TaskRunner } from "./TaskRunner.interface";
3
+ import type { ID } from "./types";
4
+ import type { TranslationStrategyName } from "../translation-pipeline/strategies";
4
5
  /**
5
6
  * Input for task handler callback
6
7
  */
7
8
  export type TaskHandlerInput = {
8
9
  collection: CollectionSlug;
9
- collectionId: string | number;
10
+ collectionId: ID;
10
11
  sourceLng: string;
11
12
  targetLng: string;
12
13
  strategy: TranslationStrategyName;
@@ -40,6 +41,7 @@ export interface TaskRunnerProvider {
40
41
  /**
41
42
  * Configures the runner and returns a Payload config modifier.
42
43
  * The modifier adds necessary tasks, jobs, queues to Payload config.
44
+ * Implementations may also install a `config.onInit` hook for boot-time initialization (e.g. stale-lock recovery).
43
45
  */
44
46
  configure(context: TaskRunnerContext): (config: Config) => Config;
45
47
  }
@@ -1,7 +1,7 @@
1
- import type { Config, Payload } from 'payload';
2
- import type { TaskRunner } from '../TaskRunner.interface';
3
- import type { PayloadJobsRunnerOptions } from './types';
4
- import type { TaskRunnerContext, TaskRunnerProvider } from '../TaskRunnerProvider.interface';
1
+ import type { Config, Payload } from "payload";
2
+ import type { TaskRunner } from "../TaskRunner.interface";
3
+ import type { PayloadJobsRunnerOptions } from "./types";
4
+ import type { TaskRunnerContext, TaskRunnerProvider } from "../TaskRunnerProvider.interface";
5
5
  /**
6
6
  * TaskRunnerProvider implementation using Payload Jobs.
7
7
  *
@@ -1,17 +1,20 @@
1
- import { PayloadJobsTaskRunner } from './PayloadJobsTaskRunner';
1
+ import { PayloadJobsTaskRunner } from "./PayloadJobsTaskRunner";
2
+ import { readCollectionRef } from "./readCollectionRef";
2
3
  const defaultAutoRun = {
3
- cron: '* * * * *',
4
+ cron: "* * * * *",
4
5
  limit: 50
5
6
  };
7
+ const DEFAULT_STALE_JOB_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
6
8
  const defaultValues = {
7
- taskName: 'translate_document',
8
- queueName: 'translations',
9
- jobsCollection: 'payload-jobs',
9
+ taskName: "translate_document",
10
+ queueName: "translations",
11
+ jobsCollection: "payload-jobs",
10
12
  autoRun: defaultAutoRun,
13
+ staleJobTimeoutMs: DEFAULT_STALE_JOB_TIMEOUT_MS,
11
14
  retries: {
12
15
  attempts: 3,
13
16
  backoff: {
14
- type: 'exponential',
17
+ type: "exponential",
15
18
  delay: 5000
16
19
  }
17
20
  }
@@ -27,11 +30,16 @@ const defaultValues = {
27
30
  ...defaultAutoRun,
28
31
  ...options.autoRun
29
32
  } : defaultAutoRun;
33
+ const staleJobTimeoutMs = options?.staleJobTimeoutMs ?? defaultValues.staleJobTimeoutMs;
34
+ if (!Number.isFinite(staleJobTimeoutMs) || staleJobTimeoutMs <= 0) {
35
+ throw new Error(`[payload-plugin-translator] staleJobTimeoutMs must be a positive finite number (got ${staleJobTimeoutMs})`);
36
+ }
30
37
  this.config = {
31
38
  taskName: options?.taskName ?? defaultValues.taskName,
32
39
  queueName: options?.queueName ?? defaultValues.queueName,
33
40
  jobsCollection: options?.jobsCollection ?? defaultValues.jobsCollection,
34
41
  autoRun,
42
+ staleJobTimeoutMs,
35
43
  retries: options?.retries ?? defaultValues.retries
36
44
  };
37
45
  }
@@ -43,33 +51,57 @@ const defaultValues = {
43
51
  const { handler, collections } = context;
44
52
  return (config)=>{
45
53
  const inputSchema = [
54
+ // Flat text reference (ID-agnostic). Current shape that jobs are
55
+ // written with — no relationship type validation against the target
56
+ // collection's ID type, so string IDs work for number-id collections.
46
57
  {
47
- type: 'relationship',
48
- name: 'collection',
49
- relationTo: collections,
58
+ type: "text",
59
+ name: "collection_slug",
60
+ required: true
61
+ },
62
+ {
63
+ type: "text",
64
+ name: "collection_id",
50
65
  required: true
51
66
  },
67
+ /**
68
+ * Legacy relationship reference, kept as a read-only fallback so jobs
69
+ * queued before the ID-agnostic migration stay readable. No longer
70
+ * written; demoted to `required: false` so new jobs (which omit it)
71
+ * pass validation. Removed in the next major.
72
+ * See docs/DEPRECATIONS.md#jobs-input-collection-field
73
+ * @deprecated
74
+ */ {
75
+ type: "relationship",
76
+ name: "collection",
77
+ relationTo: collections,
78
+ required: false,
79
+ admin: {
80
+ readOnly: true,
81
+ description: "Deprecated. See docs/DEPRECATIONS.md#jobs-input-collection-field"
82
+ }
83
+ },
52
84
  {
53
- type: 'text',
85
+ type: "text",
54
86
  maxLength: 256,
55
- name: 'source_lng',
87
+ name: "source_lng",
56
88
  required: true
57
89
  },
58
90
  {
59
- type: 'text',
91
+ type: "text",
60
92
  maxLength: 256,
61
- name: 'target_lng',
93
+ name: "target_lng",
62
94
  required: true
63
95
  },
64
96
  {
65
- type: 'text',
97
+ type: "text",
66
98
  maxLength: 256,
67
- name: 'strategy',
99
+ name: "strategy",
68
100
  required: true
69
101
  },
70
102
  {
71
- type: 'checkbox',
72
- name: 'publish_on_translation',
103
+ type: "checkbox",
104
+ name: "publish_on_translation",
73
105
  defaultValue: false
74
106
  }
75
107
  ];
@@ -78,9 +110,10 @@ const defaultValues = {
78
110
  inputSchema,
79
111
  retries,
80
112
  handler: async (args)=>{
113
+ const { collectionSlug, collectionId } = readCollectionRef(args.input);
81
114
  await handler(args.req.payload, {
82
- collection: args.input.collection.relationTo,
83
- collectionId: args.input.collection.value,
115
+ collection: collectionSlug,
116
+ collectionId,
84
117
  sourceLng: args.input.source_lng,
85
118
  targetLng: args.input.target_lng,
86
119
  strategy: args.input.strategy,
@@ -104,7 +137,7 @@ const defaultValues = {
104
137
  const existingAutoRun = config.jobs.autoRun;
105
138
  if (Array.isArray(existingAutoRun)) {
106
139
  existingAutoRun.push(autoRunConfig);
107
- } else if (typeof existingAutoRun === 'function') {
140
+ } else if (typeof existingAutoRun === "function") {
108
141
  config.jobs.autoRun = async (payload)=>[
109
142
  ...await existingAutoRun(payload),
110
143
  autoRunConfig
@@ -115,6 +148,25 @@ const defaultValues = {
115
148
  ];
116
149
  }
117
150
  }
151
+ // Reset stale locks on boot so jobs abandoned by a killed process
152
+ // (deploy/crash/timeout) become eligible for the autorun picker again.
153
+ // The picker requires processing:false, no error, and no pending waitUntil;
154
+ // a mid-run casualty (no error, no waitUntil) satisfies the rest, so
155
+ // clearing processing is sufficient for that case. Threshold-based, so a
156
+ // job genuinely in flight on another live instance (fresh updatedAt) is
157
+ // left alone. Wrapped so a reclaim failure never blocks startup.
158
+ const existingOnInit = config.onInit;
159
+ config.onInit = async (payload)=>{
160
+ if (existingOnInit) await existingOnInit(payload);
161
+ try {
162
+ await new PayloadJobsTaskRunner(payload, this.config).reclaimStaleJobs();
163
+ } catch (err) {
164
+ payload.logger?.error?.({
165
+ err,
166
+ msg: "[translator] failed to reclaim stale translation jobs"
167
+ });
168
+ }
169
+ };
118
170
  return config;
119
171
  };
120
172
  }