@cogenta/schema 0.3.0 → 0.5.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.
Files changed (189) hide show
  1. package/dist/define-collection.d.ts.map +1 -1
  2. package/dist/define-collection.js +53 -3
  3. package/dist/define-collection.js.map +1 -1
  4. package/dist/generate-types.d.ts.map +1 -1
  5. package/dist/generate-types.js +6 -1
  6. package/dist/generate-types.js.map +1 -1
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/media-usage.d.ts +56 -0
  12. package/dist/media-usage.d.ts.map +1 -0
  13. package/dist/media-usage.js +89 -0
  14. package/dist/media-usage.js.map +1 -0
  15. package/dist/rich-text.d.ts +28 -2
  16. package/dist/rich-text.d.ts.map +1 -1
  17. package/dist/rich-text.js +26 -3
  18. package/dist/rich-text.js.map +1 -1
  19. package/dist/routing/index.d.ts +5 -1
  20. package/dist/routing/index.d.ts.map +1 -1
  21. package/dist/routing/index.js +2 -0
  22. package/dist/routing/index.js.map +1 -1
  23. package/dist/routing/not-found-log.d.ts +78 -0
  24. package/dist/routing/not-found-log.d.ts.map +1 -0
  25. package/dist/routing/not-found-log.js +153 -0
  26. package/dist/routing/not-found-log.js.map +1 -0
  27. package/dist/routing/redirect-patterns.d.ts +61 -0
  28. package/dist/routing/redirect-patterns.d.ts.map +1 -0
  29. package/dist/routing/redirect-patterns.js +173 -0
  30. package/dist/routing/redirect-patterns.js.map +1 -0
  31. package/dist/routing/redirects.d.ts +27 -4
  32. package/dist/routing/redirects.d.ts.map +1 -1
  33. package/dist/routing/redirects.js +160 -48
  34. package/dist/routing/redirects.js.map +1 -1
  35. package/dist/scheduling/index.d.ts +2 -0
  36. package/dist/scheduling/index.d.ts.map +1 -1
  37. package/dist/scheduling/index.js +1 -0
  38. package/dist/scheduling/index.js.map +1 -1
  39. package/dist/scheduling/registry.d.ts +147 -0
  40. package/dist/scheduling/registry.d.ts.map +1 -0
  41. package/dist/scheduling/registry.js +318 -0
  42. package/dist/scheduling/registry.js.map +1 -0
  43. package/dist/search/extract.d.ts +24 -0
  44. package/dist/search/extract.d.ts.map +1 -1
  45. package/dist/search/extract.js +63 -8
  46. package/dist/search/extract.js.map +1 -1
  47. package/dist/search/index.d.ts +2 -1
  48. package/dist/search/index.d.ts.map +1 -1
  49. package/dist/search/index.js +1 -9
  50. package/dist/search/index.js.map +1 -1
  51. package/dist/store/admin-theme-store.d.ts +26 -0
  52. package/dist/store/admin-theme-store.d.ts.map +1 -0
  53. package/dist/store/admin-theme-store.js +81 -0
  54. package/dist/store/admin-theme-store.js.map +1 -0
  55. package/dist/store/admin-theme-tables.d.ts +24 -0
  56. package/dist/store/admin-theme-tables.d.ts.map +1 -0
  57. package/dist/store/admin-theme-tables.js +41 -0
  58. package/dist/store/admin-theme-tables.js.map +1 -0
  59. package/dist/store/admin-theme-templates.d.ts +135 -0
  60. package/dist/store/admin-theme-templates.d.ts.map +1 -0
  61. package/dist/store/admin-theme-templates.js +217 -0
  62. package/dist/store/admin-theme-templates.js.map +1 -0
  63. package/dist/store/columns.d.ts.map +1 -1
  64. package/dist/store/columns.js +8 -2
  65. package/dist/store/columns.js.map +1 -1
  66. package/dist/store/diff.d.ts +40 -0
  67. package/dist/store/diff.d.ts.map +1 -1
  68. package/dist/store/diff.js +148 -0
  69. package/dist/store/diff.js.map +1 -1
  70. package/dist/store/index.d.ts +44 -6
  71. package/dist/store/index.d.ts.map +1 -1
  72. package/dist/store/index.js +24 -3
  73. package/dist/store/index.js.map +1 -1
  74. package/dist/store/maintenance-store.d.ts +34 -0
  75. package/dist/store/maintenance-store.d.ts.map +1 -0
  76. package/dist/store/maintenance-store.js +92 -0
  77. package/dist/store/maintenance-store.js.map +1 -0
  78. package/dist/store/menu-store.d.ts +48 -1
  79. package/dist/store/menu-store.d.ts.map +1 -1
  80. package/dist/store/menu-store.js +151 -4
  81. package/dist/store/menu-store.js.map +1 -1
  82. package/dist/store/menu-tables.d.ts +12 -0
  83. package/dist/store/menu-tables.d.ts.map +1 -1
  84. package/dist/store/menu-tables.js +38 -0
  85. package/dist/store/menu-tables.js.map +1 -1
  86. package/dist/store/naming.d.ts +1 -1
  87. package/dist/store/naming.d.ts.map +1 -1
  88. package/dist/store/naming.js +2 -0
  89. package/dist/store/naming.js.map +1 -1
  90. package/dist/store/pattern-store.d.ts +75 -0
  91. package/dist/store/pattern-store.d.ts.map +1 -0
  92. package/dist/store/pattern-store.js +118 -0
  93. package/dist/store/pattern-store.js.map +1 -0
  94. package/dist/store/pattern-tables.d.ts +29 -0
  95. package/dist/store/pattern-tables.d.ts.map +1 -0
  96. package/dist/store/pattern-tables.js +53 -0
  97. package/dist/store/pattern-tables.js.map +1 -0
  98. package/dist/store/read-only.d.ts +3 -3
  99. package/dist/store/read-only.d.ts.map +1 -1
  100. package/dist/store/read-only.js +4 -3
  101. package/dist/store/read-only.js.map +1 -1
  102. package/dist/store/redirect-tracking.d.ts +50 -0
  103. package/dist/store/redirect-tracking.d.ts.map +1 -0
  104. package/dist/store/redirect-tracking.js +91 -0
  105. package/dist/store/redirect-tracking.js.map +1 -0
  106. package/dist/store/review-transitions.d.ts +34 -0
  107. package/dist/store/review-transitions.d.ts.map +1 -0
  108. package/dist/store/review-transitions.js +51 -0
  109. package/dist/store/review-transitions.js.map +1 -0
  110. package/dist/store/role-permission-export.d.ts +22 -0
  111. package/dist/store/role-permission-export.d.ts.map +1 -0
  112. package/dist/store/role-permission-export.js +0 -0
  113. package/dist/store/role-permission-export.js.map +1 -0
  114. package/dist/store/role-permission-overlay.d.ts +27 -0
  115. package/dist/store/role-permission-overlay.d.ts.map +1 -0
  116. package/dist/store/role-permission-overlay.js +0 -0
  117. package/dist/store/role-permission-overlay.js.map +1 -0
  118. package/dist/store/role-permission-store.d.ts +72 -0
  119. package/dist/store/role-permission-store.d.ts.map +1 -0
  120. package/dist/store/role-permission-store.js +148 -0
  121. package/dist/store/role-permission-store.js.map +1 -0
  122. package/dist/store/role-permission-tables.d.ts +27 -0
  123. package/dist/store/role-permission-tables.d.ts.map +1 -0
  124. package/dist/store/role-permission-tables.js +52 -0
  125. package/dist/store/role-permission-tables.js.map +1 -0
  126. package/dist/store/scheduled-publish-failures.d.ts +36 -0
  127. package/dist/store/scheduled-publish-failures.d.ts.map +1 -0
  128. package/dist/store/scheduled-publish-failures.js +67 -0
  129. package/dist/store/scheduled-publish-failures.js.map +1 -0
  130. package/dist/store/schema-2-1-migration.d.ts +29 -0
  131. package/dist/store/schema-2-1-migration.d.ts.map +1 -0
  132. package/dist/store/schema-2-1-migration.js +71 -0
  133. package/dist/store/schema-2-1-migration.js.map +1 -0
  134. package/dist/store/search-console-store.d.ts +44 -0
  135. package/dist/store/search-console-store.d.ts.map +1 -0
  136. package/dist/store/search-console-store.js +90 -0
  137. package/dist/store/search-console-store.js.map +1 -0
  138. package/dist/store/search-console-tables.d.ts +14 -0
  139. package/dist/store/search-console-tables.d.ts.map +1 -0
  140. package/dist/store/search-console-tables.js +29 -0
  141. package/dist/store/search-console-tables.js.map +1 -0
  142. package/dist/store/search-indexing.d.ts +36 -0
  143. package/dist/store/search-indexing.d.ts.map +1 -1
  144. package/dist/store/search-indexing.js +33 -0
  145. package/dist/store/search-indexing.js.map +1 -1
  146. package/dist/store/site-settings-registry.d.ts +164 -0
  147. package/dist/store/site-settings-registry.d.ts.map +1 -0
  148. package/dist/store/site-settings-registry.js +1200 -0
  149. package/dist/store/site-settings-registry.js.map +1 -0
  150. package/dist/store/site-settings-store.d.ts +42 -0
  151. package/dist/store/site-settings-store.d.ts.map +1 -0
  152. package/dist/store/site-settings-store.js +97 -0
  153. package/dist/store/site-settings-store.js.map +1 -0
  154. package/dist/store/site-settings-tables.d.ts +29 -0
  155. package/dist/store/site-settings-tables.d.ts.map +1 -0
  156. package/dist/store/site-settings-tables.js +48 -0
  157. package/dist/store/site-settings-tables.js.map +1 -0
  158. package/dist/store/store.d.ts +48 -1
  159. package/dist/store/store.d.ts.map +1 -1
  160. package/dist/store/store.js +231 -39
  161. package/dist/store/store.js.map +1 -1
  162. package/dist/store/tables.d.ts.map +1 -1
  163. package/dist/store/tables.js +11 -0
  164. package/dist/store/tables.js.map +1 -1
  165. package/dist/store/taxonomy-store.d.ts.map +1 -1
  166. package/dist/store/taxonomy-store.js +90 -39
  167. package/dist/store/taxonomy-store.js.map +1 -1
  168. package/dist/store/taxonomy-usage.d.ts +47 -0
  169. package/dist/store/taxonomy-usage.d.ts.map +1 -0
  170. package/dist/store/taxonomy-usage.js +101 -0
  171. package/dist/store/taxonomy-usage.js.map +1 -0
  172. package/dist/store/theme-store.d.ts +73 -0
  173. package/dist/store/theme-store.d.ts.map +1 -0
  174. package/dist/store/theme-store.js +193 -0
  175. package/dist/store/theme-store.js.map +1 -0
  176. package/dist/store/types.d.ts +36 -1
  177. package/dist/store/types.d.ts.map +1 -1
  178. package/dist/store/values.d.ts.map +1 -1
  179. package/dist/store/values.js +28 -1
  180. package/dist/store/values.js.map +1 -1
  181. package/dist/system-fields.d.ts +8 -1
  182. package/dist/system-fields.d.ts.map +1 -1
  183. package/dist/system-fields.js +14 -1
  184. package/dist/system-fields.js.map +1 -1
  185. package/dist/types.d.ts +54 -1
  186. package/dist/types.d.ts.map +1 -1
  187. package/dist/types.js +21 -0
  188. package/dist/types.js.map +1 -1
  189. package/package.json +2 -2
