@focus-reactive/payload-plugin-translator 0.2.0 → 0.3.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.
- package/dist/client/entities/translation/api/queries/useCollectionTranslationStatus.d.ts +77 -13
- package/dist/client/entities/translation/api/queries/useDocumentTranslation.d.ts +77 -13
- package/dist/client/features/collection-translation-form/model/schema.d.ts +2 -2
- package/dist/client/features/translate-document-form/model/schema.d.ts +3 -3
- package/dist/server/features/enqueue-translation/model.d.ts +4 -4
- package/dist/server/features/get-document-status/model.d.ts +1 -1
- package/dist/server/features/translate-document/handler.d.ts +4 -4
- package/dist/server/features/translate-document/handler.js +4 -4
- package/dist/server/features/translate-document/index.d.ts +2 -3
- package/dist/server/features/translate-document/index.js +1 -2
- package/dist/server/features/translate-document/model.d.ts +4 -31
- package/dist/server/features/translate-document/model.js +3 -27
- package/dist/server/modules/task-runner/TaskRunnerProvider.interface.d.ts +5 -4
- package/dist/server/modules/task-runner/payload-jobs-runner/PayloadJobsRunnerProvider.d.ts +4 -4
- package/dist/server/modules/task-runner/payload-jobs-runner/PayloadJobsRunnerProvider.js +46 -20
- package/dist/server/modules/task-runner/payload-jobs-runner/PayloadJobsTaskRunner.d.ts +22 -56
- package/dist/server/modules/task-runner/payload-jobs-runner/PayloadJobsTaskRunner.js +45 -85
- package/dist/server/modules/task-runner/payload-jobs-runner/normalizeJob.d.ts +2 -2
- package/dist/server/modules/task-runner/payload-jobs-runner/normalizeJob.js +14 -12
- package/dist/server/modules/task-runner/payload-jobs-runner/readCollectionRef.d.ts +22 -0
- package/dist/server/modules/task-runner/payload-jobs-runner/readCollectionRef.js +22 -0
- package/dist/server/modules/task-runner/payload-jobs-runner/types.d.ts +13 -5
- package/dist/server/modules/task-runner/sync-runner/SyncTaskRunner.d.ts +5 -5
- package/dist/server/modules/task-runner/sync-runner/SyncTaskRunner.js +7 -6
- package/dist/server/modules/task-runner/types.d.ts +15 -5
- package/package.json +1 -1
- package/dist/server/features/translate-document/task.d.ts +0 -40
- package/dist/server/features/translate-document/task.js +0 -44
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import { PayloadJobsTaskRunner } from
|
|
1
|
+
import { PayloadJobsTaskRunner } from "./PayloadJobsTaskRunner";
|
|
2
|
+
import { readCollectionRef } from "./readCollectionRef";
|
|
2
3
|
const defaultAutoRun = {
|
|
3
|
-
cron:
|
|
4
|
+
cron: "* * * * *",
|
|
4
5
|
limit: 50
|
|
5
6
|
};
|
|
6
7
|
const defaultValues = {
|
|
7
|
-
taskName:
|
|
8
|
-
queueName:
|
|
9
|
-
jobsCollection:
|
|
8
|
+
taskName: "translate_document",
|
|
9
|
+
queueName: "translations",
|
|
10
|
+
jobsCollection: "payload-jobs",
|
|
10
11
|
autoRun: defaultAutoRun,
|
|
11
12
|
retries: {
|
|
12
13
|
attempts: 3,
|
|
13
14
|
backoff: {
|
|
14
|
-
type:
|
|
15
|
+
type: "exponential",
|
|
15
16
|
delay: 5000
|
|
16
17
|
}
|
|
17
18
|
}
|
|
@@ -43,33 +44,57 @@ const defaultValues = {
|
|
|
43
44
|
const { handler, collections } = context;
|
|
44
45
|
return (config)=>{
|
|
45
46
|
const inputSchema = [
|
|
47
|
+
// Flat text reference (ID-agnostic). Current shape that jobs are
|
|
48
|
+
// written with — no relationship type validation against the target
|
|
49
|
+
// collection's ID type, so string IDs work for number-id collections.
|
|
46
50
|
{
|
|
47
|
-
type:
|
|
48
|
-
name:
|
|
49
|
-
|
|
51
|
+
type: "text",
|
|
52
|
+
name: "collection_slug",
|
|
53
|
+
required: true
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
type: "text",
|
|
57
|
+
name: "collection_id",
|
|
50
58
|
required: true
|
|
51
59
|
},
|
|
60
|
+
/**
|
|
61
|
+
* Legacy relationship reference, kept as a read-only fallback so jobs
|
|
62
|
+
* queued before the ID-agnostic migration stay readable. No longer
|
|
63
|
+
* written; demoted to `required: false` so new jobs (which omit it)
|
|
64
|
+
* pass validation. Removed in the next major.
|
|
65
|
+
* See docs/DEPRECATIONS.md#jobs-input-collection-field
|
|
66
|
+
* @deprecated
|
|
67
|
+
*/ {
|
|
68
|
+
type: "relationship",
|
|
69
|
+
name: "collection",
|
|
70
|
+
relationTo: collections,
|
|
71
|
+
required: false,
|
|
72
|
+
admin: {
|
|
73
|
+
readOnly: true,
|
|
74
|
+
description: "Deprecated. See docs/DEPRECATIONS.md#jobs-input-collection-field"
|
|
75
|
+
}
|
|
76
|
+
},
|
|
52
77
|
{
|
|
53
|
-
type:
|
|
78
|
+
type: "text",
|
|
54
79
|
maxLength: 256,
|
|
55
|
-
name:
|
|
80
|
+
name: "source_lng",
|
|
56
81
|
required: true
|
|
57
82
|
},
|
|
58
83
|
{
|
|
59
|
-
type:
|
|
84
|
+
type: "text",
|
|
60
85
|
maxLength: 256,
|
|
61
|
-
name:
|
|
86
|
+
name: "target_lng",
|
|
62
87
|
required: true
|
|
63
88
|
},
|
|
64
89
|
{
|
|
65
|
-
type:
|
|
90
|
+
type: "text",
|
|
66
91
|
maxLength: 256,
|
|
67
|
-
name:
|
|
92
|
+
name: "strategy",
|
|
68
93
|
required: true
|
|
69
94
|
},
|
|
70
95
|
{
|
|
71
|
-
type:
|
|
72
|
-
name:
|
|
96
|
+
type: "checkbox",
|
|
97
|
+
name: "publish_on_translation",
|
|
73
98
|
defaultValue: false
|
|
74
99
|
}
|
|
75
100
|
];
|
|
@@ -78,9 +103,10 @@ const defaultValues = {
|
|
|
78
103
|
inputSchema,
|
|
79
104
|
retries,
|
|
80
105
|
handler: async (args)=>{
|
|
106
|
+
const { collectionSlug, collectionId } = readCollectionRef(args.input);
|
|
81
107
|
await handler(args.req.payload, {
|
|
82
|
-
collection:
|
|
83
|
-
collectionId
|
|
108
|
+
collection: collectionSlug,
|
|
109
|
+
collectionId,
|
|
84
110
|
sourceLng: args.input.source_lng,
|
|
85
111
|
targetLng: args.input.target_lng,
|
|
86
112
|
strategy: args.input.strategy,
|
|
@@ -104,7 +130,7 @@ const defaultValues = {
|
|
|
104
130
|
const existingAutoRun = config.jobs.autoRun;
|
|
105
131
|
if (Array.isArray(existingAutoRun)) {
|
|
106
132
|
existingAutoRun.push(autoRunConfig);
|
|
107
|
-
} else if (typeof existingAutoRun ===
|
|
133
|
+
} else if (typeof existingAutoRun === "function") {
|
|
108
134
|
config.jobs.autoRun = async (payload)=>[
|
|
109
135
|
...await existingAutoRun(payload),
|
|
110
136
|
autoRunConfig
|
|
@@ -17,78 +17,44 @@ export declare class PayloadJobsTaskRunner implements TaskRunner {
|
|
|
17
17
|
/**
|
|
18
18
|
* Find translation jobs for a collection, optionally narrowed by document IDs.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* where: {
|
|
26
|
-
* and: [
|
|
27
|
-
* { 'input.collection.relationTo': { equals: collectionSlug } },
|
|
28
|
-
* { 'input.collection.value': { in: documentIds } },
|
|
29
|
-
* ],
|
|
30
|
-
* }
|
|
20
|
+
* Narrowing is by `taskSlug` only in SQL; the collection slug and document
|
|
21
|
+
* IDs are matched in memory (via the normalized `Task`, which reads both the
|
|
22
|
+
* current flat-text shape and the legacy relationship shape). This is the
|
|
23
|
+
* one path that must transparently span both stored shapes during the
|
|
24
|
+
* ID-agnostic migration — see docs/DEPRECATIONS.md#jobs-input-collection-field.
|
|
31
25
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
26
|
+
* IMPORTANT — why slug/id are matched in memory, not in the SQL WHERE
|
|
27
|
+
* ------------------------------------------------------------------------
|
|
28
|
+
* The "natural" implementation would push `input.collection_id` into the
|
|
29
|
+
* where clause. This DOES NOT work on SQLite (and is unreliable on any
|
|
30
|
+
* adapter) because of two compounding bugs in Payload's drizzle layer.
|
|
34
31
|
*
|
|
35
32
|
* 1. The `input` field on `payload-jobs` is declared `type: 'json'`. The
|
|
36
33
|
* drizzle path resolver (`@payloadcms/drizzle/queries/getTableColumnFromPath`)
|
|
37
34
|
* has no `case 'json'` branch, so the value is left as a raw column and
|
|
38
35
|
* the path segments are passed through to `parseParams.js`, which on
|
|
39
36
|
* SQLite builds raw SQL using `convertPathToJSONTraversal` — generating
|
|
40
|
-
* expressions like `input->>'
|
|
37
|
+
* expressions like `input->>'collection_id'`.
|
|
41
38
|
*
|
|
42
39
|
* 2. When `parseParams.js` formats the right-hand side of `in`/`not_in`
|
|
43
40
|
* (and even `equals` when `!isNaN(val)`), it inlines values via JS
|
|
44
41
|
* template literals WITHOUT wrapping strings in quotes. The string
|
|
45
42
|
* `'1'` from our WHERE becomes raw `1` in the SQL. Drizzle therefore
|
|
46
|
-
* emits queries like
|
|
47
|
-
*
|
|
48
|
-
* WHERE input->>'collection'->>'value' IN (1)
|
|
49
|
-
*
|
|
50
|
-
* even though the caller passed `['1']` (an array of strings).
|
|
43
|
+
* emits queries like `WHERE input->>'collection_id' IN (1)` even though
|
|
44
|
+
* the caller passed `['1']` (an array of strings).
|
|
51
45
|
*
|
|
52
|
-
* On SQLite, `->>` preserves the JSON value's type
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* TEXT '1' IN (1) → false (text vs integer, no match)
|
|
58
|
-
* INTEGER 1 IN ('1') → false (integer vs text, no match)
|
|
59
|
-
*
|
|
60
|
-
* Combined with bug #2 above, any value passed by the caller — even if
|
|
61
|
-
* we normalize it to a string on write — gets re-coerced to a number in
|
|
62
|
-
* the generated SQL and never matches the stored JSON.
|
|
63
|
-
*
|
|
64
|
-
* Postgres avoids most of this because `jsonb_path_query` returns text
|
|
65
|
-
* uniformly and PG's type coercion is more permissive, but the same
|
|
66
|
-
* un-quoted-string bug technically affects it too.
|
|
67
|
-
*
|
|
68
|
-
* Why we don't fix it upstream / patch the dep
|
|
69
|
-
* --------------------------------------------
|
|
70
|
-
* - This plugin is published to npm. Consumers install it with their own
|
|
71
|
-
* Payload version and would not receive any local `bun patch` /
|
|
72
|
-
* `patch-package` overrides on `@payloadcms/drizzle`. The plugin must
|
|
73
|
-
* work against vanilla Payload.
|
|
74
|
-
* - A PR to Payload core is the proper long-term fix, but the plugin
|
|
75
|
-
* cannot block on its merge/release cycle.
|
|
76
|
-
* - Forcing a non-numeric prefix on the stored ID (e.g., `"id:1"`) would
|
|
77
|
-
* work around bug #2, but bloats the data shape and breaks anything
|
|
78
|
-
* that reads `input.collection.value` expecting a plain id.
|
|
46
|
+
* On SQLite, `->>` preserves the JSON value's type and `IN (...)` does NOT
|
|
47
|
+
* coerce between TEXT and INTEGER, so a numeric-looking string id never
|
|
48
|
+
* matches once bug #2 strips its quotes. Storing the id as text (this
|
|
49
|
+
* migration) does not fix the SQL path — drizzle re-numbers it anyway — so
|
|
50
|
+
* we keep matching in memory.
|
|
79
51
|
*
|
|
80
52
|
* Why in-memory filtering is acceptable here
|
|
81
53
|
* ------------------------------------------
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* jobs so they don't accumulate), so a Set-membership check in JS is
|
|
87
|
-
* effectively free.
|
|
88
|
-
*
|
|
89
|
-
* If/when the upstream drizzle bug is fixed (or this plugin gains a
|
|
90
|
-
* mirror collection with indexed flat columns), this method can collapse
|
|
91
|
-
* back to a single SQL query.
|
|
54
|
+
* Per-task job sets are small (typically <100 rows; the plugin actively
|
|
55
|
+
* cancels superseded jobs so they don't accumulate), so the JS filtering
|
|
56
|
+
* is effectively free. If/when the upstream drizzle bug is fixed, this can
|
|
57
|
+
* collapse back to a single SQL query.
|
|
92
58
|
*/
|
|
93
59
|
findByCollection(collectionSlug: CollectionSlug, documentIds?: Array<string | number>): Promise<Task[]>;
|
|
94
60
|
/**
|
|
@@ -13,7 +13,7 @@ import { normalizeJob } from "./normalizeJob";
|
|
|
13
13
|
async enqueue(tasks) {
|
|
14
14
|
const byCollection = this.groupByCollection(tasks);
|
|
15
15
|
for (const [collectionSlug, items] of byCollection){
|
|
16
|
-
const documentIds = items.map((t)=>
|
|
16
|
+
const documentIds = items.map((t)=>t.collectionId);
|
|
17
17
|
const existing = await this.findByCollection(collectionSlug, documentIds);
|
|
18
18
|
if (existing.length > 0) {
|
|
19
19
|
await this.cancelInternal(existing.map((t)=>t.id));
|
|
@@ -23,19 +23,13 @@ import { normalizeJob } from "./normalizeJob";
|
|
|
23
23
|
task: this.config.taskName,
|
|
24
24
|
queue: this.config.queueName,
|
|
25
25
|
input: {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// task handler. `findByCollection` reads back via in-memory
|
|
34
|
-
// filtering and normalizes both sides with String(...) for the
|
|
35
|
-
// comparison, so it does not need write-side normalization.
|
|
36
|
-
relationTo: task.collectionSlug,
|
|
37
|
-
value: task.collectionId
|
|
38
|
-
},
|
|
26
|
+
// Flat text reference (ID-agnostic). Stored as a string — no
|
|
27
|
+
// relationship type validation against the collection's ID type,
|
|
28
|
+
// which is what previously left number-id jobs stuck in processing.
|
|
29
|
+
// This is the single write boundary, so `String(...)` here is the
|
|
30
|
+
// one place IDs are normalized for storage.
|
|
31
|
+
collection_slug: task.collectionSlug,
|
|
32
|
+
collection_id: String(task.collectionId),
|
|
39
33
|
source_lng: task.sourceLng,
|
|
40
34
|
target_lng: task.targetLng,
|
|
41
35
|
strategy: task.strategy,
|
|
@@ -84,89 +78,54 @@ import { normalizeJob } from "./normalizeJob";
|
|
|
84
78
|
/**
|
|
85
79
|
* Find translation jobs for a collection, optionally narrowed by document IDs.
|
|
86
80
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* where: {
|
|
93
|
-
* and: [
|
|
94
|
-
* { 'input.collection.relationTo': { equals: collectionSlug } },
|
|
95
|
-
* { 'input.collection.value': { in: documentIds } },
|
|
96
|
-
* ],
|
|
97
|
-
* }
|
|
81
|
+
* Narrowing is by `taskSlug` only in SQL; the collection slug and document
|
|
82
|
+
* IDs are matched in memory (via the normalized `Task`, which reads both the
|
|
83
|
+
* current flat-text shape and the legacy relationship shape). This is the
|
|
84
|
+
* one path that must transparently span both stored shapes during the
|
|
85
|
+
* ID-agnostic migration — see docs/DEPRECATIONS.md#jobs-input-collection-field.
|
|
98
86
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
87
|
+
* IMPORTANT — why slug/id are matched in memory, not in the SQL WHERE
|
|
88
|
+
* ------------------------------------------------------------------------
|
|
89
|
+
* The "natural" implementation would push `input.collection_id` into the
|
|
90
|
+
* where clause. This DOES NOT work on SQLite (and is unreliable on any
|
|
91
|
+
* adapter) because of two compounding bugs in Payload's drizzle layer.
|
|
101
92
|
*
|
|
102
93
|
* 1. The `input` field on `payload-jobs` is declared `type: 'json'`. The
|
|
103
94
|
* drizzle path resolver (`@payloadcms/drizzle/queries/getTableColumnFromPath`)
|
|
104
95
|
* has no `case 'json'` branch, so the value is left as a raw column and
|
|
105
96
|
* the path segments are passed through to `parseParams.js`, which on
|
|
106
97
|
* SQLite builds raw SQL using `convertPathToJSONTraversal` — generating
|
|
107
|
-
* expressions like `input->>'
|
|
98
|
+
* expressions like `input->>'collection_id'`.
|
|
108
99
|
*
|
|
109
100
|
* 2. When `parseParams.js` formats the right-hand side of `in`/`not_in`
|
|
110
101
|
* (and even `equals` when `!isNaN(val)`), it inlines values via JS
|
|
111
102
|
* template literals WITHOUT wrapping strings in quotes. The string
|
|
112
103
|
* `'1'` from our WHERE becomes raw `1` in the SQL. Drizzle therefore
|
|
113
|
-
* emits queries like
|
|
114
|
-
*
|
|
115
|
-
* WHERE input->>'collection'->>'value' IN (1)
|
|
116
|
-
*
|
|
117
|
-
* even though the caller passed `['1']` (an array of strings).
|
|
118
|
-
*
|
|
119
|
-
* On SQLite, `->>` preserves the JSON value's type — if the stored JSON
|
|
120
|
-
* has `"value": "1"` (a JSON string), `->>` returns SQLite TEXT `'1'`;
|
|
121
|
-
* if the JSON has `"value": 1` (a JSON number), `->>` returns INTEGER `1`.
|
|
122
|
-
* SQLite's `IN (...)` does NOT coerce between TEXT and INTEGER. So:
|
|
123
|
-
*
|
|
124
|
-
* TEXT '1' IN (1) → false (text vs integer, no match)
|
|
125
|
-
* INTEGER 1 IN ('1') → false (integer vs text, no match)
|
|
126
|
-
*
|
|
127
|
-
* Combined with bug #2 above, any value passed by the caller — even if
|
|
128
|
-
* we normalize it to a string on write — gets re-coerced to a number in
|
|
129
|
-
* the generated SQL and never matches the stored JSON.
|
|
104
|
+
* emits queries like `WHERE input->>'collection_id' IN (1)` even though
|
|
105
|
+
* the caller passed `['1']` (an array of strings).
|
|
130
106
|
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* --------------------------------------------
|
|
137
|
-
* - This plugin is published to npm. Consumers install it with their own
|
|
138
|
-
* Payload version and would not receive any local `bun patch` /
|
|
139
|
-
* `patch-package` overrides on `@payloadcms/drizzle`. The plugin must
|
|
140
|
-
* work against vanilla Payload.
|
|
141
|
-
* - A PR to Payload core is the proper long-term fix, but the plugin
|
|
142
|
-
* cannot block on its merge/release cycle.
|
|
143
|
-
* - Forcing a non-numeric prefix on the stored ID (e.g., `"id:1"`) would
|
|
144
|
-
* work around bug #2, but bloats the data shape and breaks anything
|
|
145
|
-
* that reads `input.collection.value` expecting a plain id.
|
|
107
|
+
* On SQLite, `->>` preserves the JSON value's type and `IN (...)` does NOT
|
|
108
|
+
* coerce between TEXT and INTEGER, so a numeric-looking string id never
|
|
109
|
+
* matches once bug #2 strips its quotes. Storing the id as text (this
|
|
110
|
+
* migration) does not fix the SQL path — drizzle re-numbers it anyway — so
|
|
111
|
+
* we keep matching in memory.
|
|
146
112
|
*
|
|
147
113
|
* Why in-memory filtering is acceptable here
|
|
148
114
|
* ------------------------------------------
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
* jobs so they don't accumulate), so a Set-membership check in JS is
|
|
154
|
-
* effectively free.
|
|
155
|
-
*
|
|
156
|
-
* If/when the upstream drizzle bug is fixed (or this plugin gains a
|
|
157
|
-
* mirror collection with indexed flat columns), this method can collapse
|
|
158
|
-
* back to a single SQL query.
|
|
115
|
+
* Per-task job sets are small (typically <100 rows; the plugin actively
|
|
116
|
+
* cancels superseded jobs so they don't accumulate), so the JS filtering
|
|
117
|
+
* is effectively free. If/when the upstream drizzle bug is fixed, this can
|
|
118
|
+
* collapse back to a single SQL query.
|
|
159
119
|
*/ async findByCollection(collectionSlug, documentIds) {
|
|
160
|
-
const
|
|
161
|
-
"input.collection.relationTo": {
|
|
162
|
-
equals: collectionSlug
|
|
163
|
-
}
|
|
164
|
-
}, {
|
|
120
|
+
const all = await this.findJobsInternal(undefined, {
|
|
165
121
|
pagination: false
|
|
166
122
|
});
|
|
167
|
-
|
|
123
|
+
const bySlug = all.filter((t)=>t.input.collectionSlug === collectionSlug);
|
|
124
|
+
if (!documentIds?.length) return bySlug;
|
|
125
|
+
// `documentIds` is the public `Array<string | number>` param, so normalize
|
|
126
|
+
// it here; `t.input.collectionId` is already `ID` (string) via normalizeJob.
|
|
168
127
|
const wanted = new Set(documentIds.map(String));
|
|
169
|
-
return
|
|
128
|
+
return bySlug.filter((t)=>wanted.has(t.input.collectionId));
|
|
170
129
|
}
|
|
171
130
|
/**
|
|
172
131
|
* Group tasks by collection slug
|
|
@@ -203,19 +162,20 @@ import { normalizeJob } from "./normalizeJob";
|
|
|
203
162
|
/**
|
|
204
163
|
* Internal method to find jobs with where clause
|
|
205
164
|
*/ async findJobsInternal(where, params) {
|
|
165
|
+
const and = [
|
|
166
|
+
{
|
|
167
|
+
taskSlug: {
|
|
168
|
+
equals: this.config.taskName
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
];
|
|
172
|
+
if (where) and.push(where);
|
|
206
173
|
const response = await this.payload.find({
|
|
207
174
|
collection: this.config.jobsCollection,
|
|
208
175
|
limit: params?.limit,
|
|
209
176
|
pagination: params?.pagination,
|
|
210
177
|
where: {
|
|
211
|
-
and
|
|
212
|
-
{
|
|
213
|
-
taskSlug: {
|
|
214
|
-
equals: this.config.taskName
|
|
215
|
-
}
|
|
216
|
-
},
|
|
217
|
-
where
|
|
218
|
-
]
|
|
178
|
+
and
|
|
219
179
|
}
|
|
220
180
|
});
|
|
221
181
|
return response.docs.map(normalizeJob);
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
+
import { readCollectionRef } from "./readCollectionRef";
|
|
1
2
|
/**
|
|
2
3
|
* Transform Payload job to normalized Task
|
|
3
4
|
*/ export function normalizeJob(job) {
|
|
5
|
+
const { collectionSlug, collectionId } = readCollectionRef(job.input);
|
|
4
6
|
return {
|
|
5
7
|
id: job.id,
|
|
6
8
|
status: getJobStatus(job),
|
|
7
9
|
input: {
|
|
8
|
-
collectionSlug
|
|
9
|
-
collectionId
|
|
10
|
-
sourceLng: job.input?.source_lng ??
|
|
11
|
-
targetLng: job.input?.target_lng ??
|
|
12
|
-
strategy: job.input?.strategy ??
|
|
10
|
+
collectionSlug,
|
|
11
|
+
collectionId,
|
|
12
|
+
sourceLng: job.input?.source_lng ?? "",
|
|
13
|
+
targetLng: job.input?.target_lng ?? "",
|
|
14
|
+
strategy: job.input?.strategy ?? "overwrite",
|
|
13
15
|
publishOnTranslation: job.input?.publish_on_translation ?? false
|
|
14
16
|
},
|
|
15
17
|
createdAt: job.createdAt,
|
|
@@ -22,19 +24,19 @@
|
|
|
22
24
|
};
|
|
23
25
|
}
|
|
24
26
|
function getJobStatus(job) {
|
|
25
|
-
if (job.completedAt) return
|
|
26
|
-
if (job.processing) return
|
|
27
|
-
if (job.error) return
|
|
28
|
-
return
|
|
27
|
+
if (job.completedAt) return "completed";
|
|
28
|
+
if (job.processing) return "running";
|
|
29
|
+
if (job.error) return "failed";
|
|
30
|
+
return "pending";
|
|
29
31
|
}
|
|
30
32
|
function extractErrorMessage(error) {
|
|
31
|
-
if (error && typeof error ===
|
|
33
|
+
if (error && typeof error === "object" && "message" in error && typeof error.message === "string") {
|
|
32
34
|
return error.message;
|
|
33
35
|
}
|
|
34
|
-
return
|
|
36
|
+
return "Unknown error";
|
|
35
37
|
}
|
|
36
38
|
function isCancelled(error) {
|
|
37
|
-
return error !== null && typeof error ===
|
|
39
|
+
return error !== null && typeof error === "object" && "cancelled" in error && typeof error.cancelled === "boolean" && error.cancelled;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
//# sourceMappingURL=normalizeJob.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CollectionSlug } from "payload";
|
|
2
|
+
import type { ID } from "../types";
|
|
3
|
+
import type { PayloadJob } from "./types";
|
|
4
|
+
/**
|
|
5
|
+
* Normalized, ID-agnostic document reference parsed out of a stored job input.
|
|
6
|
+
*/
|
|
7
|
+
export type CollectionRef = {
|
|
8
|
+
collectionSlug: CollectionSlug;
|
|
9
|
+
collectionId: ID;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Single storage-read boundary for a job's collection reference.
|
|
13
|
+
*
|
|
14
|
+
* Reads the current flat-text shape (`collection_slug` / `collection_id`) and
|
|
15
|
+
* falls back to the legacy relationship shape (`collection.{relationTo,value}`)
|
|
16
|
+
* for jobs queued before the ID-agnostic migration. This is the one place a
|
|
17
|
+
* stored id is coerced to string — the coercion exists only because the legacy
|
|
18
|
+
* relationship `value` is typed `string | number`, and it goes away together
|
|
19
|
+
* with the legacy field in the next major.
|
|
20
|
+
* See docs/DEPRECATIONS.md#jobs-input-collection-field
|
|
21
|
+
*/
|
|
22
|
+
export declare function readCollectionRef(input: PayloadJob["input"]): CollectionRef;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single storage-read boundary for a job's collection reference.
|
|
3
|
+
*
|
|
4
|
+
* Reads the current flat-text shape (`collection_slug` / `collection_id`) and
|
|
5
|
+
* falls back to the legacy relationship shape (`collection.{relationTo,value}`)
|
|
6
|
+
* for jobs queued before the ID-agnostic migration. This is the one place a
|
|
7
|
+
* stored id is coerced to string — the coercion exists only because the legacy
|
|
8
|
+
* relationship `value` is typed `string | number`, and it goes away together
|
|
9
|
+
* with the legacy field in the next major.
|
|
10
|
+
* See docs/DEPRECATIONS.md#jobs-input-collection-field
|
|
11
|
+
*/ export function readCollectionRef(input) {
|
|
12
|
+
// `??` is intentional: `collection_slug` / `collection_id` are `required: true`
|
|
13
|
+
// in the inputSchema, so they are never an empty string for any job this plugin
|
|
14
|
+
// writes. The fallback to the legacy shape therefore only fires when the new
|
|
15
|
+
// field is genuinely absent (i.e. a job queued before the ID-agnostic migration).
|
|
16
|
+
return {
|
|
17
|
+
collectionSlug: input?.collection_slug ?? input?.collection?.relationTo ?? "",
|
|
18
|
+
collectionId: String(input?.collection_id ?? input?.collection?.value ?? "")
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=readCollectionRef.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CollectionSlug } from
|
|
1
|
+
import type { CollectionSlug } from "payload";
|
|
2
2
|
/**
|
|
3
3
|
* Configuration for automatic job processing.
|
|
4
4
|
*/
|
|
@@ -32,7 +32,7 @@ export type PayloadJobsRunnerOptions = {
|
|
|
32
32
|
* Name of the Payload jobs collection.
|
|
33
33
|
* @default 'payload-jobs'
|
|
34
34
|
*/
|
|
35
|
-
jobsCollection?:
|
|
35
|
+
jobsCollection?: CollectionSlug;
|
|
36
36
|
/**
|
|
37
37
|
* Automatic job processing configuration.
|
|
38
38
|
* Set to `false` to disable (for Vercel/serverless deployments).
|
|
@@ -47,7 +47,7 @@ export type PayloadJobsRunnerOptions = {
|
|
|
47
47
|
attempts?: number;
|
|
48
48
|
backoff?: {
|
|
49
49
|
delay?: number;
|
|
50
|
-
type:
|
|
50
|
+
type: "exponential" | "fixed";
|
|
51
51
|
};
|
|
52
52
|
};
|
|
53
53
|
};
|
|
@@ -57,9 +57,9 @@ export type PayloadJobsRunnerOptions = {
|
|
|
57
57
|
export type PayloadJobsRunnerConfig = {
|
|
58
58
|
taskName: string;
|
|
59
59
|
queueName: string;
|
|
60
|
-
jobsCollection:
|
|
60
|
+
jobsCollection: CollectionSlug;
|
|
61
61
|
autoRun: false | Required<AutoRunConfig>;
|
|
62
|
-
retries?: PayloadJobsRunnerOptions[
|
|
62
|
+
retries?: PayloadJobsRunnerOptions["retries"];
|
|
63
63
|
};
|
|
64
64
|
/**
|
|
65
65
|
* Raw Payload job structure
|
|
@@ -72,6 +72,14 @@ export type PayloadJob = {
|
|
|
72
72
|
error?: unknown;
|
|
73
73
|
processing?: boolean | null;
|
|
74
74
|
input?: {
|
|
75
|
+
/** Document reference (flat text, ID-agnostic). Current shape. */
|
|
76
|
+
collection_slug?: string;
|
|
77
|
+
collection_id?: string;
|
|
78
|
+
/**
|
|
79
|
+
* @deprecated Legacy relationship shape, read-only fallback for jobs queued
|
|
80
|
+
* before the ID-agnostic migration. Removed in next major.
|
|
81
|
+
* See docs/DEPRECATIONS.md#jobs-input-collection-field
|
|
82
|
+
*/
|
|
75
83
|
collection?: {
|
|
76
84
|
relationTo: CollectionSlug;
|
|
77
85
|
value: string | number;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { Payload, CollectionSlug } from
|
|
2
|
-
import type { TaskRunner } from
|
|
3
|
-
import type { TaskHandler } from
|
|
4
|
-
import type { Task, TaskInput, RunResult } from
|
|
5
|
-
import type { LazyMap } from
|
|
1
|
+
import type { Payload, CollectionSlug } from "payload";
|
|
2
|
+
import type { TaskRunner } from "../TaskRunner.interface";
|
|
3
|
+
import type { TaskHandler } from "../TaskRunnerProvider.interface";
|
|
4
|
+
import type { Task, TaskInput, RunResult } from "../types";
|
|
5
|
+
import type { LazyMap } from "../../../shared/utils";
|
|
6
6
|
/**
|
|
7
7
|
* Synchronous TaskRunner implementation.
|
|
8
8
|
*
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
const now = new Date().toISOString();
|
|
19
19
|
const task = {
|
|
20
20
|
id: crypto.randomUUID(),
|
|
21
|
-
status:
|
|
21
|
+
status: "running",
|
|
22
22
|
input,
|
|
23
23
|
createdAt: now,
|
|
24
24
|
updatedAt: now,
|
|
@@ -34,12 +34,12 @@
|
|
|
34
34
|
strategy: input.strategy,
|
|
35
35
|
publishOnTranslation: input.publishOnTranslation
|
|
36
36
|
});
|
|
37
|
-
task.status =
|
|
37
|
+
task.status = "completed";
|
|
38
38
|
task.completedAt = new Date().toISOString();
|
|
39
39
|
} catch (error) {
|
|
40
|
-
task.status =
|
|
40
|
+
task.status = "failed";
|
|
41
41
|
task.error = {
|
|
42
|
-
message: error instanceof Error ? error.message :
|
|
42
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
task.updatedAt = new Date().toISOString();
|
|
@@ -52,14 +52,15 @@
|
|
|
52
52
|
// Sync runner executes tasks immediately, no pending tasks to run
|
|
53
53
|
return {
|
|
54
54
|
success: false,
|
|
55
|
-
error:
|
|
55
|
+
error: "not_found"
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
async findByCollection(collectionSlug, documentIds) {
|
|
59
59
|
const results = [];
|
|
60
|
+
const wanted = documentIds ? new Set(documentIds.map(String)) : undefined;
|
|
60
61
|
for (const [, task] of this.tasks){
|
|
61
62
|
if (task.input.collectionSlug !== collectionSlug) continue;
|
|
62
|
-
if (
|
|
63
|
+
if (wanted && !wanted.has(task.input.collectionId)) continue;
|
|
63
64
|
results.push(task);
|
|
64
65
|
}
|
|
65
66
|
return results;
|