@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.
@@ -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 — and anchors (publish, unpublish, discard, restore) and pinned
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 — "The attribute
6
- * "documentId" is reserved and cannot be used in a model" — at boot, before
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,7 @@ declare const _default: {
6
6
  findOne(ctx: any): Promise<any>;
7
7
  diff(ctx: any): Promise<any>;
8
8
  preview(ctx: any): Promise<any>;
9
+ pin(ctx: any): Promise<any>;
9
10
  restore(ctx: any): Promise<any>;
10
11
  };
11
12
  };
@@ -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 — usually `json` — so this is the only thing specific
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 — the values themselves, for a plain before/after. */
16
+ /** Scalars only - the values themselves, for a plain before/after. */
17
17
  before?: unknown;
18
18
  after?: unknown;
19
- /** Prose fields — a word-level diff of the readable text. */
19
+ /** Prose fields - a word-level diff of the readable text. */
20
20
  spans?: DiffSpan[];
21
- /** Relations and media — what was linked and unlinked. */
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 — rather than against the document
49
- * as it stands — is what makes a history readable: each entry answers "what
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 — do not reuse
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 — left exactly as they are. */
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 — skipped. */
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 — `crossLocaleFields` in particular, where restoring the Polish
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 — a relation can only be populated by naming its fields, a
7
- * dynamic zone only through `on` fragments — not because the code was copied.
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 — restoring them would try to rewrite the translation graph.
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 — a failure here must not be able to reach the editor's save.
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 — the list stays half-unreadable for
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 — they are
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 — to debug, to test
7
- * an upgrade — and the history it exists to protect is gone without a prompt.
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 — reporting "content changed" — is true and worthless. The
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 — a `text` key here, a
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.1.3",
4
- "description": "Document version history for Strapi v5 — snapshots on every save, with restore",
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 — snapshots on every save, with restore"
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": {