@@ -0,0 +1,75 @@
1
+ import { type DatabaseHandle } from '@cogenta/core';
2
+ import type { Provenance } from '../types.js';
3
+ import type { PatternKind } from './pattern-tables.js';
4
+ /**
5
+ * The persistence layer of the page builder's pattern/template library
6
+ * (fiche 43 sub-chantier A; fiche 05 task 1).
7
+ *
8
+ * Deliberately **not** content (contract A): a pattern carries no `status`,
9
+ * no `version`, no trash, no `translationOf` — it is a shape an editor
10
+ * composes from existing blocks, not a published thing of its own. It does
11
+ * carry `provenance`/`provenanceDetail`, in the same two-value/detail shape
12
+ * contract A uses for an entry, because the same question applies just as
13
+ * much here: a pattern an agent generated must never read back
14
+ * indistinguishable from one a person built by hand (the EU AI Act
15
+ * disclosure obligation `docs/03-decisions.md` already tracks for content).
16
+ */
17
+ export type { PatternKind } from './pattern-tables.js';
18
+ /** Same shape contract A's `provenanceDetail` carries, kept local rather than imported: a pattern is not an entry, and this table has no dependency on `store/types.ts`. */
19
+ export interface PatternProvenanceDetail {
20
+ readonly agent?: string;
21
+ readonly model?: string;
22
+ readonly at?: string;
23
+ readonly prompt?: string;
24
+ }
25
+ /** The wire shape of one block — identical to contract A's `ContentBlock`, kept local for the same reason as `PatternProvenanceDetail`. */
26
+ export interface PatternBlock {
27
+ readonly key: string;
28
+ readonly type: string;
29
+ readonly data: Readonly<Record<string, unknown>>;
30
+ }
31
+ export interface Pattern {
32
+ readonly id: string;
33
+ readonly name: string;
34
+ /** An admin-only, free-form grouping label — never validated against a fixed set (unlike contract B's own vocabulary). */
35
+ readonly category: string | null;
36
+ readonly kind: PatternKind;
37
+ readonly blocks: readonly PatternBlock[];
38
+ readonly provenance: Provenance;
39
+ readonly provenanceDetail: PatternProvenanceDetail | null;
40
+ readonly createdAt: string;
41
+ readonly updatedAt: string;
42
+ }
43
+ export interface CreatePatternInput {
44
+ readonly id?: string;
45
+ readonly name: string;
46
+ readonly category?: string | null;
47
+ readonly kind: PatternKind;
48
+ readonly blocks: readonly PatternBlock[];
49
+ /** Defaults to `'human'` — the same default contract A's own store uses. */
50
+ readonly provenance?: Provenance;
51
+ readonly provenanceDetail?: PatternProvenanceDetail | null;
52
+ }
53
+ export interface UpdatePatternInput {
54
+ readonly name?: string;
55
+ /** Absent leaves it untouched; `null` clears it; a string reassigns it. */
56
+ readonly category?: string | null;
57
+ }
58
+ export interface ListPatternsOptions {
59
+ readonly kind?: PatternKind;
60
+ }
61
+ export interface PatternStoreOptions {
62
+ readonly db: DatabaseHandle;
63
+ /** Injectable so tests can pin time; nothing else should pass it. */
64
+ readonly now?: () => Date;
65
+ readonly newId?: () => string;
66
+ }
67
+ export interface PatternStore {
68
+ create(input: CreatePatternInput): Promise<Pattern>;
69
+ read(id: string): Promise<Pattern | null>;
70
+ update(id: string, input: UpdatePatternInput): Promise<Pattern>;
71
+ delete(id: string): Promise<boolean>;
72
+ list(options?: ListPatternsOptions): Promise<readonly Pattern[]>;
73
+ }
74
+ export declare function createPatternStore(options: PatternStoreOptions): PatternStore;
75
+ //# sourceMappingURL=pattern-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pattern-store.d.ts","sourceRoot":"","sources":["../../src/store/pattern-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,118 @@
1
+ import { CogentaError, identifier, sql, } from '@cogenta/core';
2
+ import { newId as uuidv7 } from '../id.js';
3
+ import { joinFragments } from './fragments.js';
4
+ import { PATTERN_TABLE } from './pattern-tables.js';
5
+ function text(value) {
6
+ return typeof value === 'string' ? value : String(value);
7
+ }
8
+ function nullableText(value) {
9
+ return value === null || value === undefined ? null : text(value);
10
+ }
11
+ function parseBlocks(raw) {
12
+ const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
13
+ if (!Array.isArray(parsed))
14
+ return [];
15
+ return parsed;
16
+ }
17
+ function parseProvenanceDetail(raw) {
18
+ if (raw === null || raw === undefined)
19
+ return null;
20
+ const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
21
+ return (parsed ?? null);
22
+ }
23
+ function patternNotFound(id) {
24
+ return new CogentaError({
25
+ code: 'PATTERN_UNKNOWN',
26
+ message: `No pattern "${id}".`,
27
+ hint: 'Check the identifier — list the patterns of this site to find the right one.',
28
+ details: { id },
29
+ });
30
+ }
31
+ export function createPatternStore(options) {
32
+ const { db } = options;
33
+ const dialect = db.dialect;
34
+ const now = options.now ?? (() => new Date());
35
+ const newId = options.newId ?? uuidv7;
36
+ const patterns = identifier(PATTERN_TABLE, dialect);
37
+ const idColumn = identifier('id', dialect);
38
+ const kindColumn = identifier('kind', dialect);
39
+ const createdAtColumn = identifier('created_at', dialect);
40
+ const updatedAtColumn = identifier('updated_at', dialect);
41
+ const stamp = () => now().toISOString();
42
+ function toPattern(row) {
43
+ return {
44
+ id: text(row.id),
45
+ name: text(row.name),
46
+ category: nullableText(row.category),
47
+ kind: text(row.kind),
48
+ blocks: parseBlocks(row.blocks),
49
+ provenance: text(row.provenance),
50
+ provenanceDetail: parseProvenanceDetail(row.provenance_detail),
51
+ createdAt: text(row.created_at),
52
+ updatedAt: text(row.updated_at),
53
+ };
54
+ }
55
+ async function rowOf(tx, id) {
56
+ const found = await tx.query(sql `select * from ${patterns} where ${idColumn} = ${id}`);
57
+ return found.rows[0] ?? null;
58
+ }
59
+ return {
60
+ create: async (input) => {
61
+ const id = input.id ?? newId();
62
+ const at = stamp();
63
+ await db.query(sql `insert into ${patterns} (${joinFragments([
64
+ idColumn,
65
+ identifier('name', dialect),
66
+ identifier('category', dialect),
67
+ kindColumn,
68
+ identifier('blocks', dialect),
69
+ identifier('provenance', dialect),
70
+ identifier('provenance_detail', dialect),
71
+ createdAtColumn,
72
+ updatedAtColumn,
73
+ ], ', ')}) values (
74
+ ${id}, ${input.name}, ${input.category ?? null}, ${input.kind},
75
+ ${JSON.stringify(input.blocks)},
76
+ ${input.provenance ?? 'human'},
77
+ ${input.provenanceDetail === undefined || input.provenanceDetail === null ? null : JSON.stringify(input.provenanceDetail)},
78
+ ${at}, ${at}
79
+ )`);
80
+ const row = await rowOf(db, id);
81
+ if (row === null)
82
+ throw patternNotFound(id);
83
+ return toPattern(row);
84
+ },
85
+ read: async (id) => {
86
+ const row = await rowOf(db, id);
87
+ return row === null ? null : toPattern(row);
88
+ },
89
+ update: async (id, input) => db.transaction(async (tx) => {
90
+ const row = await rowOf(tx, id);
91
+ if (row === null)
92
+ throw patternNotFound(id);
93
+ const assignments = [sql `${updatedAtColumn} = ${stamp()}`];
94
+ if (input.name !== undefined) {
95
+ assignments.push(sql `${identifier('name', dialect)} = ${input.name}`);
96
+ }
97
+ if (input.category !== undefined) {
98
+ assignments.push(sql `${identifier('category', dialect)} = ${input.category}`);
99
+ }
100
+ await tx.query(sql `update ${patterns} set ${joinFragments(assignments, ', ')} where ${idColumn} = ${id}`);
101
+ const after = await rowOf(tx, id);
102
+ if (after === null)
103
+ throw patternNotFound(id);
104
+ return toPattern(after);
105
+ }, { immediate: true }),
106
+ delete: async (id) => {
107
+ const removed = await db.query(sql `delete from ${patterns} where ${idColumn} = ${id}`);
108
+ return removed.rowsAffected > 0;
109
+ },
110
+ list: async (listOptions = {}) => {
111
+ const found = await db.query(listOptions.kind === undefined
112
+ ? sql `select * from ${patterns} order by ${createdAtColumn} desc`
113
+ : sql `select * from ${patterns} where ${kindColumn} = ${listOptions.kind} order by ${createdAtColumn} desc`);
114
+ return found.rows.map(toPattern);
115
+ },
116
+ };
117
+ }
118
+ //# sourceMappingURL=pattern-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pattern-store.js","sourceRoot":"","sources":["../../src/store/pattern-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import { type DatabaseHandle } from '@cogenta/core';
2
+ /**
3
+ * The page builder's motif/model library (fiche 43, sub-chantier A; fiche 05
4
+ * task 1).
5
+ *
6
+ * A pattern is not schema-declared content — it is a reusable *shape*, an
7
+ * editor's own composition saved from the builder — so it gets the same
8
+ * one-fixed-table treatment as a menu (`menu-tables.ts`) rather than a table
9
+ * per collection. `blocks` stores exactly the contract-B block list the
10
+ * builder already sends to `POST /api/builder/render` (`key`/`type`/`data`),
11
+ * never HTML or CSS (R3): inserting a pattern is indistinguishable from
12
+ * placing the same blocks by hand.
13
+ *
14
+ * `kind` distinguishes a **motif** (a handful of blocks dropped into an
15
+ * existing page, added to whatever is already there) from a **modèle de page
16
+ * complet** (fiche 43 sub-chantier A's "second niveau" — replaces the whole
17
+ * block zone, and only after the admin asks for explicit confirmation; that
18
+ * confirmation is a UI concern, not something this table enforces). Both
19
+ * shapes are "a list of blocks"; nothing about the stored row differs.
20
+ */
21
+ export declare const PATTERN_TABLE = "cogenta_patterns";
22
+ export declare const PATTERN_KINDS: readonly ['pattern', 'template'];
23
+ export type PatternKind = (typeof PATTERN_KINDS)[number];
24
+ /** How long a pattern's `name` may be. */
25
+ export declare const PATTERN_NAME_LENGTH = 255;
26
+ /** How long a pattern's `category` (an admin-only, free-form grouping label) may be. */
27
+ export declare const PATTERN_CATEGORY_LENGTH = 64;
28
+ export declare function ensurePatternTables(db: DatabaseHandle): Promise<void>;
29
+ //# sourceMappingURL=pattern-tables.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pattern-tables.d.ts","sourceRoot":"","sources":["../../src/store/pattern-tables.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAqC,MAAM,eAAe,CAAA;AAItF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,aAAa,qBAAqB,CAAA;AAE/C,eAAO,MAAM,aAAa,YAAI,SAAS,EAAE,UAAU,CAAU,CAAA;AAC7D,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAA;AAExD,0CAA0C;AAC1C,eAAO,MAAM,mBAAmB,MAAM,CAAA;AACtC,wFAAwF;AACxF,eAAO,MAAM,uBAAuB,KAAK,CAAA;AAEzC,wBAAsB,mBAAmB,CAAC,EAAE,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB3E"}
@@ -0,0 +1,53 @@
1
+ import { identifier, sql } from '@cogenta/core';
2
+ import { jsonColumn, textColumn, timestampColumn, uuidColumn } from './columns.js';
3
+ import { indexName } from './naming.js';
4
+ /**
5
+ * The page builder's motif/model library (fiche 43, sub-chantier A; fiche 05
6
+ * task 1).
7
+ *
8
+ * A pattern is not schema-declared content — it is a reusable *shape*, an
9
+ * editor's own composition saved from the builder — so it gets the same
10
+ * one-fixed-table treatment as a menu (`menu-tables.ts`) rather than a table
11
+ * per collection. `blocks` stores exactly the contract-B block list the
12
+ * builder already sends to `POST /api/builder/render` (`key`/`type`/`data`),
13
+ * never HTML or CSS (R3): inserting a pattern is indistinguishable from
14
+ * placing the same blocks by hand.
15
+ *
16
+ * `kind` distinguishes a **motif** (a handful of blocks dropped into an
17
+ * existing page, added to whatever is already there) from a **modèle de page
18
+ * complet** (fiche 43 sub-chantier A's "second niveau" — replaces the whole
19
+ * block zone, and only after the admin asks for explicit confirmation; that
20
+ * confirmation is a UI concern, not something this table enforces). Both
21
+ * shapes are "a list of blocks"; nothing about the stored row differs.
22
+ */
23
+ export const PATTERN_TABLE = 'cogenta_patterns';
24
+ export const PATTERN_KINDS = ['pattern', 'template'];
25
+ /** How long a pattern's `name` may be. */
26
+ export const PATTERN_NAME_LENGTH = 255;
27
+ /** How long a pattern's `category` (an admin-only, free-form grouping label) may be. */
28
+ export const PATTERN_CATEGORY_LENGTH = 64;
29
+ export async function ensurePatternTables(db) {
30
+ const dialect = db.dialect;
31
+ const patterns = identifier(PATTERN_TABLE, dialect);
32
+ await db.query(sql `create table if not exists ${patterns} (
33
+ ${identifier('id', dialect)} ${uuidColumn(dialect)} not null primary key,
34
+ ${identifier('name', dialect)} ${textColumn(dialect, PATTERN_NAME_LENGTH)} not null,
35
+ ${identifier('category', dialect)} ${textColumn(dialect, PATTERN_CATEGORY_LENGTH)},
36
+ ${identifier('kind', dialect)} ${textColumn(dialect, 16)} not null,
37
+ ${identifier('blocks', dialect)} ${jsonColumn()} not null,
38
+ ${identifier('provenance', dialect)} ${textColumn(dialect, 16)} not null,
39
+ ${identifier('provenance_detail', dialect)} ${jsonColumn()},
40
+ ${identifier('created_at', dialect)} ${timestampColumn(dialect)} not null,
41
+ ${identifier('updated_at', dialect)} ${timestampColumn(dialect)} not null
42
+ )`);
43
+ const indexStatements = [
44
+ // Listing is always "every pattern of this kind, most recent first" — the
45
+ // only query the picker and the library screen ever make.
46
+ sql `create index ${identifier(indexName(PATTERN_TABLE, 'kind_created'), dialect)}
47
+ on ${patterns} (${identifier('kind', dialect)}, ${identifier('created_at', dialect)})`,
48
+ ];
49
+ for (const statement of indexStatements) {
50
+ await db.query(statement).catch(() => undefined);
51
+ }
52
+ }
53
+ //# sourceMappingURL=pattern-tables.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pattern-tables.js","sourceRoot":"","sources":["../../src/store/pattern-tables.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,UAAU,EAAoB,GAAG,EAAE,MAAM,eAAe,CAAA;AACtF,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAClF,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEvC;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,kBAAkB,CAAA;AAE/C,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,SAAS,EAAE,UAAU,CAAU,CAAA;AAG7D,0CAA0C;AAC1C,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,CAAA;AACtC,wFAAwF;AACxF,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,CAAA;AAEzC,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,EAAkB;IAC1D,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,CAAA;IAC1B,MAAM,QAAQ,GAAG,UAAU,CAAC,aAAa,EAAE,OAAO,CAAC,CAAA;IAEnD,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,CAAA,8BAA8B,QAAQ;MACpD,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC;MAChD,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,mBAAmB,CAAC;MACvE,UAAU,CAAC,UAAU,EAAE,OAAO,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,uBAAuB,CAAC;MAC/E,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC;MACtD,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,UAAU,EAAE;MAC7C,UAAU,CAAC,YAAY,EAAE,OAAO,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC;MAC5D,UAAU,CAAC,mBAAmB,EAAE,OAAO,CAAC,IAAI,UAAU,EAAE;MACxD,UAAU,CAAC,YAAY,EAAE,OAAO,CAAC,IAAI,eAAe,CAAC,OAAO,CAAC;MAC7D,UAAU,CAAC,YAAY,EAAE,OAAO,CAAC,IAAI,eAAe,CAAC,OAAO,CAAC;IAC/D,CAAC,CAAA;IAEH,MAAM,eAAe,GAAkB;QACrC,0EAA0E;QAC1E,0DAA0D;QAC1D,GAAG,CAAA,gBAAgB,UAAU,CAAC,SAAS,CAAC,aAAa,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC;aACvE,QAAQ,KAAK,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,UAAU,CAAC,YAAY,EAAE,OAAO,CAAC,GAAG;KAC3F,CAAA;IACD,KAAK,MAAM,SAAS,IAAI,eAAe,EAAE,CAAC;QACxC,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;IAClD,CAAC;AACH,CAAC"}
@@ -4,9 +4,9 @@ import type { ContentValues } from './types.js';
4
4
  * "Commencer par une démo en lecture seule" (L9 tâche 12, playground). Wraps
