@qkix/strapi-plugin-rewind 0.1.3 → 0.2.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/README.md +33 -19
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +145 -121
- package/dist/admin/src/diffRegistry.d.ts +1 -1
- package/dist/admin/src/index.d.ts +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/index.mjs +204 -171
- package/dist/server/src/config/index.d.ts +1 -1
- package/dist/server/src/content-types/index.d.ts +2 -2
- package/dist/server/src/controllers/index.d.ts +1 -0
- package/dist/server/src/controllers/version.d.ts +6 -0
- package/dist/server/src/index.d.ts +9 -0
- package/dist/server/src/services/diff.d.ts +6 -6
- package/dist/server/src/services/index.d.ts +8 -0
- package/dist/server/src/services/lock.d.ts +1 -1
- package/dist/server/src/services/pin.d.ts +19 -0
- package/dist/server/src/services/restore.d.ts +4 -4
- package/dist/server/src/services/serializer.d.ts +3 -3
- package/dist/server/src/services/snapshot.d.ts +1 -1
- package/dist/server/src/utils/backfillLabels.d.ts +1 -1
- package/dist/server/src/utils/captureContext.d.ts +1 -1
- package/dist/server/src/utils/persistTable.d.ts +2 -2
- package/dist/server/src/utils/textDiff.d.ts +2 -2
- package/package.json +4 -4
|
@@ -31,7 +31,7 @@ declare const _default: {
|
|
|
31
31
|
* On by default, because a version table that only grows is a problem the
|
|
32
32
|
* plugin would be creating rather than solving. The defaults are gentle:
|
|
33
33
|
* a full week of every save, then a version a day for a month, then one a
|
|
34
|
-
* week
|
|
34
|
+
* week - and anchors (publish, unpublish, discard, restore) and pinned
|
|
35
35
|
* versions are never removed at all, whatever their age.
|
|
36
36
|
*
|
|
37
37
|
* Set `enabled: false` to keep everything forever and manage it yourself.
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* The versions table.
|
|
3
3
|
*
|
|
4
4
|
* `relatedDocumentId`, not `documentId`: Strapi injects a `documentId` into
|
|
5
|
-
* every content type and rejects the name outright
|
|
6
|
-
* "documentId" is reserved and cannot be used in a model"
|
|
5
|
+
* every content type and rejects the name outright - "The attribute
|
|
6
|
+
* "documentId" is reserved and cannot be used in a model" - at boot, before
|
|
7
7
|
* anything else runs. Strapi's own history model calls it `relatedDocumentId`
|
|
8
8
|
* for the same reason.
|
|
9
9
|
*
|
|
@@ -6,6 +6,12 @@ declare const version: ({ strapi }: {
|
|
|
6
6
|
findOne(ctx: any): Promise<any>;
|
|
7
7
|
diff(ctx: any): Promise<any>;
|
|
8
8
|
preview(ctx: any): Promise<any>;
|
|
9
|
+
/**
|
|
10
|
+
* `pinned` is read from the body rather than inferred as a toggle, so that two
|
|
11
|
+
* editors with the panel open cannot flip the same version past each other:
|
|
12
|
+
* each request states the state it wants, and the last one wins visibly.
|
|
13
|
+
*/
|
|
14
|
+
pin(ctx: any): Promise<any>;
|
|
9
15
|
restore(ctx: any): Promise<any>;
|
|
10
16
|
};
|
|
11
17
|
export default version;
|
|
@@ -112,6 +112,7 @@ declare const _default: {
|
|
|
112
112
|
findOne(ctx: any): Promise<any>;
|
|
113
113
|
diff(ctx: any): Promise<any>;
|
|
114
114
|
preview(ctx: any): Promise<any>;
|
|
115
|
+
pin(ctx: any): Promise<any>;
|
|
115
116
|
restore(ctx: any): Promise<any>;
|
|
116
117
|
};
|
|
117
118
|
};
|
|
@@ -161,6 +162,14 @@ declare const _default: {
|
|
|
161
162
|
acquire(name: string, ttlMs: number): Promise<boolean>;
|
|
162
163
|
release(name: string): Promise<void>;
|
|
163
164
|
};
|
|
165
|
+
pin: ({ strapi }: {
|
|
166
|
+
strapi: import("@strapi/types/dist/core").Strapi;
|
|
167
|
+
}) => {
|
|
168
|
+
set(versionId: number, pinned: boolean): Promise<{
|
|
169
|
+
id: number;
|
|
170
|
+
pinned: boolean;
|
|
171
|
+
}>;
|
|
172
|
+
};
|
|
164
173
|
};
|
|
165
174
|
};
|
|
166
175
|
export default _default;
|
|
@@ -8,17 +8,17 @@ export interface FieldChange {
|
|
|
8
8
|
type: string;
|
|
9
9
|
/**
|
|
10
10
|
* A custom field's uid, when the attribute is one. Custom fields all report
|
|
11
|
-
* their storage type
|
|
11
|
+
* their storage type - usually `json` - so this is the only thing specific
|
|
12
12
|
* enough for the owning package to key a diff renderer on.
|
|
13
13
|
*/
|
|
14
14
|
customField?: string;
|
|
15
15
|
kind: ChangeKind;
|
|
16
|
-
/** Scalars only
|
|
16
|
+
/** Scalars only - the values themselves, for a plain before/after. */
|
|
17
17
|
before?: unknown;
|
|
18
18
|
after?: unknown;
|
|
19
|
-
/** Prose fields
|
|
19
|
+
/** Prose fields - a word-level diff of the readable text. */
|
|
20
20
|
spans?: DiffSpan[];
|
|
21
|
-
/** Relations and media
|
|
21
|
+
/** Relations and media - what was linked and unlinked. */
|
|
22
22
|
linked?: RelationRef[];
|
|
23
23
|
unlinked?: RelationRef[];
|
|
24
24
|
}
|
|
@@ -45,8 +45,8 @@ declare const diff: ({ strapi }: {
|
|
|
45
45
|
/**
|
|
46
46
|
* What changed between a version and the one before it.
|
|
47
47
|
*
|
|
48
|
-
* Comparing against the previous version
|
|
49
|
-
* as it stands
|
|
48
|
+
* Comparing against the previous version - rather than against the document
|
|
49
|
+
* as it stands - is what makes a history readable: each entry answers "what
|
|
50
50
|
* did this edit do?". The other question, "what would restoring this
|
|
51
51
|
* change?", is what `restore.preview()` already answers.
|
|
52
52
|
*/
|
|
@@ -31,5 +31,13 @@ declare const _default: {
|
|
|
31
31
|
acquire(name: string, ttlMs: number): Promise<boolean>;
|
|
32
32
|
release(name: string): Promise<void>;
|
|
33
33
|
};
|
|
34
|
+
pin: ({ strapi }: {
|
|
35
|
+
strapi: import("@strapi/types/dist/core").Strapi;
|
|
36
|
+
}) => {
|
|
37
|
+
set(versionId: number, pinned: boolean): Promise<{
|
|
38
|
+
id: number;
|
|
39
|
+
pinned: boolean;
|
|
40
|
+
}>;
|
|
41
|
+
};
|
|
34
42
|
};
|
|
35
43
|
export default _default;
|
|
@@ -8,7 +8,7 @@ import type { Core } from '@strapi/strapi';
|
|
|
8
8
|
*
|
|
9
9
|
* Best-effort is the honest description: read-then-write is not atomic, so two
|
|
10
10
|
* instances starting within the same millisecond can both win. That is
|
|
11
|
-
* acceptable for a nightly tidy-up and not for anything else
|
|
11
|
+
* acceptable for a nightly tidy-up and not for anything else - do not reuse
|
|
12
12
|
* this for something that must happen exactly once.
|
|
13
13
|
*/
|
|
14
14
|
declare const lock: ({ strapi }: {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Core } from '@strapi/strapi';
|
|
2
|
+
/**
|
|
3
|
+
* Pinning is the only write an editor makes to the history itself rather than
|
|
4
|
+
* to a document, so it is deliberately the smallest one available: a boolean on
|
|
5
|
+
* a row, no cascade, and no effect beyond prune skipping that row from then on
|
|
6
|
+
* (see `selectExpendable` in ../utils/thinning).
|
|
7
|
+
*
|
|
8
|
+
* It is kept out of the controller so that it can be exercised against a real
|
|
9
|
+
* database in the integration tests, which have no HTTP layer to call.
|
|
10
|
+
*/
|
|
11
|
+
declare const pin: ({ strapi }: {
|
|
12
|
+
strapi: Core.Strapi;
|
|
13
|
+
}) => {
|
|
14
|
+
set(versionId: number, pinned: boolean): Promise<{
|
|
15
|
+
id: number;
|
|
16
|
+
pinned: boolean;
|
|
17
|
+
}>;
|
|
18
|
+
};
|
|
19
|
+
export default pin;
|
|
@@ -3,9 +3,9 @@ import type { RelationRef } from './serializer';
|
|
|
3
3
|
export interface RestorePreview {
|
|
4
4
|
/** Fields that will be written back. */
|
|
5
5
|
fieldsToRestore: string[];
|
|
6
|
-
/** In the model but not in the version
|
|
6
|
+
/** In the model but not in the version - left exactly as they are. */
|
|
7
7
|
fieldsKeptAsIs: string[];
|
|
8
|
-
/** In the version but no longer in the model
|
|
8
|
+
/** In the version but no longer in the model - skipped. */
|
|
9
9
|
fieldsDropped: string[];
|
|
10
10
|
/** Not localised, so restoring them changes every locale at once. */
|
|
11
11
|
crossLocaleFields: string[];
|
|
@@ -26,14 +26,14 @@ declare const restore: ({ strapi }: {
|
|
|
26
26
|
*
|
|
27
27
|
* A separate call, and the UI shows it before asking for confirmation.
|
|
28
28
|
* Restore without a preview of the consequences is a trap rather than a
|
|
29
|
-
* tool
|
|
29
|
+
* tool - `crossLocaleFields` in particular, where restoring the Polish
|
|
30
30
|
* version of a document quietly rewrites a field in every other language.
|
|
31
31
|
*/
|
|
32
32
|
preview(versionId: number): Promise<RestorePreview>;
|
|
33
33
|
/**
|
|
34
34
|
* Writes the version back, to the draft only.
|
|
35
35
|
*
|
|
36
|
-
* The document lands in "modified" and publishing stays a deliberate act
|
|
36
|
+
* The document lands in "modified" and publishing stays a deliberate act -
|
|
37
37
|
* restoring must never change what the public sees on its own.
|
|
38
38
|
*/
|
|
39
39
|
apply(versionId: number, userId: number | null): Promise<{
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Written for this plugin rather than lifted from Strapi's own history module.
|
|
5
5
|
* Where the shapes here resemble theirs it is because Strapi's populate API
|
|
6
|
-
* dictates them
|
|
7
|
-
* dynamic zone only through `on` fragments
|
|
6
|
+
* dictates them - a relation can only be populated by naming its fields, a
|
|
7
|
+
* dynamic zone only through `on` fragments - not because the code was copied.
|
|
8
8
|
* Their implementation is MIT and was read while working out what the problem
|
|
9
9
|
* actually is; two facts learned that way are worth stating outright, since
|
|
10
10
|
* both cost a day if you meet them the hard way:
|
|
@@ -26,7 +26,7 @@ import type { Core } from '@strapi/strapi';
|
|
|
26
26
|
* The timestamps and author columns change on every write, so a version that
|
|
27
27
|
* keeps them can never be compared with another. `locale` and `localizations`
|
|
28
28
|
* are injected by i18n and describe how the document is wired up rather than
|
|
29
|
-
* what it says
|
|
29
|
+
* what it says - restoring them would try to rewrite the translation graph.
|
|
30
30
|
*/
|
|
31
31
|
export declare const FIELDS_TO_IGNORE: string[];
|
|
32
32
|
export interface RelationRef {
|
|
@@ -9,7 +9,7 @@ declare const snapshot: ({ strapi }: {
|
|
|
9
9
|
* Writes one version per affected locale, for each buffered intent.
|
|
10
10
|
*
|
|
11
11
|
* Called from the transaction's commit hook and never awaited by the write
|
|
12
|
-
* path
|
|
12
|
+
* path - a failure here must not be able to reach the editor's save.
|
|
13
13
|
*/
|
|
14
14
|
captureAll(intents: SnapshotIntent[], userId: number | null): Promise<void>;
|
|
15
15
|
capture(intent: SnapshotIntent, userId: number | null): Promise<void>;
|
|
@@ -3,7 +3,7 @@ import type { Core } from '@strapi/strapi';
|
|
|
3
3
|
* Gives versions recorded before labels existed something to show.
|
|
4
4
|
*
|
|
5
5
|
* Without this, upgrading leaves a history where older entries are a badge and
|
|
6
|
-
* a timestamp and newer ones have a title
|
|
6
|
+
* a timestamp and newer ones have a title - the list stays half-unreadable for
|
|
7
7
|
* exactly as long as the old versions are worth keeping.
|
|
8
8
|
*
|
|
9
9
|
* Two details that matter:
|
|
@@ -11,7 +11,7 @@ export interface SnapshotIntent {
|
|
|
11
11
|
* Intents buffered against the transaction they belong to.
|
|
12
12
|
*
|
|
13
13
|
* Keyed by the Knex transaction object because that is the only thing the
|
|
14
|
-
* Content Manager's `update` and `publish` calls have in common
|
|
14
|
+
* Content Manager's `update` and `publish` calls have in common - they are
|
|
15
15
|
* sequential siblings, not nested, so nothing about the call stack relates
|
|
16
16
|
* them. Strapi hands the same `trx` to every nested `db.transaction`, which
|
|
17
17
|
* makes it a reliable key, and a WeakMap means a finished transaction takes its
|
|
@@ -3,8 +3,8 @@ import type { Core } from '@strapi/strapi';
|
|
|
3
3
|
* Keeps the versions table alive when the plugin is not loaded.
|
|
4
4
|
*
|
|
5
5
|
* Strapi's schema sync drops any table it previously tracked that is no longer
|
|
6
|
-
* in the user schema. Disable this plugin for a single boot
|
|
7
|
-
* an upgrade
|
|
6
|
+
* in the user schema. Disable this plugin for a single boot - to debug, to test
|
|
7
|
+
* an upgrade - and the history it exists to protect is gone without a prompt.
|
|
8
8
|
*
|
|
9
9
|
* `persisted_tables` is the core-store key `@strapi/database` consults before
|
|
10
10
|
* dropping (schema/diff.js). The EE `admin::persist-tables` service writes the
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A word-level diff, and the text extraction that makes it useful.
|
|
3
3
|
*
|
|
4
|
-
* The alternative
|
|
4
|
+
* The alternative - reporting "content changed" - is true and worthless. The
|
|
5
5
|
* field an editor cares most about is usually rich text stored as JSON, so the
|
|
6
6
|
* readable part is pulled out and compared as prose. That is renderer-agnostic:
|
|
7
7
|
* it walks the structure collecting anything that reads as text, without
|
|
@@ -29,7 +29,7 @@ export declare const diffWords: (before: string, after: string) => DiffSpan[];
|
|
|
29
29
|
* Pulls the human-readable text out of an arbitrary value.
|
|
30
30
|
*
|
|
31
31
|
* Rich text, components and dynamic zones are all nested objects and arrays
|
|
32
|
-
* whose shape this plugin deliberately does not model
|
|
32
|
+
* whose shape this plugin deliberately does not model - a `text` key here, a
|
|
33
33
|
* `children` array there. Collecting every string reached is imprecise but
|
|
34
34
|
* stable across editors, and it is what turns "content changed" into a sentence
|
|
35
35
|
* an editor recognises.
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qkix/strapi-plugin-rewind",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Document version history for Strapi v5
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Document version history for Strapi v5 - snapshots on every save, with restore",
|
|
5
5
|
"strapi": {
|
|
6
6
|
"kind": "plugin",
|
|
7
7
|
"name": "rewind",
|
|
8
8
|
"displayName": "Rewind",
|
|
9
|
-
"description": "Document version history for Strapi v5
|
|
9
|
+
"description": "Document version history for Strapi v5 - snapshots on every save, with restore"
|
|
10
10
|
},
|
|
11
11
|
"keywords": [
|
|
12
12
|
"strapi",
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"styled-components": "^6.1.17",
|
|
56
56
|
"typescript": "^5.8.3",
|
|
57
57
|
"vitest": "^3.2.4",
|
|
58
|
-
"@qkix/eslint-config": "0.0.0",
|
|
59
58
|
"@qkix/strapi-test-harness": "0.0.0",
|
|
59
|
+
"@qkix/eslint-config": "0.0.0",
|
|
60
60
|
"@qkix/tsconfig": "0.0.0"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|