@byline/core 3.7.0 → 3.8.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/@types/db-types.d.ts +2 -0
- package/dist/schemas/zod/builder.d.ts +42 -0
- package/dist/schemas/zod/builder.js +5 -0
- package/dist/services/document-lifecycle/copy-to-locale.js +2 -1
- package/dist/services/document-lifecycle/create.js +2 -1
- package/dist/services/document-lifecycle/delete-locale.js +2 -1
- package/dist/services/document-lifecycle/duplicate.js +3 -1
- package/dist/services/document-lifecycle/internals.d.ts +8 -0
- package/dist/services/document-lifecycle/internals.js +10 -0
- package/dist/services/document-lifecycle/restore.js +2 -1
- package/dist/services/document-lifecycle/update.js +3 -1
- package/dist/services/document-lifecycle.test.node.js +21 -0
- package/package.json +2 -2
|
@@ -415,6 +415,8 @@ export interface IDocumentCommands {
|
|
|
415
415
|
documentId: string;
|
|
416
416
|
locale: string;
|
|
417
417
|
status?: string;
|
|
418
|
+
/** Acting user id for the version audit trail (`created_by`). See docs/AUDIT.md. */
|
|
419
|
+
createdBy?: string;
|
|
418
420
|
}): Promise<{
|
|
419
421
|
newVersionId: string;
|
|
420
422
|
previousVersionId: string;
|
|
@@ -12,6 +12,8 @@ export declare const createBaseSchema: (collection?: CollectionDefinition) => z.
|
|
|
12
12
|
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
13
13
|
createdAt: z.ZodISODateTime;
|
|
14
14
|
updatedAt: z.ZodISODateTime;
|
|
15
|
+
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
16
|
+
eventType: z.ZodOptional<z.ZodString>;
|
|
15
17
|
}, z.core.$strip>;
|
|
16
18
|
export declare const createFieldsSchema: (fields: Field[], strict?: boolean) => z.ZodObject<{
|
|
17
19
|
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
@@ -44,6 +46,8 @@ export declare const createCollectionSchemasForPath: (path: string) => {
|
|
|
44
46
|
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
45
47
|
createdAt: z.ZodISODateTime;
|
|
46
48
|
updatedAt: z.ZodISODateTime;
|
|
49
|
+
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
50
|
+
eventType: z.ZodOptional<z.ZodString>;
|
|
47
51
|
}, z.core.$strip>;
|
|
48
52
|
fields: z.ZodObject<{
|
|
49
53
|
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
@@ -62,6 +66,8 @@ export declare const createCollectionSchemasForPath: (path: string) => {
|
|
|
62
66
|
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
63
67
|
createdAt: z.ZodISODateTime;
|
|
64
68
|
updatedAt: z.ZodISODateTime;
|
|
69
|
+
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
70
|
+
eventType: z.ZodOptional<z.ZodString>;
|
|
65
71
|
}, z.core.$strip>;
|
|
66
72
|
list: z.ZodObject<{
|
|
67
73
|
docs: z.ZodArray<z.ZodObject<{
|
|
@@ -78,6 +84,8 @@ export declare const createCollectionSchemasForPath: (path: string) => {
|
|
|
78
84
|
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
79
85
|
createdAt: z.ZodISODateTime;
|
|
80
86
|
updatedAt: z.ZodISODateTime;
|
|
87
|
+
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
88
|
+
eventType: z.ZodOptional<z.ZodString>;
|
|
81
89
|
}, z.core.$strip>>;
|
|
82
90
|
meta: z.ZodObject<{
|
|
83
91
|
page: z.ZodNumber;
|
|
@@ -113,6 +121,8 @@ export declare const createCollectionSchemasForPath: (path: string) => {
|
|
|
113
121
|
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
114
122
|
createdAt: z.ZodISODateTime;
|
|
115
123
|
updatedAt: z.ZodISODateTime;
|
|
124
|
+
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
125
|
+
eventType: z.ZodOptional<z.ZodString>;
|
|
116
126
|
}, z.core.$strip>>;
|
|
117
127
|
meta: z.ZodObject<{
|
|
118
128
|
page: z.ZodNumber;
|
|
@@ -140,6 +150,8 @@ export declare const createCollectionSchemasForPath: (path: string) => {
|
|
|
140
150
|
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
141
151
|
createdAt: z.ZodISODateTime;
|
|
142
152
|
updatedAt: z.ZodISODateTime;
|
|
153
|
+
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
154
|
+
eventType: z.ZodOptional<z.ZodString>;
|
|
143
155
|
}, z.core.$strip>;
|
|
144
156
|
update: z.ZodObject<{
|
|
145
157
|
[x: string]: z.ZodOptional<z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>>;
|
|
@@ -157,6 +169,8 @@ export declare const createCollectionSchemas: (collection: CollectionDefinition)
|
|
|
157
169
|
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
158
170
|
createdAt: z.ZodISODateTime;
|
|
159
171
|
updatedAt: z.ZodISODateTime;
|
|
172
|
+
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
173
|
+
eventType: z.ZodOptional<z.ZodString>;
|
|
160
174
|
}, z.core.$strip>;
|
|
161
175
|
fields: z.ZodObject<{
|
|
162
176
|
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
@@ -175,6 +189,8 @@ export declare const createCollectionSchemas: (collection: CollectionDefinition)
|
|
|
175
189
|
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
176
190
|
createdAt: z.ZodISODateTime;
|
|
177
191
|
updatedAt: z.ZodISODateTime;
|
|
192
|
+
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
193
|
+
eventType: z.ZodOptional<z.ZodString>;
|
|
178
194
|
}, z.core.$strip>;
|
|
179
195
|
list: z.ZodObject<{
|
|
180
196
|
docs: z.ZodArray<z.ZodObject<{
|
|
@@ -191,6 +207,8 @@ export declare const createCollectionSchemas: (collection: CollectionDefinition)
|
|
|
191
207
|
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
192
208
|
createdAt: z.ZodISODateTime;
|
|
193
209
|
updatedAt: z.ZodISODateTime;
|
|
210
|
+
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
211
|
+
eventType: z.ZodOptional<z.ZodString>;
|
|
194
212
|
}, z.core.$strip>>;
|
|
195
213
|
meta: z.ZodObject<{
|
|
196
214
|
page: z.ZodNumber;
|
|
@@ -226,6 +244,8 @@ export declare const createCollectionSchemas: (collection: CollectionDefinition)
|
|
|
226
244
|
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
227
245
|
createdAt: z.ZodISODateTime;
|
|
228
246
|
updatedAt: z.ZodISODateTime;
|
|
247
|
+
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
248
|
+
eventType: z.ZodOptional<z.ZodString>;
|
|
229
249
|
}, z.core.$strip>>;
|
|
230
250
|
meta: z.ZodObject<{
|
|
231
251
|
page: z.ZodNumber;
|
|
@@ -253,6 +273,8 @@ export declare const createCollectionSchemas: (collection: CollectionDefinition)
|
|
|
253
273
|
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
254
274
|
createdAt: z.ZodISODateTime;
|
|
255
275
|
updatedAt: z.ZodISODateTime;
|
|
276
|
+
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
277
|
+
eventType: z.ZodOptional<z.ZodString>;
|
|
256
278
|
}, z.core.$strip>;
|
|
257
279
|
update: z.ZodObject<{
|
|
258
280
|
[x: string]: z.ZodOptional<z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>>;
|
|
@@ -270,6 +292,8 @@ export declare const createTypedCollectionSchemas: (collection: CollectionDefini
|
|
|
270
292
|
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
271
293
|
createdAt: z.ZodISODateTime;
|
|
272
294
|
updatedAt: z.ZodISODateTime;
|
|
295
|
+
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
296
|
+
eventType: z.ZodOptional<z.ZodString>;
|
|
273
297
|
}, z.core.$strip>;
|
|
274
298
|
fields: z.ZodObject<{
|
|
275
299
|
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
@@ -288,6 +312,8 @@ export declare const createTypedCollectionSchemas: (collection: CollectionDefini
|
|
|
288
312
|
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
289
313
|
createdAt: z.ZodISODateTime;
|
|
290
314
|
updatedAt: z.ZodISODateTime;
|
|
315
|
+
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
316
|
+
eventType: z.ZodOptional<z.ZodString>;
|
|
291
317
|
}, z.core.$strip>;
|
|
292
318
|
list: z.ZodObject<{
|
|
293
319
|
docs: z.ZodArray<z.ZodObject<{
|
|
@@ -304,6 +330,8 @@ export declare const createTypedCollectionSchemas: (collection: CollectionDefini
|
|
|
304
330
|
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
305
331
|
createdAt: z.ZodISODateTime;
|
|
306
332
|
updatedAt: z.ZodISODateTime;
|
|
333
|
+
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
334
|
+
eventType: z.ZodOptional<z.ZodString>;
|
|
307
335
|
}, z.core.$strip>>;
|
|
308
336
|
meta: z.ZodObject<{
|
|
309
337
|
page: z.ZodNumber;
|
|
@@ -339,6 +367,8 @@ export declare const createTypedCollectionSchemas: (collection: CollectionDefini
|
|
|
339
367
|
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
340
368
|
createdAt: z.ZodISODateTime;
|
|
341
369
|
updatedAt: z.ZodISODateTime;
|
|
370
|
+
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
371
|
+
eventType: z.ZodOptional<z.ZodString>;
|
|
342
372
|
}, z.core.$strip>>;
|
|
343
373
|
meta: z.ZodObject<{
|
|
344
374
|
page: z.ZodNumber;
|
|
@@ -366,6 +396,8 @@ export declare const createTypedCollectionSchemas: (collection: CollectionDefini
|
|
|
366
396
|
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
367
397
|
createdAt: z.ZodISODateTime;
|
|
368
398
|
updatedAt: z.ZodISODateTime;
|
|
399
|
+
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
400
|
+
eventType: z.ZodOptional<z.ZodString>;
|
|
369
401
|
}, z.core.$strip>;
|
|
370
402
|
update: z.ZodObject<{
|
|
371
403
|
[x: string]: z.ZodOptional<z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>>;
|
|
@@ -383,6 +415,8 @@ export declare const createTypedCollectionSchemasForPath: (path: string) => {
|
|
|
383
415
|
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
384
416
|
createdAt: z.ZodISODateTime;
|
|
385
417
|
updatedAt: z.ZodISODateTime;
|
|
418
|
+
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
419
|
+
eventType: z.ZodOptional<z.ZodString>;
|
|
386
420
|
}, z.core.$strip>;
|
|
387
421
|
fields: z.ZodObject<{
|
|
388
422
|
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
@@ -401,6 +435,8 @@ export declare const createTypedCollectionSchemasForPath: (path: string) => {
|
|
|
401
435
|
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
402
436
|
createdAt: z.ZodISODateTime;
|
|
403
437
|
updatedAt: z.ZodISODateTime;
|
|
438
|
+
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
439
|
+
eventType: z.ZodOptional<z.ZodString>;
|
|
404
440
|
}, z.core.$strip>;
|
|
405
441
|
list: z.ZodObject<{
|
|
406
442
|
docs: z.ZodArray<z.ZodObject<{
|
|
@@ -417,6 +453,8 @@ export declare const createTypedCollectionSchemasForPath: (path: string) => {
|
|
|
417
453
|
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
418
454
|
createdAt: z.ZodISODateTime;
|
|
419
455
|
updatedAt: z.ZodISODateTime;
|
|
456
|
+
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
457
|
+
eventType: z.ZodOptional<z.ZodString>;
|
|
420
458
|
}, z.core.$strip>>;
|
|
421
459
|
meta: z.ZodObject<{
|
|
422
460
|
page: z.ZodNumber;
|
|
@@ -452,6 +490,8 @@ export declare const createTypedCollectionSchemasForPath: (path: string) => {
|
|
|
452
490
|
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
453
491
|
createdAt: z.ZodISODateTime;
|
|
454
492
|
updatedAt: z.ZodISODateTime;
|
|
493
|
+
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
494
|
+
eventType: z.ZodOptional<z.ZodString>;
|
|
455
495
|
}, z.core.$strip>>;
|
|
456
496
|
meta: z.ZodObject<{
|
|
457
497
|
page: z.ZodNumber;
|
|
@@ -479,6 +519,8 @@ export declare const createTypedCollectionSchemasForPath: (path: string) => {
|
|
|
479
519
|
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
480
520
|
createdAt: z.ZodISODateTime;
|
|
481
521
|
updatedAt: z.ZodISODateTime;
|
|
522
|
+
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
523
|
+
eventType: z.ZodOptional<z.ZodString>;
|
|
482
524
|
}, z.core.$strip>;
|
|
483
525
|
update: z.ZodObject<{
|
|
484
526
|
[x: string]: z.ZodOptional<z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>>;
|
|
@@ -205,6 +205,11 @@ export const createBaseSchema = (collection) => {
|
|
|
205
205
|
hasPublishedVersion: z.boolean().optional(),
|
|
206
206
|
createdAt: z.iso.datetime(),
|
|
207
207
|
updatedAt: z.iso.datetime(),
|
|
208
|
+
// Version audit metadata — acting user + action (see docs/AUDIT.md — Workstream 1).
|
|
209
|
+
// Declared so list/get/history responses carry them through the
|
|
210
|
+
// server-fn parse; Zod would otherwise strip them as undeclared keys.
|
|
211
|
+
createdBy: z.uuid().optional(),
|
|
212
|
+
eventType: z.string().optional(),
|
|
208
213
|
});
|
|
209
214
|
};
|
|
210
215
|
// Create field schemas for a collection.
|
|
@@ -10,7 +10,7 @@ import { assertActorCanPerform } from '../../auth/assert-actor-can-perform.js';
|
|
|
10
10
|
import { ERR_NOT_FOUND, ERR_VALIDATION } from '../../lib/errors.js';
|
|
11
11
|
import { withLogContext } from '../../lib/logger.js';
|
|
12
12
|
import { getDefaultStatus } from '../../workflow/workflow.js';
|
|
13
|
-
import { applyRichTextEmbed, extractVersionId, invokeHook } from './internals.js';
|
|
13
|
+
import { actorId, applyRichTextEmbed, extractVersionId, invokeHook } from './internals.js';
|
|
14
14
|
import { mergeLocaleData } from './merge-locale-data.js';
|
|
15
15
|
/**
|
|
16
16
|
* Copy a document's content from one locale into another, in place on
|
|
@@ -133,6 +133,7 @@ export async function copyToLocale(ctx, params) {
|
|
|
133
133
|
status: getDefaultStatus(definition),
|
|
134
134
|
locale: params.targetLocale,
|
|
135
135
|
previousVersionId,
|
|
136
|
+
createdBy: actorId(ctx),
|
|
136
137
|
});
|
|
137
138
|
const documentVersionId = extractVersionId(writeResult.document);
|
|
138
139
|
await invokeHook(hooks?.afterUpdate, {
|
|
@@ -13,7 +13,7 @@ import { normaliseDateFields } from '../../utils/normalise-dates.js';
|
|
|
13
13
|
import { slugify } from '../../utils/slugify.js';
|
|
14
14
|
import { getDefaultStatus } from '../../workflow/workflow.js';
|
|
15
15
|
import { assignCounterValues } from '../assign-counter-values.js';
|
|
16
|
-
import { applyRichTextEmbed, derivePath, extractDocumentId, extractVersionId, invokeHook, maybeAppendOrderKey, rethrowPathConflict, } from './internals.js';
|
|
16
|
+
import { actorId, applyRichTextEmbed, derivePath, extractDocumentId, extractVersionId, invokeHook, maybeAppendOrderKey, rethrowPathConflict, } from './internals.js';
|
|
17
17
|
/**
|
|
18
18
|
* Create a new document.
|
|
19
19
|
*
|
|
@@ -75,6 +75,7 @@ export async function createDocument(ctx, params) {
|
|
|
75
75
|
status: params.status ?? data.status ?? getDefaultStatus(definition),
|
|
76
76
|
locale: params.locale ?? defaultLocale,
|
|
77
77
|
orderKey,
|
|
78
|
+
createdBy: actorId(ctx),
|
|
78
79
|
})
|
|
79
80
|
.catch((err) => rethrowPathConflict(err, resolvedPath, defaultLocale));
|
|
80
81
|
const documentId = extractDocumentId(result.document);
|
|
@@ -10,7 +10,7 @@ import { assertActorCanPerform } from '../../auth/assert-actor-can-perform.js';
|
|
|
10
10
|
import { ERR_NOT_FOUND, ERR_VALIDATION } from '../../lib/errors.js';
|
|
11
11
|
import { withLogContext } from '../../lib/logger.js';
|
|
12
12
|
import { getDefaultStatus } from '../../workflow/workflow.js';
|
|
13
|
-
import { invokeHook } from './internals.js';
|
|
13
|
+
import { actorId, invokeHook } from './internals.js';
|
|
14
14
|
/**
|
|
15
15
|
* Remove one content locale's data from a document, in place on the same
|
|
16
16
|
* document, by writing a new immutable version that omits that locale's
|
|
@@ -90,6 +90,7 @@ export async function deleteLocale(ctx, params) {
|
|
|
90
90
|
documentId: params.documentId,
|
|
91
91
|
locale: params.locale,
|
|
92
92
|
status: getDefaultStatus(definition),
|
|
93
|
+
createdBy: actorId(ctx),
|
|
93
94
|
});
|
|
94
95
|
if (result == null) {
|
|
95
96
|
throw ERR_NOT_FOUND({
|
|
@@ -12,7 +12,7 @@ import { withLogContext } from '../../lib/logger.js';
|
|
|
12
12
|
import { slugify } from '../../utils/slugify.js';
|
|
13
13
|
import { getDefaultStatus } from '../../workflow/workflow.js';
|
|
14
14
|
import { assignCounterValues } from '../assign-counter-values.js';
|
|
15
|
-
import { applyRichTextEmbed, derivePath, extractDocumentId, extractVersionId, invokeHook, isPathConflictError, maybeAppendOrderKey, rethrowPathConflict, stripMetaIdsInPlace, } from './internals.js';
|
|
15
|
+
import { actorId, applyRichTextEmbed, derivePath, extractDocumentId, extractVersionId, invokeHook, isPathConflictError, maybeAppendOrderKey, rethrowPathConflict, stripMetaIdsInPlace, } from './internals.js';
|
|
16
16
|
/**
|
|
17
17
|
* Apply the `" (copy)"` suffix to the configured `useAsTitle` field on a
|
|
18
18
|
* duplicate's data tree. Handles both shapes:
|
|
@@ -170,6 +170,7 @@ export async function duplicateDocument(ctx, params) {
|
|
|
170
170
|
status: defaultStatus,
|
|
171
171
|
locale: 'all',
|
|
172
172
|
orderKey,
|
|
173
|
+
createdBy: actorId(ctx),
|
|
173
174
|
})
|
|
174
175
|
.catch((err) => rethrowPathConflict(err, finalPath, defaultLocale));
|
|
175
176
|
}
|
|
@@ -194,6 +195,7 @@ export async function duplicateDocument(ctx, params) {
|
|
|
194
195
|
status: defaultStatus,
|
|
195
196
|
locale: 'all',
|
|
196
197
|
orderKey,
|
|
198
|
+
createdBy: actorId(ctx),
|
|
197
199
|
})
|
|
198
200
|
.catch((retryErr) => rethrowPathConflict(retryErr, finalPath, defaultLocale));
|
|
199
201
|
}
|
|
@@ -15,6 +15,14 @@ import { type CollectionDefinition, type CollectionHookSlot } from '../../@types
|
|
|
15
15
|
import type { BylineLogger } from '../../lib/logger.js';
|
|
16
16
|
import type { SlugifierFn } from '../../utils/slugify.js';
|
|
17
17
|
import type { DocumentLifecycleContext } from './context.js';
|
|
18
|
+
/**
|
|
19
|
+
* The acting user's id for the version audit trail (`created_by` on
|
|
20
|
+
* `byline_document_versions`). `undefined` for internal-tooling callers
|
|
21
|
+
* without a `requestContext` (seeds, migrations — the documented escape
|
|
22
|
+
* hatch), which persists as NULL. Both actor realms (`AdminAuth`,
|
|
23
|
+
* `UserAuth`) carry `id`. See docs/AUDIT.md — Workstream 1.
|
|
24
|
+
*/
|
|
25
|
+
export declare function actorId(ctx: DocumentLifecycleContext): string | undefined;
|
|
18
26
|
/**
|
|
19
27
|
* Safely invoke an optional hook slot, awaiting the result if it returns a
|
|
20
28
|
* Promise. When the slot is an array of functions they are executed
|
|
@@ -17,6 +17,16 @@ import { ERR_PATH_CONFLICT, ErrorCodes } from '../../lib/errors.js';
|
|
|
17
17
|
import { generateKeyBetween } from '../../lib/fractional-index.js';
|
|
18
18
|
import { createReadContext } from '../populate.js';
|
|
19
19
|
import { embedRichTextFields } from '../richtext-embed.js';
|
|
20
|
+
/**
|
|
21
|
+
* The acting user's id for the version audit trail (`created_by` on
|
|
22
|
+
* `byline_document_versions`). `undefined` for internal-tooling callers
|
|
23
|
+
* without a `requestContext` (seeds, migrations — the documented escape
|
|
24
|
+
* hatch), which persists as NULL. Both actor realms (`AdminAuth`,
|
|
25
|
+
* `UserAuth`) carry `id`. See docs/AUDIT.md — Workstream 1.
|
|
26
|
+
*/
|
|
27
|
+
export function actorId(ctx) {
|
|
28
|
+
return ctx.requestContext?.actor?.id;
|
|
29
|
+
}
|
|
20
30
|
/**
|
|
21
31
|
* Safely invoke an optional hook slot, awaiting the result if it returns a
|
|
22
32
|
* Promise. When the slot is an array of functions they are executed
|
|
@@ -10,7 +10,7 @@ import { assertActorCanPerform } from '../../auth/assert-actor-can-perform.js';
|
|
|
10
10
|
import { ERR_INVALID_TRANSITION, ERR_NOT_FOUND, ERR_VALIDATION } from '../../lib/errors.js';
|
|
11
11
|
import { withLogContext } from '../../lib/logger.js';
|
|
12
12
|
import { getDefaultStatus } from '../../workflow/workflow.js';
|
|
13
|
-
import { applyRichTextEmbed, extractDocumentId, extractVersionId, invokeHook } from './internals.js';
|
|
13
|
+
import { actorId, applyRichTextEmbed, extractDocumentId, extractVersionId, invokeHook, } from './internals.js';
|
|
14
14
|
/**
|
|
15
15
|
* Restore a historical document version as the new current version.
|
|
16
16
|
*
|
|
@@ -139,6 +139,7 @@ export async function restoreDocumentVersion(ctx, params) {
|
|
|
139
139
|
status: getDefaultStatus(definition),
|
|
140
140
|
locale: 'all',
|
|
141
141
|
previousVersionId: currentMeta.document_version_id,
|
|
142
|
+
createdBy: actorId(ctx),
|
|
142
143
|
});
|
|
143
144
|
const documentId = extractDocumentId(result.document) || params.documentId;
|
|
144
145
|
const documentVersionId = extractVersionId(result.document);
|
|
@@ -13,7 +13,7 @@ import { applyPatches } from '../../patches/index.js';
|
|
|
13
13
|
import { normaliseDateFields } from '../../utils/normalise-dates.js';
|
|
14
14
|
import { getDefaultStatus } from '../../workflow/workflow.js';
|
|
15
15
|
import { assignCounterValues } from '../assign-counter-values.js';
|
|
16
|
-
import { applyRichTextEmbed, extractDocumentId, extractVersionId, invokeHook, resolvePathForUpdate, rethrowPathConflict, } from './internals.js';
|
|
16
|
+
import { actorId, applyRichTextEmbed, extractDocumentId, extractVersionId, invokeHook, resolvePathForUpdate, rethrowPathConflict, } from './internals.js';
|
|
17
17
|
/**
|
|
18
18
|
* Update a document via full replacement (PUT semantics).
|
|
19
19
|
*
|
|
@@ -85,6 +85,7 @@ export async function updateDocument(ctx, params) {
|
|
|
85
85
|
status: defaultStatus,
|
|
86
86
|
locale: requestLocale,
|
|
87
87
|
previousVersionId: originalData.document_version_id,
|
|
88
|
+
createdBy: actorId(ctx),
|
|
88
89
|
})
|
|
89
90
|
.catch((err) => rethrowPathConflict(err, pathForCommand ?? '', defaultLocale));
|
|
90
91
|
const documentId = extractDocumentId(result.document) || params.documentId;
|
|
@@ -198,6 +199,7 @@ export async function updateDocumentWithPatches(ctx, params) {
|
|
|
198
199
|
status: defaultStatus,
|
|
199
200
|
locale: requestLocale,
|
|
200
201
|
previousVersionId: originalData.document_version_id,
|
|
202
|
+
createdBy: actorId(ctx),
|
|
201
203
|
})
|
|
202
204
|
.catch((err) => rethrowPathConflict(err, pathForCommand ?? '', defaultLocale));
|
|
203
205
|
const documentId = extractDocumentId(result.document) || params.documentId;
|
|
@@ -139,6 +139,17 @@ describe('Document lifecycle service', () => {
|
|
|
139
139
|
expect(result.documentId).toBe('doc-1');
|
|
140
140
|
expect(result.documentVersionId).toBe('ver-1');
|
|
141
141
|
});
|
|
142
|
+
it('passes the acting user id as createdBy for the audit trail', async () => {
|
|
143
|
+
const { db, createDocumentVersion } = createMockDb();
|
|
144
|
+
const ctx = buildCtx(db);
|
|
145
|
+
await createDocument(ctx, {
|
|
146
|
+
data: { title: 'Hello' },
|
|
147
|
+
locale: 'en',
|
|
148
|
+
});
|
|
149
|
+
// Audit contract (docs/AUDIT.md — W1): every version row
|
|
150
|
+
// records the actor that created it.
|
|
151
|
+
expect(createDocumentVersion.mock.calls[0]?.[0].createdBy).toBe('test-super-admin');
|
|
152
|
+
});
|
|
142
153
|
it('invokes beforeCreate and afterCreate hooks in order', async () => {
|
|
143
154
|
const callOrder = [];
|
|
144
155
|
const hooks = {
|
|
@@ -307,6 +318,16 @@ describe('Document lifecycle service', () => {
|
|
|
307
318
|
// updateDocument (PUT)
|
|
308
319
|
// -----------------------------------------------------------------------
|
|
309
320
|
describe('updateDocument', () => {
|
|
321
|
+
it('passes the acting user id as createdBy for the audit trail', async () => {
|
|
322
|
+
const { db, getDocumentById, createDocumentVersion } = createMockDb();
|
|
323
|
+
getDocumentById.mockResolvedValue({ status: 'draft', fields: { title: 'Old' } });
|
|
324
|
+
const ctx = buildCtx(db);
|
|
325
|
+
await updateDocument(ctx, {
|
|
326
|
+
documentId: 'doc-1',
|
|
327
|
+
data: { title: 'New' },
|
|
328
|
+
});
|
|
329
|
+
expect(createDocumentVersion.mock.calls[0]?.[0].createdBy).toBe('test-super-admin');
|
|
330
|
+
});
|
|
310
331
|
it('fetches the original before calling hooks', async () => {
|
|
311
332
|
const { db, getDocumentById, createDocumentVersion } = createMockDb();
|
|
312
333
|
getDocumentById.mockResolvedValue({ status: 'draft', fields: { title: 'Old' } });
|
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.8.0",
|
|
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.8.0"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"@biomejs/biome": "2.4.15",
|