@qkix/strapi-plugin-rewind 0.3.0 → 0.4.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.
@@ -35,6 +35,15 @@ export interface RelationRef {
35
35
  id?: number;
36
36
  locale?: string | null;
37
37
  targetUid: string;
38
+ /**
39
+ * What the target was called when the version was taken.
40
+ *
41
+ * Stored rather than looked up, because the moment it matters is the moment
42
+ * it can no longer be looked up: a relation to a since-deleted document would
43
+ * otherwise read as an id and a shrug. Absent on versions written before this
44
+ * was recorded, and on a target whose type has no field that reads as a name.
45
+ */
46
+ label?: string;
38
47
  }
39
48
  export interface SplitDocument {
40
49
  data: Record<string, unknown>;
@@ -44,8 +53,44 @@ export interface SplitDocument {
44
53
  type Attribute = Record<string, any>;
45
54
  /** Morph relations are skipped: Strapi's own populate builder skips them too. */
46
55
  export declare const isMorphRelation: (attribute: Attribute) => boolean;
47
- /** Populate object covering everything a snapshot needs, and nothing more. */
48
- export declare const buildDeepPopulate: (strapi: Core.Strapi, uid: string, useDatabaseSyntax?: boolean) => Record<string, unknown>;
56
+ /** Media is a relation to one particular content type, and always has been. */
57
+ export declare const MEDIA_UID = "plugin::upload.file";
58
+ /** The field to read a target's name from, per target uid. */
59
+ export type LabelFields = Map<string, string>;
60
+ /**
61
+ * Which field names a target of each relation on this content type.
62
+ *
63
+ * The Content Manager's configured `mainField` first, so a version agrees with
64
+ * what the relation input in the editor shows, then the usual suspects. The
65
+ * result is validated against the target's own attributes: a `mainField` can
66
+ * name a field that has since been deleted, and selecting a column that is not
67
+ * there fails the whole snapshot rather than one label.
68
+ *
69
+ * Async because the configuration lives in the store. Resolved once per
70
+ * capture, not once per relation - see the cache in `createMainFieldResolver`.
71
+ */
72
+ export declare const labelFieldsFor: (strapi: Core.Strapi, uid: string, mainFieldFor: (targetUid: string) => Promise<string | undefined>) => Promise<LabelFields>;
73
+ /**
74
+ * The same relations with their labels taken off.
75
+ *
76
+ * Content hashes are what stops an unchanged save becoming a version, and a
77
+ * label is not content: renaming an author does not change which author an
78
+ * article points at. Hashing it would make the next save of every article
79
+ * mentioning that author look like an edit, and produce a version whose diff is
80
+ * empty. Stripping here also keeps hashes written before labels existed valid.
81
+ */
82
+ export declare const withoutLabels: (relations: Record<string, RelationRef[]>) => Record<string, RelationRef[]>;
83
+ /**
84
+ * Populate object covering everything a snapshot needs, and nothing more.
85
+ *
86
+ * `labelFields` names, per target uid, the one extra column to read so the
87
+ * relation can be recorded with the target's name as well as its id - see
88
+ * {@link labelFieldsFor}. It is applied to this level only, never to the
89
+ * recursion: a relation nested inside a component is stored as part of `data`
90
+ * rather than extracted, so widening its select would change what a component
91
+ * holds, and with it every content hash ever computed.
92
+ */
93
+ export declare const buildDeepPopulate: (strapi: Core.Strapi, uid: string, useDatabaseSyntax?: boolean, labelFields?: LabelFields) => Record<string, unknown>;
49
94
  /**
50
95
  * The content type's attributes plus every component reachable from it, however
51
96
  * deeply nested.
@@ -61,5 +106,5 @@ export declare const buildSchemaSnapshot: (strapi: Core.Strapi, uid: string) =>
61
106
  * and "the author changed" as different kinds of change, and so a restore can
62
107
  * check each target still exists before writing it back.
63
108
  */
64
- export declare const split: (strapi: Core.Strapi, uid: string, entry: Record<string, unknown>) => SplitDocument;
109
+ export declare const split: (strapi: Core.Strapi, uid: string, entry: Record<string, unknown>, labelFields?: LabelFields) => SplitDocument;
65
110
  export {};
@@ -1,4 +1,22 @@
1
1
  import type { Core } from '@strapi/strapi';
2
+ /**
3
+ * Which field the Content Manager treats as a type's title.
4
+ *
5
+ * Returned as a function so the lookup is cached per content type: a backfill
6
+ * runs it once per row, a snapshot once per relation target, and the
7
+ * configuration behind it does not change while the server is up.
8
+ *
9
+ * `undefined` when the type has no configuration yet - which happens for a
10
+ * content type added since the last admin build - so callers guess instead.
11
+ */
12
+ export declare const createMainFieldResolver: (strapi: Core.Strapi) => (uid: string) => Promise<string | undefined>;
13
+ /**
14
+ * Field names worth trying when the Content Manager has no opinion.
15
+ *
16
+ * Shared with {@link import('../services/serializer').labelFieldsFor}, so a
17
+ * version and the relations inside it are named by the same rule.
18
+ */
19
+ export declare const LABEL_FIELD_CANDIDATES: string[];
2
20
  /**
3
21
  * Derives the short, human-readable stand-in shown for a version in the panel.
4
22
  *
@@ -7,8 +25,6 @@ import type { Core } from '@strapi/strapi';
7
25
  * pressing Restore would give them.
8
26
  *
9
27
  * The field is whatever the Content Manager displays as the entry's title, so
10
- * the panel agrees with the rest of the admin. The returned function caches the
11
- * lookup per content type: a backfill runs it once per row, and the
12
- * configuration behind it does not change while the server is up.
28
+ * the panel agrees with the rest of the admin.
13
29
  */
14
30
  export declare const createLabeller: (strapi: Core.Strapi) => (uid: string, data: Record<string, unknown> | null | undefined) => Promise<string | null>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qkix/strapi-plugin-rewind",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Document version history for Strapi v5 - snapshots on every save, with restore",
5
5
  "strapi": {
6
6
  "kind": "plugin",
@@ -56,8 +56,8 @@
56
56
  "typescript": "^5.8.3",
57
57
  "vitest": "^3.2.4",
58
58
  "@qkix/eslint-config": "0.0.0",
59
- "@qkix/tsconfig": "0.0.0",
60
- "@qkix/strapi-test-harness": "0.0.0"
59
+ "@qkix/strapi-test-harness": "0.0.0",
60
+ "@qkix/tsconfig": "0.0.0"
61
61
  },
62
62
  "peerDependencies": {
63
63
  "@strapi/admin": "^5.0.0",