@byline/core 3.2.1 → 3.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/@types/db-types.d.ts +28 -0
- package/dist/services/collection-bootstrap.test.node.js +4 -0
- package/dist/services/discover-counter-groups.test.node.js +2 -0
- package/dist/services/document-lifecycle.d.ts +54 -0
- package/dist/services/document-lifecycle.js +89 -0
- package/dist/services/document-lifecycle.test.node.js +2 -0
- package/dist/services/field-upload.test.node.js +2 -0
- package/dist/services/populate.test.node.js +2 -0
- package/package.json +2 -2
|
@@ -340,6 +340,34 @@ export interface IDocumentCommands {
|
|
|
340
340
|
document: any;
|
|
341
341
|
fieldCount: number;
|
|
342
342
|
}>;
|
|
343
|
+
/**
|
|
344
|
+
* Standalone, non-versioned write of a document's URL path
|
|
345
|
+
* (`byline_document_paths`). Edits the document-grain, sticky path row
|
|
346
|
+
* in-place **without** minting a new version or touching workflow status —
|
|
347
|
+
* the change is immediate and applies across every version. Backs the admin
|
|
348
|
+
* path widget's direct-write Save. The unique constraint on
|
|
349
|
+
* `(collection_id, locale, path)` may surface as `ERR_PATH_CONFLICT` from the
|
|
350
|
+
* lifecycle layer. See `docs/I18N.md`.
|
|
351
|
+
*/
|
|
352
|
+
updateDocumentPath(params: {
|
|
353
|
+
documentId: string;
|
|
354
|
+
collectionId: string;
|
|
355
|
+
locale: string;
|
|
356
|
+
path: string;
|
|
357
|
+
}): Promise<void>;
|
|
358
|
+
/**
|
|
359
|
+
* Standalone, non-versioned write of a document's editorial advertised-locale
|
|
360
|
+
* set (`byline_document_available_locales`). Replaces the document-grain set
|
|
361
|
+
* wholesale **without** minting a new version or touching workflow status —
|
|
362
|
+
* the change is immediate and applies across every version. `[]` clears it.
|
|
363
|
+
* Backs the admin available-locales widget's direct-write Save. See
|
|
364
|
+
* `docs/I18N.md`.
|
|
365
|
+
*/
|
|
366
|
+
setDocumentAvailableLocales(params: {
|
|
367
|
+
documentId: string;
|
|
368
|
+
collectionId: string;
|
|
369
|
+
availableLocales: string[];
|
|
370
|
+
}): Promise<void>;
|
|
343
371
|
/**
|
|
344
372
|
* Mutate the status on an existing document version row.
|
|
345
373
|
*
|
|
@@ -39,6 +39,8 @@ function createMockDb(options) {
|
|
|
39
39
|
collections: { create, update, delete: vi.fn(fail) },
|
|
40
40
|
documents: {
|
|
41
41
|
createDocumentVersion: vi.fn(fail),
|
|
42
|
+
updateDocumentPath: vi.fn(fail),
|
|
43
|
+
setDocumentAvailableLocales: vi.fn(fail),
|
|
42
44
|
setDocumentStatus: vi.fn(fail),
|
|
43
45
|
archivePublishedVersions: vi.fn(fail),
|
|
44
46
|
softDeleteDocument: vi.fn(fail),
|
|
@@ -182,6 +184,8 @@ describe('ensureCollections', () => {
|
|
|
182
184
|
collections: { create, update, delete: vi.fn() },
|
|
183
185
|
documents: {
|
|
184
186
|
createDocumentVersion: vi.fn(),
|
|
187
|
+
updateDocumentPath: vi.fn(),
|
|
188
|
+
setDocumentAvailableLocales: vi.fn(),
|
|
185
189
|
setDocumentStatus: vi.fn(),
|
|
186
190
|
archivePublishedVersions: vi.fn(),
|
|
187
191
|
softDeleteDocument: vi.fn(),
|
|
@@ -24,6 +24,8 @@ function makeAdapter(options) {
|
|
|
24
24
|
collections: { create: vi.fn(fail), update: vi.fn(fail), delete: vi.fn(fail) },
|
|
25
25
|
documents: {
|
|
26
26
|
createDocumentVersion: vi.fn(fail),
|
|
27
|
+
updateDocumentPath: vi.fn(fail),
|
|
28
|
+
setDocumentAvailableLocales: vi.fn(fail),
|
|
27
29
|
setDocumentStatus: vi.fn(fail),
|
|
28
30
|
archivePublishedVersions: vi.fn(fail),
|
|
29
31
|
softDeleteDocument: vi.fn(fail),
|
|
@@ -104,6 +104,13 @@ export interface UpdateDocumentWithPatchesResult {
|
|
|
104
104
|
documentId: string;
|
|
105
105
|
documentVersionId: string;
|
|
106
106
|
}
|
|
107
|
+
export interface UpdateDocumentSystemFieldsResult {
|
|
108
|
+
documentId: string;
|
|
109
|
+
/** The path actually written, or `undefined` when no path write occurred. */
|
|
110
|
+
path?: string;
|
|
111
|
+
/** Whether the advertised-locale set was rewritten this call. */
|
|
112
|
+
availableLocalesWritten: boolean;
|
|
113
|
+
}
|
|
107
114
|
export interface ChangeStatusResult {
|
|
108
115
|
previousStatus: string;
|
|
109
116
|
newStatus: string;
|
|
@@ -263,6 +270,53 @@ export declare function updateDocumentWithPatches(ctx: DocumentLifecycleContext,
|
|
|
263
270
|
*/
|
|
264
271
|
availableLocales?: string[];
|
|
265
272
|
}): Promise<UpdateDocumentWithPatchesResult>;
|
|
273
|
+
/**
|
|
274
|
+
* Write a document's system-managed, document-grain fields — `path` and the
|
|
275
|
+
* editorial `availableLocales` set — **without** minting a new version or
|
|
276
|
+
* touching workflow status.
|
|
277
|
+
*
|
|
278
|
+
* These fields are document-grain (they live in `byline_document_paths` and
|
|
279
|
+
* `byline_document_available_locales`, keyed by logical document, sticky across
|
|
280
|
+
* versions), so a workflow status change would falsely imply the edit is gated
|
|
281
|
+
* behind publish. It is not: the write is immediate and applies across every
|
|
282
|
+
* version. This service backs the admin path / available-locales widgets'
|
|
283
|
+
* direct-write Save (the `direct-write` and `both` dirty-reason cases). The
|
|
284
|
+
* public *advertised* set remains the intersection of `availableLocales` with
|
|
285
|
+
* the resolved version's completeness ledger. See docs/I18N.md.
|
|
286
|
+
*
|
|
287
|
+
* Flow:
|
|
288
|
+
* 1. `assertActorCanPerform('update')` — same auth gate as content writes.
|
|
289
|
+
* 2. Fetch the document to resolve its `source_locale` anchor + current path.
|
|
290
|
+
* 3. Path (when supplied): `resolvePathForUpdate` enforces the source-locale
|
|
291
|
+
* rule (translation-locale path edits are dropped with a warn); a real
|
|
292
|
+
* change is written via `updateDocumentPath`, mapping the unique-constraint
|
|
293
|
+
* violation to `ERR_PATH_CONFLICT`.
|
|
294
|
+
* 4. `availableLocales` (when supplied): rewritten wholesale via
|
|
295
|
+
* `setDocumentAvailableLocales`.
|
|
296
|
+
*
|
|
297
|
+
* No content hooks fire — these are not content writes. Accountability for
|
|
298
|
+
* these mutations is the job of the (planned) document-grain audit log.
|
|
299
|
+
*
|
|
300
|
+
* @throws {BylineError} ERR_NOT_FOUND if the document does not exist.
|
|
301
|
+
* @throws {BylineError} ERR_PATH_CONFLICT if the path is already in use.
|
|
302
|
+
*/
|
|
303
|
+
export declare function updateDocumentSystemFields(ctx: DocumentLifecycleContext, params: {
|
|
304
|
+
documentId: string;
|
|
305
|
+
locale?: string;
|
|
306
|
+
/**
|
|
307
|
+
* Explicit path override from the path widget. `null` / empty / omitted
|
|
308
|
+
* means "no path write" (the existing row stays sticky). A non-empty
|
|
309
|
+
* string is written when the request locale is the document's source
|
|
310
|
+
* locale; on a translation locale it is dropped with a warn.
|
|
311
|
+
*/
|
|
312
|
+
path?: string | null;
|
|
313
|
+
/**
|
|
314
|
+
* The editorial advertised-locale set from the available-locales widget.
|
|
315
|
+
* `undefined` means "no advertised-locale write"; an explicit array — `[]`
|
|
316
|
+
* included — replaces the set wholesale.
|
|
317
|
+
*/
|
|
318
|
+
availableLocales?: string[];
|
|
319
|
+
}): Promise<UpdateDocumentSystemFieldsResult>;
|
|
266
320
|
/**
|
|
267
321
|
* Change a document's workflow status.
|
|
268
322
|
*
|
|
@@ -460,6 +460,95 @@ export async function updateDocumentWithPatches(ctx, params) {
|
|
|
460
460
|
return { documentId, documentVersionId };
|
|
461
461
|
});
|
|
462
462
|
}
|
|
463
|
+
/**
|
|
464
|
+
* Write a document's system-managed, document-grain fields — `path` and the
|
|
465
|
+
* editorial `availableLocales` set — **without** minting a new version or
|
|
466
|
+
* touching workflow status.
|
|
467
|
+
*
|
|
468
|
+
* These fields are document-grain (they live in `byline_document_paths` and
|
|
469
|
+
* `byline_document_available_locales`, keyed by logical document, sticky across
|
|
470
|
+
* versions), so a workflow status change would falsely imply the edit is gated
|
|
471
|
+
* behind publish. It is not: the write is immediate and applies across every
|
|
472
|
+
* version. This service backs the admin path / available-locales widgets'
|
|
473
|
+
* direct-write Save (the `direct-write` and `both` dirty-reason cases). The
|
|
474
|
+
* public *advertised* set remains the intersection of `availableLocales` with
|
|
475
|
+
* the resolved version's completeness ledger. See docs/I18N.md.
|
|
476
|
+
*
|
|
477
|
+
* Flow:
|
|
478
|
+
* 1. `assertActorCanPerform('update')` — same auth gate as content writes.
|
|
479
|
+
* 2. Fetch the document to resolve its `source_locale` anchor + current path.
|
|
480
|
+
* 3. Path (when supplied): `resolvePathForUpdate` enforces the source-locale
|
|
481
|
+
* rule (translation-locale path edits are dropped with a warn); a real
|
|
482
|
+
* change is written via `updateDocumentPath`, mapping the unique-constraint
|
|
483
|
+
* violation to `ERR_PATH_CONFLICT`.
|
|
484
|
+
* 4. `availableLocales` (when supplied): rewritten wholesale via
|
|
485
|
+
* `setDocumentAvailableLocales`.
|
|
486
|
+
*
|
|
487
|
+
* No content hooks fire — these are not content writes. Accountability for
|
|
488
|
+
* these mutations is the job of the (planned) document-grain audit log.
|
|
489
|
+
*
|
|
490
|
+
* @throws {BylineError} ERR_NOT_FOUND if the document does not exist.
|
|
491
|
+
* @throws {BylineError} ERR_PATH_CONFLICT if the path is already in use.
|
|
492
|
+
*/
|
|
493
|
+
export async function updateDocumentSystemFields(ctx, params) {
|
|
494
|
+
return withLogContext({ domain: 'services', module: 'lifecycle', function: 'updateDocumentSystemFields' }, async () => {
|
|
495
|
+
const { db, collectionId, collectionPath, defaultLocale } = ctx;
|
|
496
|
+
assertActorCanPerform(ctx.requestContext, collectionPath, 'update');
|
|
497
|
+
const requestLocale = params.locale ?? defaultLocale;
|
|
498
|
+
// Resolve the document's source-locale anchor + current path. Both feed
|
|
499
|
+
// the path source-locale guard below; the fetch also asserts existence.
|
|
500
|
+
const latest = await db.queries.documents.getDocumentById({
|
|
501
|
+
collection_id: collectionId,
|
|
502
|
+
document_id: params.documentId,
|
|
503
|
+
locale: requestLocale,
|
|
504
|
+
reconstruct: true,
|
|
505
|
+
});
|
|
506
|
+
if (latest == null) {
|
|
507
|
+
throw ERR_NOT_FOUND({
|
|
508
|
+
message: 'document not found',
|
|
509
|
+
details: { documentId: params.documentId },
|
|
510
|
+
}).log(ctx.logger);
|
|
511
|
+
}
|
|
512
|
+
const originalData = latest;
|
|
513
|
+
const sourceLocale = originalData.source_locale ?? defaultLocale;
|
|
514
|
+
// Path: honour the same source-locale-only rule the versioned write
|
|
515
|
+
// uses. `resolvePathForUpdate` returns `undefined` to mean "skip the
|
|
516
|
+
// write" (null/empty override, or a translation-locale save).
|
|
517
|
+
const explicitPath = typeof params.path === 'string' && params.path.length > 0 ? params.path : null;
|
|
518
|
+
const pathForCommand = resolvePathForUpdate({
|
|
519
|
+
explicitPath,
|
|
520
|
+
currentPath: originalData.path,
|
|
521
|
+
requestLocale,
|
|
522
|
+
sourceLocale,
|
|
523
|
+
documentId: params.documentId,
|
|
524
|
+
logger: ctx.logger,
|
|
525
|
+
});
|
|
526
|
+
if (pathForCommand !== undefined) {
|
|
527
|
+
await db.commands.documents
|
|
528
|
+
.updateDocumentPath({
|
|
529
|
+
documentId: params.documentId,
|
|
530
|
+
collectionId,
|
|
531
|
+
locale: sourceLocale,
|
|
532
|
+
path: pathForCommand,
|
|
533
|
+
})
|
|
534
|
+
.catch((err) => rethrowPathConflict(err, pathForCommand, defaultLocale));
|
|
535
|
+
}
|
|
536
|
+
// Advertised locales: rewrite the document-grain set wholesale.
|
|
537
|
+
const availableLocalesWritten = params.availableLocales !== undefined;
|
|
538
|
+
if (params.availableLocales !== undefined) {
|
|
539
|
+
await db.commands.documents.setDocumentAvailableLocales({
|
|
540
|
+
documentId: params.documentId,
|
|
541
|
+
collectionId,
|
|
542
|
+
availableLocales: params.availableLocales,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
return {
|
|
546
|
+
documentId: params.documentId,
|
|
547
|
+
path: pathForCommand,
|
|
548
|
+
availableLocalesWritten,
|
|
549
|
+
};
|
|
550
|
+
});
|
|
551
|
+
}
|
|
463
552
|
/**
|
|
464
553
|
* Change a document's workflow status.
|
|
465
554
|
*
|
|
@@ -141,6 +141,8 @@ function makeMockAdapter(store = {}, pathByCollectionId = {}) {
|
|
|
141
141
|
collections: { create: vi.fn(), update: vi.fn(), delete: vi.fn() },
|
|
142
142
|
documents: {
|
|
143
143
|
createDocumentVersion: vi.fn(),
|
|
144
|
+
updateDocumentPath: vi.fn(),
|
|
145
|
+
setDocumentAvailableLocales: vi.fn(),
|
|
144
146
|
setDocumentStatus: vi.fn(),
|
|
145
147
|
archivePublishedVersions: vi.fn(),
|
|
146
148
|
softDeleteDocument: vi.fn(),
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@byline/core",
|
|
3
3
|
"private": false,
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.3.1",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=20.9.0"
|
|
8
8
|
},
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"sharp": "^0.34.5",
|
|
80
80
|
"uuid": "^14.0.0",
|
|
81
81
|
"zod": "^4.4.3",
|
|
82
|
-
"@byline/auth": "3.
|
|
82
|
+
"@byline/auth": "3.3.1"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"@biomejs/biome": "2.4.15",
|