@byline/core 3.0.1 → 3.0.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.
- package/dist/@types/collection-types.d.ts +8 -0
- package/dist/@types/db-types.d.ts +18 -0
- package/dist/services/collection-bootstrap.test.node.js +2 -0
- package/dist/services/discover-counter-groups.test.node.js +1 -0
- package/dist/services/document-lifecycle.d.ts +36 -0
- package/dist/services/document-lifecycle.js +104 -0
- package/dist/services/document-lifecycle.test.node.js +1 -0
- package/dist/services/field-upload.test.node.js +1 -0
- package/dist/services/populate.test.node.js +1 -0
- package/package.json +2 -2
|
@@ -360,6 +360,10 @@ export interface BeforeUpdateContext {
|
|
|
360
360
|
sourceLocale: string;
|
|
361
361
|
targetLocale: string;
|
|
362
362
|
};
|
|
363
|
+
/** Set only when the update originates from a Delete-Locale operation. */
|
|
364
|
+
deleteLocale?: {
|
|
365
|
+
locale: string;
|
|
366
|
+
};
|
|
363
367
|
}
|
|
364
368
|
/**
|
|
365
369
|
* Context passed to `afterUpdate` hooks.
|
|
@@ -385,6 +389,10 @@ export interface AfterUpdateContext {
|
|
|
385
389
|
sourceLocale: string;
|
|
386
390
|
targetLocale: string;
|
|
387
391
|
};
|
|
392
|
+
/** Mirrors `BeforeUpdateContext.deleteLocale`. */
|
|
393
|
+
deleteLocale?: {
|
|
394
|
+
locale: string;
|
|
395
|
+
};
|
|
388
396
|
}
|
|
389
397
|
/**
|
|
390
398
|
* Context passed to `beforeStatusChange` / `afterStatusChange` hooks.
|
|
@@ -373,6 +373,24 @@ export interface IDocumentCommands {
|
|
|
373
373
|
softDeleteDocument(params: {
|
|
374
374
|
document_id: string;
|
|
375
375
|
}): Promise<number>;
|
|
376
|
+
/**
|
|
377
|
+
* Remove one content locale's data from a document by writing a new
|
|
378
|
+
* immutable version that carries forward every store row except the target
|
|
379
|
+
* locale's (the `'all'` rows and all other locales are kept). The prior
|
|
380
|
+
* version still holds the deleted locale, so the operation is recoverable.
|
|
381
|
+
*
|
|
382
|
+
* `status` is the new version's status (the lifecycle service passes the
|
|
383
|
+
* workflow default — a fresh draft). Returns the new and previous version
|
|
384
|
+
* ids, or `null` when the document has no current version.
|
|
385
|
+
*/
|
|
386
|
+
deleteDocumentLocale(params: {
|
|
387
|
+
documentId: string;
|
|
388
|
+
locale: string;
|
|
389
|
+
status?: string;
|
|
390
|
+
}): Promise<{
|
|
391
|
+
newVersionId: string;
|
|
392
|
+
previousVersionId: string;
|
|
393
|
+
} | null>;
|
|
376
394
|
/**
|
|
377
395
|
* Write the fractional-index `order_key` on a single `byline_documents`
|
|
378
396
|
* row. Used by the reorder server fn for `orderable: true` collections.
|
|
@@ -42,6 +42,7 @@ function createMockDb(options) {
|
|
|
42
42
|
setDocumentStatus: vi.fn(fail),
|
|
43
43
|
archivePublishedVersions: vi.fn(fail),
|
|
44
44
|
softDeleteDocument: vi.fn(fail),
|
|
45
|
+
deleteDocumentLocale: vi.fn(fail),
|
|
45
46
|
setOrderKey: vi.fn(fail),
|
|
46
47
|
},
|
|
47
48
|
counters: {
|
|
@@ -183,6 +184,7 @@ describe('ensureCollections', () => {
|
|
|
183
184
|
setDocumentStatus: vi.fn(),
|
|
184
185
|
archivePublishedVersions: vi.fn(),
|
|
185
186
|
softDeleteDocument: vi.fn(),
|
|
187
|
+
deleteDocumentLocale: vi.fn(),
|
|
186
188
|
setOrderKey: vi.fn(),
|
|
187
189
|
},
|
|
188
190
|
counters: {
|
|
@@ -135,6 +135,13 @@ export interface CopyToLocaleResult {
|
|
|
135
135
|
*/
|
|
136
136
|
fieldsUpdated: number;
|
|
137
137
|
}
|
|
138
|
+
export interface DeleteLocaleResult {
|
|
139
|
+
documentId: string;
|
|
140
|
+
/** The newly-created version that omits the deleted locale's content. */
|
|
141
|
+
documentVersionId: string;
|
|
142
|
+
/** The content locale that was removed. */
|
|
143
|
+
locale: string;
|
|
144
|
+
}
|
|
138
145
|
export interface DuplicateDocumentResult {
|
|
139
146
|
/** The newly-created document's id. */
|
|
140
147
|
documentId: string;
|
|
@@ -421,3 +428,32 @@ export declare function copyToLocale(ctx: DocumentLifecycleContext, params: {
|
|
|
421
428
|
targetLocale: string;
|
|
422
429
|
overwrite: boolean;
|
|
423
430
|
}): Promise<CopyToLocaleResult>;
|
|
431
|
+
/**
|
|
432
|
+
* Remove one content locale's data from a document, in place on the same
|
|
433
|
+
* document, by writing a new immutable version that omits that locale's
|
|
434
|
+
* store rows (every other locale and all non-localized `'all'` rows are
|
|
435
|
+
* carried forward by the storage primitive).
|
|
436
|
+
*
|
|
437
|
+
* The default content locale is the document's anchor (path + source_locale)
|
|
438
|
+
* and can never be removed — rejected up front. The new version lands as the
|
|
439
|
+
* workflow's default status (a fresh draft), exactly like `copyToLocale`: the
|
|
440
|
+
* previously-published version keeps serving — including the locale being
|
|
441
|
+
* removed — until the new version is reviewed and published. The deletion is
|
|
442
|
+
* recoverable: the prior version still holds the locale, so restoring it
|
|
443
|
+
* brings the content back.
|
|
444
|
+
*
|
|
445
|
+
* Flow:
|
|
446
|
+
* 1. `assertActorCanPerform('update')` — removing a translation is an edit.
|
|
447
|
+
* 2. Reject `locale === defaultLocale`.
|
|
448
|
+
* 3. Read the document in the target locale (validates existence; supplies
|
|
449
|
+
* `originalData` for hooks and the availability set for the presence
|
|
450
|
+
* check).
|
|
451
|
+
* 4. Reject when the locale has no content to delete.
|
|
452
|
+
* 5. `hooks.beforeUpdate({ …, deleteLocale: { locale } })`.
|
|
453
|
+
* 6. `db.commands.documents.deleteDocumentLocale({ …, status: default })`.
|
|
454
|
+
* 7. `hooks.afterUpdate({ …, deleteLocale: { locale } })`.
|
|
455
|
+
*/
|
|
456
|
+
export declare function deleteLocale(ctx: DocumentLifecycleContext, params: {
|
|
457
|
+
documentId: string;
|
|
458
|
+
locale: string;
|
|
459
|
+
}): Promise<DeleteLocaleResult>;
|
|
@@ -1341,3 +1341,107 @@ export async function copyToLocale(ctx, params) {
|
|
|
1341
1341
|
};
|
|
1342
1342
|
});
|
|
1343
1343
|
}
|
|
1344
|
+
// ---------------------------------------------------------------------------
|
|
1345
|
+
// deleteLocale
|
|
1346
|
+
// ---------------------------------------------------------------------------
|
|
1347
|
+
/**
|
|
1348
|
+
* Remove one content locale's data from a document, in place on the same
|
|
1349
|
+
* document, by writing a new immutable version that omits that locale's
|
|
1350
|
+
* store rows (every other locale and all non-localized `'all'` rows are
|
|
1351
|
+
* carried forward by the storage primitive).
|
|
1352
|
+
*
|
|
1353
|
+
* The default content locale is the document's anchor (path + source_locale)
|
|
1354
|
+
* and can never be removed — rejected up front. The new version lands as the
|
|
1355
|
+
* workflow's default status (a fresh draft), exactly like `copyToLocale`: the
|
|
1356
|
+
* previously-published version keeps serving — including the locale being
|
|
1357
|
+
* removed — until the new version is reviewed and published. The deletion is
|
|
1358
|
+
* recoverable: the prior version still holds the locale, so restoring it
|
|
1359
|
+
* brings the content back.
|
|
1360
|
+
*
|
|
1361
|
+
* Flow:
|
|
1362
|
+
* 1. `assertActorCanPerform('update')` — removing a translation is an edit.
|
|
1363
|
+
* 2. Reject `locale === defaultLocale`.
|
|
1364
|
+
* 3. Read the document in the target locale (validates existence; supplies
|
|
1365
|
+
* `originalData` for hooks and the availability set for the presence
|
|
1366
|
+
* check).
|
|
1367
|
+
* 4. Reject when the locale has no content to delete.
|
|
1368
|
+
* 5. `hooks.beforeUpdate({ …, deleteLocale: { locale } })`.
|
|
1369
|
+
* 6. `db.commands.documents.deleteDocumentLocale({ …, status: default })`.
|
|
1370
|
+
* 7. `hooks.afterUpdate({ …, deleteLocale: { locale } })`.
|
|
1371
|
+
*/
|
|
1372
|
+
export async function deleteLocale(ctx, params) {
|
|
1373
|
+
return withLogContext({ domain: 'services', module: 'lifecycle', function: 'deleteLocale' }, async () => {
|
|
1374
|
+
const { db, definition, collectionId, collectionPath, defaultLocale } = ctx;
|
|
1375
|
+
assertActorCanPerform(ctx.requestContext, collectionPath, 'update');
|
|
1376
|
+
// The default locale anchors the document's path and source_locale —
|
|
1377
|
+
// it cannot be deleted (the other locales fall back to it).
|
|
1378
|
+
if (params.locale === defaultLocale) {
|
|
1379
|
+
throw ERR_VALIDATION({
|
|
1380
|
+
message: `cannot delete the default content locale ('${defaultLocale}')`,
|
|
1381
|
+
details: { documentId: params.documentId, locale: params.locale, collectionPath },
|
|
1382
|
+
}).log(ctx.logger);
|
|
1383
|
+
}
|
|
1384
|
+
// Read the document in the locale being removed — validates existence,
|
|
1385
|
+
// supplies originalData for hooks, and the availability set for the
|
|
1386
|
+
// content-presence check below.
|
|
1387
|
+
const target = await db.queries.documents.getDocumentById({
|
|
1388
|
+
collection_id: collectionId,
|
|
1389
|
+
document_id: params.documentId,
|
|
1390
|
+
locale: params.locale,
|
|
1391
|
+
reconstruct: true,
|
|
1392
|
+
lenient: true,
|
|
1393
|
+
requestContext: ctx.requestContext,
|
|
1394
|
+
});
|
|
1395
|
+
if (target == null) {
|
|
1396
|
+
throw ERR_NOT_FOUND({
|
|
1397
|
+
message: 'document not found',
|
|
1398
|
+
details: { documentId: params.documentId, collectionPath },
|
|
1399
|
+
}).log(ctx.logger);
|
|
1400
|
+
}
|
|
1401
|
+
const targetRecord = target;
|
|
1402
|
+
// `_availableVersionLocales` is the derived (path-coverage) set; it is
|
|
1403
|
+
// the same source the editor's Delete-Locale picker is built from, so a
|
|
1404
|
+
// locale offered in the UI resolves here. A partially-translated locale
|
|
1405
|
+
// that never reached full coverage is not deletable through this path.
|
|
1406
|
+
const available = targetRecord._availableVersionLocales ?? [];
|
|
1407
|
+
if (!available.includes(params.locale)) {
|
|
1408
|
+
throw ERR_NOT_FOUND({
|
|
1409
|
+
message: `locale '${params.locale}' has no content to delete`,
|
|
1410
|
+
details: { documentId: params.documentId, locale: params.locale, collectionPath },
|
|
1411
|
+
}).log(ctx.logger);
|
|
1412
|
+
}
|
|
1413
|
+
const hooks = definition.hooks;
|
|
1414
|
+
const deleteLocaleMarker = { locale: params.locale };
|
|
1415
|
+
const originalData = targetRecord.fields ?? {};
|
|
1416
|
+
await invokeHook(hooks?.beforeUpdate, {
|
|
1417
|
+
data: originalData,
|
|
1418
|
+
originalData,
|
|
1419
|
+
collectionPath,
|
|
1420
|
+
deleteLocale: deleteLocaleMarker,
|
|
1421
|
+
});
|
|
1422
|
+
const result = await db.commands.documents.deleteDocumentLocale({
|
|
1423
|
+
documentId: params.documentId,
|
|
1424
|
+
locale: params.locale,
|
|
1425
|
+
status: getDefaultStatus(definition),
|
|
1426
|
+
});
|
|
1427
|
+
if (result == null) {
|
|
1428
|
+
throw ERR_NOT_FOUND({
|
|
1429
|
+
message: 'document not found',
|
|
1430
|
+
details: { documentId: params.documentId, collectionPath },
|
|
1431
|
+
}).log(ctx.logger);
|
|
1432
|
+
}
|
|
1433
|
+
await invokeHook(hooks?.afterUpdate, {
|
|
1434
|
+
data: originalData,
|
|
1435
|
+
originalData,
|
|
1436
|
+
collectionPath,
|
|
1437
|
+
documentId: params.documentId,
|
|
1438
|
+
documentVersionId: result.newVersionId,
|
|
1439
|
+
deleteLocale: deleteLocaleMarker,
|
|
1440
|
+
});
|
|
1441
|
+
return {
|
|
1442
|
+
documentId: params.documentId,
|
|
1443
|
+
documentVersionId: result.newVersionId,
|
|
1444
|
+
locale: params.locale,
|
|
1445
|
+
};
|
|
1446
|
+
});
|
|
1447
|
+
}
|
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.0.
|
|
5
|
+
"version": "3.0.2",
|
|
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.0.
|
|
82
|
+
"@byline/auth": "3.0.2"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"@biomejs/biome": "2.4.15",
|