5
5
  * any `ContentStore` so every mutating method refuses instead of writing —
6
6
  * `create`/`duplicate`/`update`/`delete`/`untrash`/`purge`/`purgeExpired`/
7
- * `publish`/`unpublish`/`restore` — while every read (`read`/`list`/`history`/
8
- * `readVersion`/`diff`/`translations`/`resolveLocale`) passes straight
9
- * through, unchanged.
7
+ * `publish`/`unpublish`/`restore`/`claimForScheduledPublish` — while every
8
+ * read (`read`/`list`/`history`/`readVersion`/`diff`/`translations`/
9
+ * `resolveLocale`) passes straight through, unchanged.
10
10
  *
11
11
  * The three trash methods are refused for the reason `delete` always was, and
12
12
  * `purge` most of all: a read-only demo that let a visitor empty the trash
@@ -1 +1 @@
1
- {"version":3,"file":"read-only.d.ts","sourceRoot":"","sources":["../../src/store/read-only.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,SAAS,aAAa,GAAG,aAAa,EAC7E,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,GAC3B,YAAY,CAAC,OAAO,CAAC,CA0BvB"}
1
+ {"version":3,"file":"read-only.d.ts","sourceRoot":"","sources":["../../src/store/read-only.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,SAAS,aAAa,GAAG,aAAa,EAC7E,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,GAC3B,YAAY,CAAC,OAAO,CAAC,CA2BvB"}
@@ -3,9 +3,9 @@ import { CogentaError } from '@cogenta/core';
3
3
  * "Commencer par une démo en lecture seule" (L9 tâche 12, playground). Wraps
4
4
  * any `ContentStore` so every mutating method refuses instead of writing —
5
5
  * `create`/`duplicate`/`update`/`delete`/`untrash`/`purge`/`purgeExpired`/
6
- * `publish`/`unpublish`/`restore` — while every read (`read`/`list`/`history`/
7
- * `readVersion`/`diff`/`translations`/`resolveLocale`) passes straight
8
- * through, unchanged.
6
+ * `publish`/`unpublish`/`restore`/`claimForScheduledPublish` — while every
7
+ * read (`read`/`list`/`history`/`readVersion`/`diff`/`translations`/
8
+ * `resolveLocale`) passes straight through, unchanged.
9
9
  *
10
10
  * The three trash methods are refused for the reason `delete` always was, and
11
11
  * `purge` most of all: a read-only demo that let a visitor empty the trash
@@ -41,6 +41,7 @@ export function withReadOnlyStore(store) {
41
41
  publish: () => refuse(),
42
42
  unpublish: () => refuse(),
43
43
  restore: () => refuse(),
44
+ claimForScheduledPublish: () => refuse(),
44
45
  };
45
46
  }
46
47
  //# sourceMappingURL=read-only.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"read-only.js","sourceRoot":"","sources":["../../src/store/read-only.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAI5C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAA4B;IAE5B,0EAA0E;IAC1E,wEAAwE;IACxE,wEAAwE;IACxE,8BAA8B;IAC9B,KAAK,UAAU,MAAM;QACnB,MAAM,IAAI,YAAY,CAAC;YACrB,IAAI,EAAE,mBAAmB;YACzB,OAAO,EAAE,iEAAiE;YAC1E,IAAI,EAAE,uHAAuH;SAC9H,CAAC,CAAA;IACJ,CAAC;IAED,OAAO;QACL,GAAG,KAAK;QACR,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACtB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACzB,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACtB,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACtB,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACvB,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACrB,YAAY,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QAC5B,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACvB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACzB,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;KACxB,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"read-only.js","sourceRoot":"","sources":["../../src/store/read-only.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAI5C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAA4B;IAE5B,0EAA0E;IAC1E,wEAAwE;IACxE,wEAAwE;IACxE,8BAA8B;IAC9B,KAAK,UAAU,MAAM;QACnB,MAAM,IAAI,YAAY,CAAC;YACrB,IAAI,EAAE,mBAAmB;YACzB,OAAO,EAAE,iEAAiE;YAC1E,IAAI,EAAE,uHAAuH;SAC9H,CAAC,CAAA;IACJ,CAAC;IAED,OAAO;QACL,GAAG,KAAK;QACR,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACtB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACzB,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACtB,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACtB,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACvB,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACrB,YAAY,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QAC5B,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACvB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACzB,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACvB,wBAAwB,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;KACzC,CAAA;AACH,CAAC"}
@@ -0,0 +1,50 @@
1
+ import { type RedirectStore } from '../routing/index.js';
2
+ import type { CollectionDefinition } from '../types.js';
3
+ import type { ContentStore } from './store.js';
4
+ import type { ContentValues } from './types.js';
5
+ /**
6
+ * Writes the redirect a slug rename owes, by wrapping the store.
7
+ *
8
+ * `recordSlugChange` (`../routing/slug-change.ts`) has existed since before
9
+ * this file and is fully tested on its own — chain flattening, loop refusal,
10
+ * reversibility — but nothing in `store/` ever called it (fiche 12, task 3
11
+ * audit: "rien dans `packages/schema/src/store/` ne touche la table de
12
+ * redirections"). This is that missing call, made the same way
13
+ * `withSearchIndexing` wires the search index: by wrapping the store rather
14
+ * than hooking a transport, so REST, GraphQL and the theme renderer — all
15
+ * handed the same store instance by `serve.ts` — get it for free.
16
+ *
17
+ * **Why the published face is re-read, not the value `update`/`publish`
18
+ * return.** With drafts enabled, `update()` on an already-published entry only
19
+ * *stages* a change — contract A's own rule, reused rather than
20
+ * reimplemented: the live row does not move until `publish()`. The value
21
+ * `update()` returns is always the *working* face (`state: 'working'`), which
22
+ * would carry the new slug immediately and fire a redirect for a URL that is
23
+ * not public yet. Reading `state: 'published'` before and after the call,
24
+ * exactly as `search-indexing.ts`'s `reindex` already does for the same
25
+ * reason, makes "was this ever actually served" a fact the store answers
26
+ * rather than something this wrapper has to guess at.
27
+ */
28
+ export interface RedirectTrackingOptions {
29
+ readonly collection: CollectionDefinition;
30
+ readonly redirects: RedirectStore;
31
+ /**
32
+ * Called when recording the redirect fails. Defaults to swallowing it.
33
+ *
34
+ * A failed redirect write must never fail the content save that triggered
35
+ * it — a slow or locked redirects table is not a reason to lose an editor's
36
+ * rename. Callers with a logger pass one so the failure stays visible.
37
+ */
38
+ readonly onError?: (error: unknown) => void;
39
+ }
40
+ /**
41
+ * Wraps a `ContentStore` so that renaming the slug of a **published** entry
42
+ * writes a 301 from the old path to the new one, with no caller having to
43
+ * remember to.
44
+ *
45
+ * A collection with no `f.slug(...)` field is returned unwrapped — there is
46
+ * nothing this decorator could ever do for it, and wrapping it anyway would
47
+ * only cost two extra reads per write for nothing.
48
+ */
49
+ export declare function withRedirectTracking<TValues extends ContentValues = ContentValues>(store: ContentStore<TValues>, options: RedirectTrackingOptions): ContentStore<TValues>;
50
+ //# sourceMappingURL=redirect-tracking.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirect-tracking.d.ts","sourceRoot":"","sources":["../../src/store/redirect-tracking.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAoB,MAAM,qBAAqB,CAAA;AAC1E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,UAAU,EAAE,oBAAoB,CAAA;IACzC,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAA;IACjC;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CAC5C;AA2DD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,SAAS,aAAa,GAAG,aAAa,EAChF,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,EAC5B,OAAO,EAAE,uBAAuB,GAC/B,YAAY,CAAC,OAAO,CAAC,CA+BvB"}
@@ -0,0 +1,91 @@
1
+ import { recordSlugChange } from '../routing/index.js';
2
+ /** The name of the collection's `f.slug(...)` field, or null when it has none. */
3
+ function slugFieldOf(collection) {
4
+ const found = Object.entries(collection.fields).find(([, field]) => field.kind === 'slug');
5
+ return found === undefined ? null : found[0];
6
+ }
7
+ /** The `:name` segments of a route pattern, in order. */
8
+ function routeParamNames(pattern) {
9
+ return pattern
10
+ .split('/')
11
+ .filter((segment) => segment.startsWith(':'))
12
+ .map((segment) => segment.slice(1));
13
+ }
14
+ async function trackRename(store, options, slugField, id, before) {
15
+ if (before === null)
16
+ return;
17
+ try {
18
+ const after = await store.read(id, { state: 'published' });
19
+ if (after === null)
20
+ return;
21
+ const previousSlug = before.values[slugField];
22
+ const nextSlug = after.values[slugField];
23
+ if (typeof previousSlug !== 'string' || typeof nextSlug !== 'string')
24
+ return;
25
+ if (previousSlug === nextSlug)
26
+ return;
27
+ const pattern = options.collection.routing?.pattern;
28
+ const otherParams = {};
29
+ if (pattern !== undefined) {
30
+ for (const name of routeParamNames(pattern)) {
31
+ if (name === slugField)
32
+ continue;
33
+ const value = after.values[name];
34
+ if (typeof value === 'string')
35
+ otherParams[name] = value;
36
+ }
37
+ }
38
+ await recordSlugChange(options.redirects, {
39
+ collection: options.collection,
40
+ entryId: id,
41
+ status: 'published',
42
+ previousSlug,
43
+ nextSlug,
44
+ slugParam: slugField,
45
+ locale: after.locale,
46
+ ...(Object.keys(otherParams).length === 0 ? {} : { params: otherParams }),
47
+ });
48
+ }
49
+ catch (error) {
50
+ options.onError?.(error);
51
+ }
52
+ }
53
+ /**
54
+ * Wraps a `ContentStore` so that renaming the slug of a **published** entry
55
+ * writes a 301 from the old path to the new one, with no caller having to
56
+ * remember to.
57
+ *
58
+ * A collection with no `f.slug(...)` field is returned unwrapped — there is
59
+ * nothing this decorator could ever do for it, and wrapping it anyway would
60
+ * only cost two extra reads per write for nothing.
61
+ */
62
+ export function withRedirectTracking(store, options) {
63
+ const slugField = slugFieldOf(options.collection);
64
+ if (slugField === null)
65
+ return store;
66
+ async function before(id) {
67
+ return store.read(id, { state: 'published' });
68
+ }
69
+ return {
70
+ ...store,
71
+ update: async (id, input) => {
72
+ const previous = await before(id);
73
+ const result = await store.update(id, input);
74
+ await trackRename(store, options, slugField, id, previous);
75
+ return result;
76
+ },
77
+ publish: async (id, input) => {
78
+ const previous = await before(id);
79
+ const result = await store.publish(id, input);
80
+ await trackRename(store, options, slugField, id, previous);
81
+ return result;
82
+ },
83
+ restore: async (id, version, input) => {
84
+ const previous = await before(id);
85
+ const result = await store.restore(id, version, input);
86
+ await trackRename(store, options, slugField, id, previous);
87
+ return result;
88
+ },
89
+ };
90
+ }
91
+ //# sourceMappingURL=redirect-tracking.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirect-tracking.js","sourceRoot":"","sources":["../../src/store/redirect-tracking.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import type { ContentAction, ReviewState, ReviewTransition } from '../types.js';
2
+ /**
3
+ * The editorial workflow's transition table (`schema@2.1`, ADR-0027).
4
+ *
5
+ * A closed set of three transitions, each with the `from` states it accepts,
6
+ * the `to` state it produces, and the contract A action a caller needs to
7
+ * make it — the same shape the contract E order state machine already
8
+ * proved out. It lives on the server and nowhere else: an admin screen reads
9
+ * it to grey out a button, it never re-derives what is legal.
10
+ *
11
+ * `approve` does **not** produce `status: 'published'`. Approving and
12
+ * publishing stay two different actions, guarded by two different
13
+ * permissions (`publish` either way) — conflating them would publish by
14
+ * surprise the moment an editor clicks "Approve".
15
+ */
16
+ export interface ReviewTransitionRule {
17
+ readonly from: readonly ReviewState[];
18
+ readonly to: ReviewState;
19
+ /** The contract A action a caller needs, checked by the caller — never by this table. */
20
+ readonly action: ContentAction;
21
+ }
22
+ export declare const REVIEW_TRANSITION_TABLE: Readonly<Record<ReviewTransition, ReviewTransitionRule>>;
23
+ /**
24
+ * Checks a transition against the table, throwing `CONTENT_REVIEW_TRANSITION_INVALID`
25
+ * for an illegal jump. Returns the state the entry should move to.
26
+ *
27
+ * Permission is not this function's business: the caller already asserted
28
+ * `REVIEW_TRANSITION_TABLE[transition].action` before reaching here.
29
+ */
30
+ export declare function nextReviewState(transition: ReviewTransition, current: ReviewState, context: {
31
+ readonly collection: string;
32
+ readonly id: string;
33
+ }): ReviewState;
34
+ //# sourceMappingURL=review-transitions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-transitions.d.ts","sourceRoot":"","sources":["../../src/store/review-transitions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAE/E;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,SAAS,WAAW,EAAE,CAAA;IACrC,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAA;IACxB,yFAAyF;IACzF,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;CAC/B;AAED,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAC,MAAM,CAAC,gBAAgB,EAAE,oBAAoB,CAAC,CAS5F,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,gBAAgB,EAC5B,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE;IAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC5D,WAAW,CAWb"}
@@ -0,0 +1,51 @@
1
+ import { CogentaError } from '@cogenta/core';
2
+ export const REVIEW_TRANSITION_TABLE = {
3
+ // A contributor sends a fresh draft — or one sent back for changes — into
4
+ // the queue. Re-submitting after `changes-requested` is expected, not an
5
+ // error: that is the whole point of asking for changes.
6
+ submit: { from: ['none', 'changes-requested'], to: 'pending', action: 'update' },
7
+ // Only a pending entry can be approved or sent back — an entry nobody
8
+ // submitted has nothing for a reviewer to have looked at yet.
9
+ approve: { from: ['pending'], to: 'approved', action: 'publish' },
10
+ requestChanges: { from: ['pending'], to: 'changes-requested', action: 'publish' },
11
+ };
12
+ /**
13
+ * Checks a transition against the table, throwing `CONTENT_REVIEW_TRANSITION_INVALID`
14
+ * for an illegal jump. Returns the state the entry should move to.
15
+ *
16
+ * Permission is not this function's business: the caller already asserted
17
+ * `REVIEW_TRANSITION_TABLE[transition].action` before reaching here.
18
+ */
19
+ export function nextReviewState(transition, current, context) {
20
+ const rule = REVIEW_TRANSITION_TABLE[transition];
21
+ if (!rule.from.includes(current)) {
22
+ throw new CogentaError({
23
+ code: 'CONTENT_REVIEW_TRANSITION_INVALID',
24
+ message: `"${context.id}" of "${context.collection}" cannot ${transitionVerb(transition)}: its review state is "${current}", not ${rule.from.map((state) => `"${state}"`).join(' or ')}.`,
25
+ hint: transitionHint(transition, current),
26
+ details: { collection: context.collection, id: context.id, transition, from: current },
27
+ });
28
+ }
29
+ return rule.to;
30
+ }
31
+ function transitionVerb(transition) {
32
+ switch (transition) {
33
+ case 'submit':
34
+ return 'be submitted for review';
35
+ case 'approve':
36
+ return 'be approved';
37
+ case 'requestChanges':
38
+ return 'have changes requested';
39
+ }
40
+ }
41
+ function transitionHint(transition, current) {
42
+ if (transition === 'submit') {
43
+ return current === 'pending'
44
+ ? 'It is already waiting for a reviewer.'
45
+ : 'Only a fresh entry or one sent back with requested changes can be submitted.';
46
+ }
47
+ return current === 'none'
48
+ ? 'Nobody submitted this entry yet — ask its author to submit it first.'
49
+ : 'Only an entry waiting for review (pending) can be approved or sent back.';
50
+ }
51
+ //# sourceMappingURL=review-transitions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-transitions.js","sourceRoot":"","sources":["../../src/store/review-transitions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAwB5C,MAAM,CAAC,MAAM,uBAAuB,GAA6D;IAC/F,0EAA0E;IAC1E,yEAAyE;IACzE,wDAAwD;IACxD,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE;IAChF,sEAAsE;IACtE,8DAA8D;IAC9D,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE;IACjE,cAAc,EAAE,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,mBAAmB,EAAE,MAAM,EAAE,SAAS,EAAE;CAClF,CAAA;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAC7B,UAA4B,EAC5B,OAAoB,EACpB,OAA6D;IAE7D,MAAM,IAAI,GAAG,uBAAuB,CAAC,UAAU,CAAC,CAAA;IAChD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,YAAY,CAAC;YACrB,IAAI,EAAE,mCAAmC;YACzC,OAAO,EAAE,IAAI,OAAO,CAAC,EAAE,SAAS,OAAO,CAAC,UAAU,YAAY,cAAc,CAAC,UAAU,CAAC,0BAA0B,OAAO,UAAU,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG;YACzL,IAAI,EAAE,cAAc,CAAC,UAAU,EAAE,OAAO,CAAC;YACzC,OAAO,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE;SACvF,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,IAAI,CAAC,EAAE,CAAA;AAChB,CAAC;AAED,SAAS,cAAc,CAAC,UAA4B;IAClD,QAAQ,UAAU,EAAE,CAAC;QACnB,KAAK,QAAQ;YACX,OAAO,yBAAyB,CAAA;QAClC,KAAK,SAAS;YACZ,OAAO,aAAa,CAAA;QACtB,KAAK,gBAAgB;YACnB,OAAO,wBAAwB,CAAA;IACnC,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,UAA4B,EAAE,OAAoB;IACxE,IAAI,UAAU,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,OAAO,KAAK,SAAS;YAC1B,CAAC,CAAC,uCAAuC;YACzC,CAAC,CAAC,8EAA8E,CAAA;IACpF,CAAC;IACD,OAAO,OAAO,KAAK,MAAM;QACvB,CAAC,CAAC,sEAAsE;QACxE,CAAC,CAAC,0EAA0E,CAAA;AAChF,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { type ContentAction } from '../types.js';
2
+ import type { RolePermissionOverrideRecord, RolePermissionTargetType } from './role-permission-store.js';
3
+ export declare const ROLE_PERMISSION_EXPORT_VERSION = 1;
4
+ export interface RolePermissionExportRow {
5
+ readonly targetType: RolePermissionTargetType;
6
+ readonly targetName: string;
7
+ readonly action: ContentAction;
8
+ readonly roles: readonly string[];
9
+ readonly own: boolean;
10
+ readonly updatedAt: string;
11
+ readonly updatedBy: string | null;
12
+ }
13
+ export interface RolePermissionExportFile {
14
+ readonly version: typeof ROLE_PERMISSION_EXPORT_VERSION;
15
+ readonly exportedAt: string;
16
+ readonly overrides: readonly RolePermissionExportRow[];
17
+ }
18
+ /** Sorted for a clean, stable git diff between two exports — never insertion order. */
19
+ export declare function serialiseRolePermissionExport(records: readonly RolePermissionOverrideRecord[], now?: () => Date): RolePermissionExportFile;
20
+ /** The round trip's other half: `JSON.parse` a file `serialiseRolePermissionExport` wrote, checked shape by shape. */
21
+ export declare function parseRolePermissionExport(value: unknown): RolePermissionExportFile;
22
+ //# sourceMappingURL=role-permission-export.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-permission-export.d.ts","sourceRoot":"","sources":["../../src/store/role-permission-export.ts"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAA;AACjE,OAAO,KAAK,EACV,4BAA4B,EAC5B,wBAAwB,EACzB,MAAM,4BAA4B,CAAA;AAmBnC,eAAO,MAAM,8BAA8B,IAAI,CAAA;AAE/C,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,UAAU,EAAE,wBAAwB,CAAA;IAC7C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAA;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,8BAA8B,CAAA;IACvD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,uBAAuB,EAAE,CAAA;CACvD;AAMD,uFAAuF;AACvF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,SAAS,4BAA4B,EAAE,EAChD,GAAG,GAAE,MAAM,IAAuB,GACjC,wBAAwB,CAG1B;AAyDD,sHAAsH;AACtH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,wBAAwB,CAqBlF"}