@sequoialabs/payload-plugin-reversia 0.2.1 → 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/endpoints/resource.js +7 -2
- package/dist/endpoints/resources-insert.js +21 -2
- package/dist/endpoints/resources.js +11 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -2
- package/dist/types.d.ts +42 -2
- package/dist/utils/payload-helpers.d.ts +25 -1
- package/dist/utils/payload-helpers.js +37 -0
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { unauthorizedResponse, validateApiKey } from '../utils/auth';
|
|
2
2
|
import { findLocalizedFields, serializeField } from '../utils/fields';
|
|
3
|
-
import { resolveDefaultLocale } from '../utils/payload-helpers';
|
|
3
|
+
import { resolveDefaultLocale, shouldUseDrafts } from '../utils/payload-helpers';
|
|
4
4
|
function extract(doc, fields) {
|
|
5
5
|
const content = {};
|
|
6
6
|
const contentTypes = {};
|
|
@@ -45,7 +45,11 @@ export function createResourceEndpoint(pluginConfig, collectionsMap, globalsMap)
|
|
|
45
45
|
return Response.json({ error: `Global ${globalSlug} not found` }, { status: 404 });
|
|
46
46
|
}
|
|
47
47
|
const localizedFields = findLocalizedFields(globalConfig.fields);
|
|
48
|
-
const doc = await req.payload.findGlobal({
|
|
48
|
+
const doc = await req.payload.findGlobal({
|
|
49
|
+
slug: globalSlug,
|
|
50
|
+
locale: defaultLocale,
|
|
51
|
+
draft: shouldUseDrafts(pluginConfig, 'global', globalConfig),
|
|
52
|
+
});
|
|
49
53
|
const { content, contentTypes } = extract(doc, localizedFields);
|
|
50
54
|
return Response.json({
|
|
51
55
|
id: globalSlug,
|
|
@@ -69,6 +73,7 @@ export function createResourceEndpoint(pluginConfig, collectionsMap, globalsMap)
|
|
|
69
73
|
collection: slug,
|
|
70
74
|
id: resourceId,
|
|
71
75
|
locale: defaultLocale,
|
|
76
|
+
draft: shouldUseDrafts(pluginConfig, 'collection', collection),
|
|
72
77
|
});
|
|
73
78
|
const { content, contentTypes } = extract(doc, localizedFields);
|
|
74
79
|
return Response.json({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { unauthorizedResponse, validateApiKey } from '../utils/auth';
|
|
2
2
|
import { deflatePopulatedRelationships, deserializeFieldValue, findLocalizedFields, } from '../utils/fields';
|
|
3
|
+
import { shouldUseDrafts } from '../utils/payload-helpers';
|
|
3
4
|
const WRITE_CONFLICT_MAX_RETRIES = 3;
|
|
4
5
|
const WRITE_CONFLICT_BASE_DELAY_MS = 50;
|
|
5
6
|
function isWriteConflict(error) {
|
|
@@ -156,7 +157,7 @@ function applyTranslations({ allowedFields, data, sourceDoc, previousDoc, }) {
|
|
|
156
157
|
* manually later.
|
|
157
158
|
*/
|
|
158
159
|
async function dropUniqueCollisions(params) {
|
|
159
|
-
const { payload, collection, id, locale, fields, updateData, acceptedFields, diff } = params;
|
|
160
|
+
const { payload, collection, id, locale, draft, fields, updateData, acceptedFields, diff } = params;
|
|
160
161
|
const dropped = [];
|
|
161
162
|
for (const field of fields) {
|
|
162
163
|
if (field.isContainer || !field.unique) {
|
|
@@ -175,6 +176,7 @@ async function dropUniqueCollisions(params) {
|
|
|
175
176
|
depth: 0,
|
|
176
177
|
limit: 1,
|
|
177
178
|
pagination: false,
|
|
179
|
+
draft,
|
|
178
180
|
where: {
|
|
179
181
|
and: [{ [field.name]: { equals: value } }, { id: { not_equals: id } }],
|
|
180
182
|
},
|
|
@@ -241,16 +243,26 @@ export function createResourcesInsertEndpoint(pluginConfig, collectionsMap, glob
|
|
|
241
243
|
continue;
|
|
242
244
|
}
|
|
243
245
|
const allowedFields = findLocalizedFields(globalConfig.fields);
|
|
246
|
+
// When draft-aware, read the source and previous values from the
|
|
247
|
+
// latest draft and save the translation as a draft too, so nothing
|
|
248
|
+
// reaches the published global until an editor publishes it.
|
|
249
|
+
const draft = shouldUseDrafts(pluginConfig, 'global', globalConfig);
|
|
244
250
|
// depth: 0 is critical — without it, Payload populates relationship
|
|
245
251
|
// and upload fields as full objects instead of raw IDs. Our clone
|
|
246
252
|
// would then write those objects back, which Payload's validator
|
|
247
253
|
// rejects with "invalid relationships: [object Object]".
|
|
248
254
|
const [sourceDoc, previousDoc] = await Promise.all([
|
|
249
|
-
req.payload.findGlobal({
|
|
255
|
+
req.payload.findGlobal({
|
|
256
|
+
slug: globalSlug,
|
|
257
|
+
locale: item.sourceLocale,
|
|
258
|
+
depth: 0,
|
|
259
|
+
draft,
|
|
260
|
+
}),
|
|
250
261
|
req.payload.findGlobal({
|
|
251
262
|
slug: globalSlug,
|
|
252
263
|
locale: item.targetLocale,
|
|
253
264
|
depth: 0,
|
|
265
|
+
draft,
|
|
254
266
|
fallbackLocale: false,
|
|
255
267
|
}),
|
|
256
268
|
]);
|
|
@@ -268,6 +280,7 @@ export function createResourcesInsertEndpoint(pluginConfig, collectionsMap, glob
|
|
|
268
280
|
slug: globalSlug,
|
|
269
281
|
locale: item.targetLocale,
|
|
270
282
|
data: updateData,
|
|
283
|
+
draft,
|
|
271
284
|
context: { reversiaInsertion: true },
|
|
272
285
|
}));
|
|
273
286
|
response[index] = { index, type: item.type, id: globalSlug, diff };
|
|
@@ -289,18 +302,22 @@ export function createResourcesInsertEndpoint(pluginConfig, collectionsMap, glob
|
|
|
289
302
|
}
|
|
290
303
|
const itemId = item.id;
|
|
291
304
|
const allowedFields = findLocalizedFields(collection.fields);
|
|
305
|
+
// See the global branch above: draft-aware reads + draft write.
|
|
306
|
+
const draft = shouldUseDrafts(pluginConfig, 'collection', collection);
|
|
292
307
|
const [sourceDoc, previousDoc] = await Promise.all([
|
|
293
308
|
req.payload.findByID({
|
|
294
309
|
collection: slug,
|
|
295
310
|
id: item.id,
|
|
296
311
|
locale: item.sourceLocale,
|
|
297
312
|
depth: 0,
|
|
313
|
+
draft,
|
|
298
314
|
}),
|
|
299
315
|
req.payload.findByID({
|
|
300
316
|
collection: slug,
|
|
301
317
|
id: item.id,
|
|
302
318
|
locale: item.targetLocale,
|
|
303
319
|
depth: 0,
|
|
320
|
+
draft,
|
|
304
321
|
fallbackLocale: false,
|
|
305
322
|
}),
|
|
306
323
|
]);
|
|
@@ -319,6 +336,7 @@ export function createResourcesInsertEndpoint(pluginConfig, collectionsMap, glob
|
|
|
319
336
|
collection: slug,
|
|
320
337
|
id: itemId,
|
|
321
338
|
locale: item.targetLocale,
|
|
339
|
+
draft,
|
|
322
340
|
fields: allowedFields,
|
|
323
341
|
updateData,
|
|
324
342
|
acceptedFields,
|
|
@@ -333,6 +351,7 @@ export function createResourcesInsertEndpoint(pluginConfig, collectionsMap, glob
|
|
|
333
351
|
id: itemId,
|
|
334
352
|
locale: item.targetLocale,
|
|
335
353
|
data: updateData,
|
|
354
|
+
draft,
|
|
336
355
|
context: { reversiaInsertion: true },
|
|
337
356
|
}));
|
|
338
357
|
response[index] = { index, type: item.type, id: itemId, diff };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { unauthorizedResponse, validateApiKey } from '../utils/auth';
|
|
2
2
|
import { decodeCursor, encodeCursor } from '../utils/cursor';
|
|
3
3
|
import { findLocalizedFields, serializeField } from '../utils/fields';
|
|
4
|
-
import { parseLimit, resolveDefaultLocale } from '../utils/payload-helpers';
|
|
4
|
+
import { parseLimit, resolveDefaultLocale, shouldUseDrafts } from '../utils/payload-helpers';
|
|
5
5
|
function extractContent(doc, fields) {
|
|
6
6
|
const content = {};
|
|
7
7
|
const contentTypes = {};
|
|
@@ -68,12 +68,17 @@ export function createResourcesEndpoint(pluginConfig, collectionsMap, globalsMap
|
|
|
68
68
|
if (cursor && cursor.type === resourceType && cursor.id) {
|
|
69
69
|
where.id = { greater_than: cursor.id };
|
|
70
70
|
}
|
|
71
|
+
// With `draft: true` Payload queries the versions table and returns
|
|
72
|
+
// the latest version per document (draft or published). It rewrites
|
|
73
|
+
// `id` in `where`/`sort` to the version's `parent`, so the cursor keeps
|
|
74
|
+
// pointing at document ids.
|
|
71
75
|
const docs = await req.payload.find({
|
|
72
76
|
collection: slug,
|
|
73
77
|
locale: defaultLocale,
|
|
74
78
|
limit: limit - totalFetched,
|
|
75
79
|
sort: 'id',
|
|
76
80
|
where,
|
|
81
|
+
draft: shouldUseDrafts(pluginConfig, 'collection', collection),
|
|
77
82
|
});
|
|
78
83
|
if (docs.docs.length === 0) {
|
|
79
84
|
continue;
|
|
@@ -123,7 +128,11 @@ export function createResourcesEndpoint(pluginConfig, collectionsMap, globalsMap
|
|
|
123
128
|
if (localizedFields.length === 0) {
|
|
124
129
|
continue;
|
|
125
130
|
}
|
|
126
|
-
const doc = await req.payload.findGlobal({
|
|
131
|
+
const doc = await req.payload.findGlobal({
|
|
132
|
+
slug,
|
|
133
|
+
locale: defaultLocale,
|
|
134
|
+
draft: shouldUseDrafts(pluginConfig, 'global', global),
|
|
135
|
+
});
|
|
127
136
|
const { content, contentTypes } = extractContent(doc, localizedFields);
|
|
128
137
|
if (Object.keys(content).length === 0) {
|
|
129
138
|
continue;
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Config } from 'payload';
|
|
|
2
2
|
import type { ReversiaPluginConfig } from './types';
|
|
3
3
|
export type { TriggerCrawlOptions, TriggerCrawlResult } from './trigger-crawl';
|
|
4
4
|
export { triggerCrawl } from './trigger-crawl';
|
|
5
|
-
export type { ConfirmResourcesSyncResponse, InsertionRequest, InsertionResponse, ResourceDefinition, ResourceItem, ResourceResponse, ReversiaErrorResponse, ReversiaFieldCustom, ReversiaPluginConfig, SettingsResponse, StreamResponse, TranslatableFieldConfig, } from './types';
|
|
5
|
+
export type { ConfirmResourcesSyncResponse, EnabledResource, EnabledResourceOptions, InsertionRequest, InsertionResponse, ResourceDefinition, ResourceItem, ResourceResponse, ReversiaErrorResponse, ReversiaFieldCustom, ReversiaPluginConfig, SettingsResponse, StreamResponse, TranslatableFieldConfig, } from './types';
|
|
6
6
|
export { ReversiaFieldBehavior, ReversiaFieldType } from './types';
|
|
7
7
|
export declare const reversiaPlugin: (pluginConfig: ReversiaPluginConfig) => (config: Config) => Config;
|
|
8
8
|
export default reversiaPlugin;
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { createSettingsEndpoint } from './endpoints/settings';
|
|
|
9
9
|
import { createTriggerCrawlDashboardEndpoint } from './endpoints/trigger-crawl-dashboard';
|
|
10
10
|
import { createAfterChangeHook } from './hooks/after-change';
|
|
11
11
|
import { findLocalizedFields } from './utils/fields';
|
|
12
|
+
import { resourceSlug } from './utils/payload-helpers';
|
|
12
13
|
export { triggerCrawl } from './trigger-crawl';
|
|
13
14
|
export { ReversiaFieldBehavior, ReversiaFieldType } from './types';
|
|
14
15
|
const APPLIED_MARKER = Symbol.for('payload-plugin-reversia.applied');
|
|
@@ -25,10 +26,10 @@ export const reversiaPlugin = (pluginConfig) => (config) => {
|
|
|
25
26
|
}
|
|
26
27
|
marked[APPLIED_MARKER] = true;
|
|
27
28
|
const enabledCollectionSlugs = pluginConfig.enabledCollections
|
|
28
|
-
? new Set(pluginConfig.enabledCollections.map(
|
|
29
|
+
? new Set(pluginConfig.enabledCollections.map(resourceSlug))
|
|
29
30
|
: null;
|
|
30
31
|
const enabledGlobalSlugs = pluginConfig.enabledGlobals
|
|
31
|
-
? new Set(pluginConfig.enabledGlobals)
|
|
32
|
+
? new Set(pluginConfig.enabledGlobals.map(resourceSlug))
|
|
32
33
|
: null;
|
|
33
34
|
const collectionsMap = new Map();
|
|
34
35
|
const globalsMap = new Map();
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import type { CollectionSlug } from 'payload';
|
|
2
2
|
import type { LeafSegment } from './utils/path-resolver';
|
|
3
|
+
/**
|
|
4
|
+
* An entry of `enabledCollections` / `enabledGlobals`: either the bare slug,
|
|
5
|
+
* or an object carrying per-resource options.
|
|
6
|
+
*/
|
|
7
|
+
export interface EnabledResourceOptions<Slug extends string = string> {
|
|
8
|
+
slug: Slug;
|
|
9
|
+
/**
|
|
10
|
+
* Per-resource override of the root `useDrafts` flag. Only meaningful when
|
|
11
|
+
* the collection or global enables `versions.drafts`.
|
|
12
|
+
*/
|
|
13
|
+
useDrafts?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export type EnabledResource<Slug extends string = string> = EnabledResourceOptions<Slug> | Slug;
|
|
3
16
|
export interface ReversiaPluginConfig {
|
|
4
17
|
/**
|
|
5
18
|
* API key used by Reversia SaaS to authenticate requests.
|
|
@@ -9,13 +22,19 @@ export interface ReversiaPluginConfig {
|
|
|
9
22
|
/**
|
|
10
23
|
* Optional: restrict which collections are exposed to Reversia.
|
|
11
24
|
* If omitted, all collections with localized fields are exposed.
|
|
25
|
+
*
|
|
26
|
+
* Each entry is a slug, or `{ slug, useDrafts }` to override the root
|
|
27
|
+
* `useDrafts` flag for that collection.
|
|
12
28
|
*/
|
|
13
|
-
enabledCollections?: CollectionSlug[];
|
|
29
|
+
enabledCollections?: EnabledResource<CollectionSlug>[];
|
|
14
30
|
/**
|
|
15
31
|
* Optional: restrict which globals are exposed to Reversia.
|
|
16
32
|
* If omitted, all globals with localized fields are exposed.
|
|
33
|
+
*
|
|
34
|
+
* Each entry is a slug, or `{ slug, useDrafts }` to override the root
|
|
35
|
+
* `useDrafts` flag for that global.
|
|
17
36
|
*/
|
|
18
|
-
enabledGlobals?:
|
|
37
|
+
enabledGlobals?: EnabledResource[];
|
|
19
38
|
/**
|
|
20
39
|
* Whether the plugin is disabled. Defaults to false.
|
|
21
40
|
*/
|
|
@@ -26,6 +45,27 @@ export interface ReversiaPluginConfig {
|
|
|
26
45
|
* built-in production URL.
|
|
27
46
|
*/
|
|
28
47
|
baseUrl?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Make the plugin draft-aware for collections and globals that enable
|
|
50
|
+
* `versions.drafts`. Defaults to false. Can be overridden per resource via
|
|
51
|
+
* `{ slug, useDrafts }` entries in `enabledCollections` / `enabledGlobals`.
|
|
52
|
+
*
|
|
53
|
+
* When enabled, for those entities:
|
|
54
|
+
* - `GET /reversia/resources` and `GET /reversia/resource` return the latest
|
|
55
|
+
* draft (falling back to the published document when no newer draft
|
|
56
|
+
* exists), so Reversia translates what editors are currently working on.
|
|
57
|
+
* - `PUT /reversia/resources-insert` reads the source locale from the draft
|
|
58
|
+
* and saves the translation as a new draft version. The published document
|
|
59
|
+
* is never touched — publishing stays an editorial action in Payload.
|
|
60
|
+
*
|
|
61
|
+
* Entities without drafts are unaffected either way.
|
|
62
|
+
*
|
|
63
|
+
* When disabled (the default), every read and write targets the published
|
|
64
|
+
* document. Note that Payload bases a non-draft update on the latest
|
|
65
|
+
* version, so on a draft-enabled entity an insertion also publishes any
|
|
66
|
+
* pending source-locale draft — enable this flag if that is not desired.
|
|
67
|
+
*/
|
|
68
|
+
useDrafts?: boolean;
|
|
29
69
|
}
|
|
30
70
|
export declare enum ReversiaFieldType {
|
|
31
71
|
TEXT = "TEXT",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { PayloadRequest } from 'payload';
|
|
1
|
+
import type { CollectionConfig, GlobalConfig, PayloadRequest } from 'payload';
|
|
2
|
+
import type { EnabledResource, ReversiaPluginConfig } from '../types';
|
|
2
3
|
/**
|
|
3
4
|
* Parses a caller-supplied `limit` query parameter and clamps it to a safe
|
|
4
5
|
* range. NaN, zero, and negatives fall back to the default.
|
|
@@ -9,6 +10,29 @@ export declare function parseLimit(raw: string | null, fallback?: number): numbe
|
|
|
9
10
|
* when localization is not configured.
|
|
10
11
|
*/
|
|
11
12
|
export declare function resolveDefaultLocale(req: PayloadRequest): string;
|
|
13
|
+
/**
|
|
14
|
+
* True when a collection or global enables `versions.drafts`. Accepts both
|
|
15
|
+
* the raw config shape (`versions: { drafts: true }`) and Payload's sanitized
|
|
16
|
+
* one (`versions: { drafts: { autosave: false, … } }`).
|
|
17
|
+
*/
|
|
18
|
+
export declare function hasDraftsEnabled(entity: CollectionConfig | GlobalConfig): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Slug of an `enabledCollections` / `enabledGlobals` entry, whichever form
|
|
21
|
+
* it was written in.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resourceSlug(entry: EnabledResource): string;
|
|
24
|
+
/**
|
|
25
|
+
* Resolves the configured `useDrafts` value for one resource: the
|
|
26
|
+
* per-resource override from its `enabledCollections` / `enabledGlobals`
|
|
27
|
+
* entry when present, otherwise the root `useDrafts` flag (default false).
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveUseDrafts(pluginConfig: ReversiaPluginConfig, kind: 'collection' | 'global', slug: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Whether reads and writes for `entity` should target its draft rather than
|
|
32
|
+
* the published document: requires both `useDrafts` resolving to true for
|
|
33
|
+
* this resource and `versions.drafts` on the entity.
|
|
34
|
+
*/
|
|
35
|
+
export declare function shouldUseDrafts(pluginConfig: ReversiaPluginConfig, kind: 'collection' | 'global', entity: CollectionConfig | GlobalConfig): boolean;
|
|
12
36
|
/**
|
|
13
37
|
* Plain dot-path lookup on an object, returning `undefined` for any missing
|
|
14
38
|
* segment or non-object traversal. Does not walk into arrays.
|
|
@@ -28,6 +28,43 @@ export function resolveDefaultLocale(req) {
|
|
|
28
28
|
}
|
|
29
29
|
return 'en';
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* True when a collection or global enables `versions.drafts`. Accepts both
|
|
33
|
+
* the raw config shape (`versions: { drafts: true }`) and Payload's sanitized
|
|
34
|
+
* one (`versions: { drafts: { autosave: false, … } }`).
|
|
35
|
+
*/
|
|
36
|
+
export function hasDraftsEnabled(entity) {
|
|
37
|
+
const versions = entity.versions;
|
|
38
|
+
return Boolean(versions && typeof versions === 'object' && versions.drafts);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Slug of an `enabledCollections` / `enabledGlobals` entry, whichever form
|
|
42
|
+
* it was written in.
|
|
43
|
+
*/
|
|
44
|
+
export function resourceSlug(entry) {
|
|
45
|
+
return typeof entry === 'string' ? entry : entry.slug;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Resolves the configured `useDrafts` value for one resource: the
|
|
49
|
+
* per-resource override from its `enabledCollections` / `enabledGlobals`
|
|
50
|
+
* entry when present, otherwise the root `useDrafts` flag (default false).
|
|
51
|
+
*/
|
|
52
|
+
export function resolveUseDrafts(pluginConfig, kind, slug) {
|
|
53
|
+
const entries = kind === 'collection' ? pluginConfig.enabledCollections : pluginConfig.enabledGlobals;
|
|
54
|
+
const entry = entries?.find((e) => resourceSlug(e) === slug);
|
|
55
|
+
if (entry && typeof entry !== 'string' && typeof entry.useDrafts === 'boolean') {
|
|
56
|
+
return entry.useDrafts;
|
|
57
|
+
}
|
|
58
|
+
return pluginConfig.useDrafts === true;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Whether reads and writes for `entity` should target its draft rather than
|
|
62
|
+
* the published document: requires both `useDrafts` resolving to true for
|
|
63
|
+
* this resource and `versions.drafts` on the entity.
|
|
64
|
+
*/
|
|
65
|
+
export function shouldUseDrafts(pluginConfig, kind, entity) {
|
|
66
|
+
return resolveUseDrafts(pluginConfig, kind, entity.slug) && hasDraftsEnabled(entity);
|
|
67
|
+
}
|
|
31
68
|
/**
|
|
32
69
|
* Plain dot-path lookup on an object, returning `undefined` for any missing
|
|
33
70
|
* segment or non-object traversal. Does not walk into arrays.
|
package/package.json
CHANGED