@cogenta/schema 0.2.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.
Files changed (193) 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 +47 -4
  71. package/dist/store/index.d.ts.map +1 -1
  72. package/dist/store/index.js +26 -2
  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 +146 -0
  79. package/dist/store/menu-store.d.ts.map +1 -0
  80. package/dist/store/menu-store.js +565 -0
  81. package/dist/store/menu-store.js.map +1 -0
  82. package/dist/store/menu-tables.d.ts +38 -0
  83. package/dist/store/menu-tables.d.ts.map +1 -0
  84. package/dist/store/menu-tables.js +117 -0
  85. package/dist/store/menu-tables.js.map +1 -0
  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-enqueue.d.ts +37 -0
  127. package/dist/store/scheduled-publish-enqueue.d.ts.map +1 -0
  128. package/dist/store/scheduled-publish-enqueue.js +51 -0
  129. package/dist/store/scheduled-publish-enqueue.js.map +1 -0
  130. package/dist/store/scheduled-publish-failures.d.ts +36 -0
  131. package/dist/store/scheduled-publish-failures.d.ts.map +1 -0
  132. package/dist/store/scheduled-publish-failures.js +67 -0
  133. package/dist/store/scheduled-publish-failures.js.map +1 -0
  134. package/dist/store/schema-2-1-migration.d.ts +29 -0
  135. package/dist/store/schema-2-1-migration.d.ts.map +1 -0
  136. package/dist/store/schema-2-1-migration.js +71 -0
  137. package/dist/store/schema-2-1-migration.js.map +1 -0
  138. package/dist/store/search-console-store.d.ts +44 -0
  139. package/dist/store/search-console-store.d.ts.map +1 -0
  140. package/dist/store/search-console-store.js +90 -0
  141. package/dist/store/search-console-store.js.map +1 -0
  142. package/dist/store/search-console-tables.d.ts +14 -0
  143. package/dist/store/search-console-tables.d.ts.map +1 -0
  144. package/dist/store/search-console-tables.js +29 -0
  145. package/dist/store/search-console-tables.js.map +1 -0
  146. package/dist/store/search-indexing.d.ts +36 -0
  147. package/dist/store/search-indexing.d.ts.map +1 -1
  148. package/dist/store/search-indexing.js +33 -0
  149. package/dist/store/search-indexing.js.map +1 -1
  150. package/dist/store/site-settings-registry.d.ts +164 -0
  151. package/dist/store/site-settings-registry.d.ts.map +1 -0
  152. package/dist/store/site-settings-registry.js +1160 -0
  153. package/dist/store/site-settings-registry.js.map +1 -0
  154. package/dist/store/site-settings-store.d.ts +42 -0
  155. package/dist/store/site-settings-store.d.ts.map +1 -0
  156. package/dist/store/site-settings-store.js +97 -0
  157. package/dist/store/site-settings-store.js.map +1 -0
  158. package/dist/store/site-settings-tables.d.ts +29 -0
  159. package/dist/store/site-settings-tables.d.ts.map +1 -0
  160. package/dist/store/site-settings-tables.js +48 -0
  161. package/dist/store/site-settings-tables.js.map +1 -0
  162. package/dist/store/store.d.ts +57 -2
  163. package/dist/store/store.d.ts.map +1 -1
  164. package/dist/store/store.js +278 -37
  165. package/dist/store/store.js.map +1 -1
  166. package/dist/store/tables.d.ts.map +1 -1
  167. package/dist/store/tables.js +11 -0
  168. package/dist/store/tables.js.map +1 -1
  169. package/dist/store/taxonomy-store.d.ts.map +1 -1
  170. package/dist/store/taxonomy-store.js +90 -39
  171. package/dist/store/taxonomy-store.js.map +1 -1
  172. package/dist/store/taxonomy-usage.d.ts +47 -0
  173. package/dist/store/taxonomy-usage.d.ts.map +1 -0
  174. package/dist/store/taxonomy-usage.js +101 -0
  175. package/dist/store/taxonomy-usage.js.map +1 -0
  176. package/dist/store/theme-store.d.ts +73 -0
  177. package/dist/store/theme-store.d.ts.map +1 -0
  178. package/dist/store/theme-store.js +193 -0
  179. package/dist/store/theme-store.js.map +1 -0
  180. package/dist/store/types.d.ts +36 -1
  181. package/dist/store/types.d.ts.map +1 -1
  182. package/dist/store/values.d.ts.map +1 -1
  183. package/dist/store/values.js +28 -1
  184. package/dist/store/values.js.map +1 -1
  185. package/dist/system-fields.d.ts +8 -1
  186. package/dist/system-fields.d.ts.map +1 -1
  187. package/dist/system-fields.js +14 -1
  188. package/dist/system-fields.js.map +1 -1
  189. package/dist/types.d.ts +54 -1
  190. package/dist/types.d.ts.map +1 -1
  191. package/dist/types.js +21 -0
  192. package/dist/types.js.map +1 -1
  193. package/package.json +2 -2
@@ -0,0 +1,37 @@
1
+ import type { QueueDriver } from '@cogenta/core';
2
+ import type { CollectionDefinition } from '../types.js';
3
+ import type { ContentStore } from './store.js';
4
+ import type { ContentValues } from './types.js';
5
+ /**
6
+ * Queues the real publication of an entry saved with `status: 'scheduled'`.
7
+ *
8
+ * `../scheduling/publish.ts` was complete and tested from L1 —
9
+ * `schedulePublication`, `registerScheduledPublishing`, the whole
10
+ * `QueueDriver`-based mechanism — but nothing anywhere in the repository ever
11
+ * called `schedulePublication`. An editor could set an entry to "scheduled"
12
+ * with a future date and nothing would ever happen: the admin showed a
13
+ * read-only badge, honest about not being wired to anything (L10 audit
14
+ * follow-up).
15
+ *
16
+ * Wrapping the *store* rather than a route is the same choice
17
+ * `withSearchIndexing`/`withLifecycleEvents` already make, for the same
18
+ * reason: REST and GraphQL are handed the same store instances by
19
+ * `serve.ts`, so one wrap covers both transports.
20
+ *
21
+ * This module only ever *enqueues*. The actual state change — moving the
22
+ * entry to `published` — happens in the handler `serve.ts` registers with
23
+ * `registerScheduledPublishing`, which re-reads the entry before publishing
24
+ * it: an entry edited back to `draft` before its hour comes must not publish
25
+ * just because an older job is still sitting in the queue. That re-check is
26
+ * what makes it safe to enqueue again on every save rather than tracking
27
+ * (and cancelling) a previous job id here — two jobs racing to publish the
28
+ * same already-published entry is a no-op, not a bug.
29
+ */
30
+ export interface ScheduledPublishEnqueueOptions {
31
+ readonly collection: CollectionDefinition;
32
+ readonly queue: QueueDriver;
33
+ /** Called when the enqueue itself fails. Defaults to swallowing it. */
34
+ readonly onError?: (error: unknown) => void;
35
+ }
36
+ export declare function withScheduledPublishEnqueue<TValues extends ContentValues = ContentValues>(store: ContentStore<TValues>, options: ScheduledPublishEnqueueOptions): ContentStore<TValues>;
37
+ //# sourceMappingURL=scheduled-publish-enqueue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduled-publish-enqueue.d.ts","sourceRoot":"","sources":["../../src/store/scheduled-publish-enqueue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAEhD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,KAAK,EAAgB,aAAa,EAAE,MAAM,YAAY,CAAA;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,UAAU,EAAE,oBAAoB,CAAA;IACzC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAA;IAC3B,uEAAuE;IACvE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CAC5C;AAwBD,wBAAgB,2BAA2B,CAAC,OAAO,SAAS,aAAa,GAAG,aAAa,EACvF,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,EAC5B,OAAO,EAAE,8BAA8B,GACtC,YAAY,CAAC,OAAO,CAAC,CA4BvB"}
@@ -0,0 +1,51 @@
1
+ import { schedulePublication } from '../scheduling/publish.js';
2
+ async function enqueueIfScheduled(entry, options) {
3
+ if (entry.status !== 'scheduled')
4
+ return;
5
+ if (entry.publishedAt === null)
6
+ return;
7
+ const publishAt = Date.parse(entry.publishedAt);
8
+ if (!Number.isFinite(publishAt))
9
+ return;
10
+ try {
11
+ await schedulePublication(options.queue, {
12
+ collection: options.collection.name,
13
+ entryId: entry.id,
14
+ locale: entry.locale,
15
+ publishAt,
16
+ });
17
+ }
18
+ catch (error) {
19
+ options.onError?.(error);
20
+ }
21
+ }
22
+ export function withScheduledPublishEnqueue(store, options) {
23
+ return {
24
+ ...store,
25
+ create: async (input) => {
26
+ const created = await store.create(input);
27
+ await enqueueIfScheduled(created, options);
28
+ return created;
29
+ },
30
+ update: async (id, input) => {
31
+ const updated = await store.update(id, input);
32
+ await enqueueIfScheduled(updated, options);
33
+ return updated;
34
+ },
35
+ // The one route that actually moves an *existing* entry into
36
+ // `scheduled`: `update()` never changes `status` (contract A keeps that
37
+ // to `publish`/`unpublish`), so `unpublish(id, { status: 'scheduled',
38
+ // publishedAt })` is where a schedule set from the admin really lands.
39
+ unpublish: async (id, input) => {
40
+ const unpublished = await store.unpublish(id, input);
41
+ await enqueueIfScheduled(unpublished, options);
42
+ return unpublished;
43
+ },
44
+ restore: async (id, version, input) => {
45
+ const restored = await store.restore(id, version, input);
46
+ await enqueueIfScheduled(restored, options);
47
+ return restored;
48
+ },
49
+ };
50
+ }
51
+ //# sourceMappingURL=scheduled-publish-enqueue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduled-publish-enqueue.js","sourceRoot":"","sources":["../../src/store/scheduled-publish-enqueue.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAqC9D,KAAK,UAAU,kBAAkB,CAC/B,KAA4B,EAC5B,OAAuC;IAEvC,IAAI,KAAK,CAAC,MAAM,KAAK,WAAW;QAAE,OAAM;IACxC,IAAI,KAAK,CAAC,WAAW,KAAK,IAAI;QAAE,OAAM;IAEtC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAA;IAC/C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAM;IAEvC,IAAI,CAAC;QACH,MAAM,mBAAmB,CAAC,OAAO,CAAC,KAAK,EAAE;YACvC,UAAU,EAAE,OAAO,CAAC,UAAU,CAAC,IAAI;YACnC,OAAO,EAAE,KAAK,CAAC,EAAE;YACjB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,SAAS;SACV,CAAC,CAAA;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAA;IAC1B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,2BAA2B,CACzC,KAA4B,EAC5B,OAAuC;IAEvC,OAAO;QACL,GAAG,KAAK;QACR,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;YACtB,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACzC,MAAM,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;YAC1C,OAAO,OAAO,CAAA;QAChB,CAAC;QACD,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE;YAC1B,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,CAAA;YAC7C,MAAM,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;YAC1C,OAAO,OAAO,CAAA;QAChB,CAAC;QACD,6DAA6D;QAC7D,wEAAwE;QACxE,sEAAsE;QACtE,uEAAuE;QACvE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE;YAC7B,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,EAAE,EAAE,KAAK,CAAC,CAAA;YACpD,MAAM,kBAAkB,CAAC,WAAW,EAAE,OAAO,CAAC,CAAA;YAC9C,OAAO,WAAW,CAAA;QACpB,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YACpC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;YACxD,MAAM,kBAAkB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;YAC3C,OAAO,QAAQ,CAAA;QACjB,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,36 @@
1
+ import { type DatabaseHandle } from '@cogenta/core';
2
+ /**
3
+ * "Contenu programmé dont la publication a échoué" — fiche 38 task 1's last
4
+ * named source. `registerScheduledPublishing`'s handler (wired by
5
+ * `cogenta serve`) can throw — the target collection's own validation refuses
6
+ * the entry, a `restrict` foreign key blocks it, a driver hiccups — and until
7
+ * now nothing surfaced that anywhere: the queue's own row silently sits in
8
+ * `status: 'failed'` after its retries are exhausted, and the only way to
9
+ * notice was to already suspect something and go look at the table by hand.
10
+ *
11
+ * Deliberately its own tiny table rather than a query against the queue
12
+ * driver's internal job table: the queue interface (`@cogenta/core`) has no
13
+ * "list jobs of this name past their retries" method, and reaching past it
14
+ * into one specific driver's schema would tie this notice to whichever queue
15
+ * driver a site happens to run — the opposite of R1. `record`/`clear` are
16
+ * called from the same handler that already knows collection/entry/locale,
17
+ * so nothing here has to guess.
18
+ */
19
+ export declare const SCHEDULED_PUBLISH_FAILURES_TABLE = "cogenta_scheduled_publish_failures";
20
+ export interface ScheduledPublishFailure {
21
+ readonly collection: string;
22
+ readonly entryId: string;
23
+ readonly locale: string;
24
+ readonly error: string;
25
+ readonly failedAt: string;
26
+ }
27
+ export interface ScheduledPublishFailureStore {
28
+ ensureTable(): Promise<void>;
29
+ /** Idempotent: a second failed attempt on the same entry replaces the first, it does not pile up. */
30
+ record(failure: Omit<ScheduledPublishFailure, 'failedAt'>): Promise<void>;
31
+ /** Called once a later attempt (or a fresh schedule) actually publishes the entry. */
32
+ clear(collection: string, entryId: string, locale: string): Promise<void>;
33
+ list(): Promise<readonly ScheduledPublishFailure[]>;
34
+ }
35
+ export declare function createScheduledPublishFailureStore(db: DatabaseHandle, now?: () => number): ScheduledPublishFailureStore;
36
+ //# sourceMappingURL=scheduled-publish-failures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduled-publish-failures.d.ts","sourceRoot":"","sources":["../../src/store/scheduled-publish-failures.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAA0B,MAAM,eAAe,CAAA;AAI3E;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,gCAAgC,uCAAuC,CAAA;AAEpF,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,4BAA4B;IAC3C,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,qGAAqG;IACrG,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,uBAAuB,EAAE,UAAU,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACzE,sFAAsF;IACtF,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACzE,IAAI,IAAI,OAAO,CAAC,SAAS,uBAAuB,EAAE,CAAC,CAAA;CACpD;AAUD,wBAAgB,kCAAkC,CAChD,EAAE,EAAE,cAAc,EAClB,GAAG,GAAE,MAAM,MAAiB,GAC3B,4BAA4B,CAgD9B"}
@@ -0,0 +1,67 @@
1
+ import { identifier, newId, sql } from '@cogenta/core';
2
+ import { textColumn, timestampColumn, uuidColumn } from './columns.js';
3
+ import { indexName } from './naming.js';
4
+ /**
5
+ * "Contenu programmé dont la publication a échoué" — fiche 38 task 1's last
6
+ * named source. `registerScheduledPublishing`'s handler (wired by
7
+ * `cogenta serve`) can throw — the target collection's own validation refuses
8
+ * the entry, a `restrict` foreign key blocks it, a driver hiccups — and until
9
+ * now nothing surfaced that anywhere: the queue's own row silently sits in
10
+ * `status: 'failed'` after its retries are exhausted, and the only way to
11
+ * notice was to already suspect something and go look at the table by hand.
12
+ *
13
+ * Deliberately its own tiny table rather than a query against the queue
14
+ * driver's internal job table: the queue interface (`@cogenta/core`) has no
15
+ * "list jobs of this name past their retries" method, and reaching past it
16
+ * into one specific driver's schema would tie this notice to whichever queue
17
+ * driver a site happens to run — the opposite of R1. `record`/`clear` are
18
+ * called from the same handler that already knows collection/entry/locale,
19
+ * so nothing here has to guess.
20
+ */
21
+ export const SCHEDULED_PUBLISH_FAILURES_TABLE = 'cogenta_scheduled_publish_failures';
22
+ export function createScheduledPublishFailureStore(db, now = Date.now) {
23
+ const table = identifier(SCHEDULED_PUBLISH_FAILURES_TABLE, db.dialect);
24
+ const d = db.dialect;
25
+ return {
26
+ async ensureTable() {
27
+ await db.query(sql `
28
+ create table if not exists ${table} (
29
+ id ${uuidColumn(d)} not null primary key,
30
+ collection ${textColumn(d, 128)} not null,
31
+ entry_id ${uuidColumn(d)} not null,
32
+ locale ${textColumn(d, 16)} not null,
33
+ error ${textColumn(d, 2048)} not null,
34
+ failed_at ${timestampColumn(d)} not null
35
+ )`);
36
+ await db
37
+ .query(sql `
38
+ create unique index ${identifier(indexName(SCHEDULED_PUBLISH_FAILURES_TABLE, 'entry'), d)}
39
+ on ${table} (collection, entry_id, locale)`)
40
+ .catch(() => undefined); // already there — no portable "if not exists" for indexes
41
+ },
42
+ async record(failure) {
43
+ await db.query(sql `
44
+ delete from ${table}
45
+ where collection = ${failure.collection} and entry_id = ${failure.entryId} and locale = ${failure.locale}`);
46
+ await db.query(sql `
47
+ insert into ${table} (id, collection, entry_id, locale, error, failed_at)
48
+ values (${newId(now)}, ${failure.collection}, ${failure.entryId}, ${failure.locale}, ${failure.error}, ${new Date(now()).toISOString()})`);
49
+ },
50
+ async clear(collection, entryId, locale) {
51
+ await db.query(sql `
52
+ delete from ${table}
53
+ where collection = ${collection} and entry_id = ${entryId} and locale = ${locale}`);
54
+ },
55
+ async list() {
56
+ const result = await db.query(sql `select * from ${table} order by failed_at desc`);
57
+ return result.rows.map((row) => ({
58
+ collection: row.collection,
59
+ entryId: row.entry_id,
60
+ locale: row.locale,
61
+ error: row.error,
62
+ failedAt: row.failed_at,
63
+ }));
64
+ },
65
+ };
66
+ }
67
+ //# sourceMappingURL=scheduled-publish-failures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduled-publish-failures.js","sourceRoot":"","sources":["../../src/store/scheduled-publish-failures.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAA;AAC3E,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEvC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAG,oCAAoC,CAAA;AA2BpF,MAAM,UAAU,kCAAkC,CAChD,EAAkB,EAClB,GAAG,GAAiB,IAAI,CAAC,GAAG;IAE5B,MAAM,KAAK,GAAG,UAAU,CAAC,gCAAgC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAA;IACtE,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,CAAA;IAEpB,OAAO;QACL,KAAK,CAAC,WAAW;YACf,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,CAAA;qCACa,KAAK;eAC3B,UAAU,CAAC,CAAC,CAAC;uBACL,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC;qBACpB,UAAU,CAAC,CAAC,CAAC;mBACf,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC;kBAClB,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC;sBACf,eAAe,CAAC,CAAC,CAAC;UAC9B,CAAC,CAAA;YACL,MAAM,EAAE;iBACL,KAAK,CAAC,GAAG,CAAA;gCACc,UAAU,CAAC,SAAS,CAAC,gCAAgC,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;eACpF,KAAK,iCAAiC,CAAC;iBAC7C,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA,CAAC,0DAA0D;QACtF,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,OAAO;YAClB,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,CAAA;sBACF,KAAK;6BACE,OAAO,CAAC,UAAU,mBAAmB,OAAO,CAAC,OAAO,iBAAiB,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;YAC7G,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,CAAA;sBACF,KAAK;kBACT,KAAK,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,UAAU,KAAK,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,MAAM,KAAK,OAAO,CAAC,KAAK,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAA;QAC9I,CAAC;QAED,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM;YACrC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,CAAA;sBACF,KAAK;6BACE,UAAU,mBAAmB,OAAO,iBAAiB,MAAM,EAAE,CAAC,CAAA;QACvF,CAAC;QAED,KAAK,CAAC,IAAI;YACR,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,KAAK,CAAa,GAAG,CAAA,iBAAiB,KAAK,0BAA0B,CAAC,CAAA;YAC9F,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBAC/B,UAAU,EAAE,GAAG,CAAC,UAAU;gBAC1B,OAAO,EAAE,GAAG,CAAC,QAAQ;gBACrB,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,KAAK,EAAE,GAAG,CAAC,KAAK;gBAChB,QAAQ,EAAE,GAAG,CAAC,SAAS;aACxB,CAAC,CAAC,CAAA;QACL,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { type Migration } from '@cogenta/core';
2
+ import type { CollectionDefinition } from '../types.js';
3
+ /**
4
+ * The `schema@2.0 → 2.1` migration (ADR-0027).
5
+ *
6
+ * Adds the editorial workflow's two columns to every collection's entry
7
+ * table — `review_state` (`schema@2.1`'s `reviewState`, orthogonal to
8
+ * `status` exactly as `deleted_at` is) and `assigned_reviewer`. Both are
9
+ * additive and reversible:
10
+ *
11
+ * - `review_state` is `not null default 'none'`, so every existing row reads
12
+ * `'none'` the instant the column exists — a client that reads `status`
13
+ * and ignores everything else gets exactly the values it always did.
14
+ * - `assigned_reviewer` is nullable, defaulting to unassigned.
15
+ *
16
+ * **Not marked `destructive`.** Unlike `schema@1.0 → 2.0`, whose rollback
17
+ * discarded the trash and every classification, rolling this one back only
18
+ * drops two columns nothing yet depends on the day it ships — there is no
19
+ * site in production, so no real workflow state exists to lose. A future
20
+ * migration that adds this to a live site with real review history should
21
+ * reconsider that if the down path would then discard something real.
22
+ */
23
+ export interface Schema21MigrationOptions {
24
+ readonly collections: readonly CollectionDefinition[];
25
+ /** Overridable so a site can slot this into its own numbering. */
26
+ readonly id?: string;
27
+ }
28
+ export declare function schema21Migration(options: Schema21MigrationOptions): Migration;
29
+ //# sourceMappingURL=schema-2-1-migration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-2-1-migration.d.ts","sourceRoot":"","sources":["../../src/store/schema-2-1-migration.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,SAAS,EAKf,MAAM,eAAe,CAAA;AACtB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAIvD;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,WAAW,EAAE,SAAS,oBAAoB,EAAE,CAAA;IACrD,kEAAkE;IAClE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CACrB;AAwDD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,SAAS,CAsC9E"}
@@ -0,0 +1,71 @@
1
+ import { identifier, sql, unsafeRaw, } from '@cogenta/core';
2
+ import { textColumn } from './columns.js';
3
+ import { entriesTable, indexName } from './naming.js';
4
+ const DEFAULT_ID = '0003_schema_2_1_editorial_workflow';
5
+ function addReviewState(collection, dialect) {
6
+ const table = identifier(entriesTable(collection.name), dialect);
7
+ return sql `alter table ${table}
8
+ add column ${identifier('review_state', dialect)} ${textColumn(dialect, 24)}
9
+ not null default ${unsafeRaw("'none'")}`;
10
+ }
11
+ function addAssignedReviewer(collection, dialect) {
12
+ const table = identifier(entriesTable(collection.name), dialect);
13
+ return sql `alter table ${table}
14
+ add column ${identifier('assigned_reviewer', dialect)} ${textColumn(dialect, 64)}`;
15
+ }
16
+ function dropReviewState(collection, dialect) {
17
+ const table = identifier(entriesTable(collection.name), dialect);
18
+ return sql `alter table ${table} drop column ${identifier('review_state', dialect)}`;
19
+ }
20
+ function dropAssignedReviewer(collection, dialect) {
21
+ const table = identifier(entriesTable(collection.name), dialect);
22
+ return sql `alter table ${table} drop column ${identifier('assigned_reviewer', dialect)}`;
23
+ }
24
+ function reviewIndex(collection, dialect) {
25
+ const name = entriesTable(collection.name);
26
+ return sql `create index ${identifier(indexName(name, 'review'), dialect)}
27
+ on ${identifier(name, dialect)}
28
+ (${identifier('review_state', dialect)}, ${identifier('assigned_reviewer', dialect)})`;
29
+ }
30
+ /** MySQL needs the table named to drop an index; SQLite (and Postgres) name it directly. */
31
+ function dropReviewIndexOnTable(collection, dialect) {
32
+ return sql `drop index ${identifier(indexName(entriesTable(collection.name), 'review'), dialect)}
33
+ on ${identifier(entriesTable(collection.name), dialect)}`;
34
+ }
35
+ function dropReviewIndexStandalone(collection, dialect) {
36
+ return sql `drop index ${identifier(indexName(entriesTable(collection.name), 'review'), dialect)}`;
37
+ }
38
+ export function schema21Migration(options) {
39
+ const { collections } = options;
40
+ return {
41
+ id: options.id ?? DEFAULT_ID,
42
+ name: 'schema@2.1 — editorial workflow and owner permission',
43
+ destructive: false,
44
+ impact: "Adds review_state (not null, default 'none') and a nullable assigned_reviewer column " +
45
+ 'to every collection. Moves no existing row and changes no existing value: a client ' +
46
+ 'that reads status and ignores the rest sees exactly what it always did. The rollback ' +
47
+ 'drops both columns, which is safe today because no site in production carries real ' +
48
+ 'review history yet.',
49
+ estimatedDurationMs: 300 * Math.max(1, collections.length),
50
+ up: async (tx) => {
51
+ const dialect = tx.dialect;
52
+ for (const collection of collections) {
53
+ await tx.query(addReviewState(collection, dialect));
54
+ await tx.query(addAssignedReviewer(collection, dialect));
55
+ // `create index` has no portable `if not exists`; a failure here means
56
+ // it is already there, which is the state we wanted anyway.
57
+ await tx.query(reviewIndex(collection, dialect)).catch(() => undefined);
58
+ }
59
+ },
60
+ down: async (tx) => {
61
+ const dialect = tx.dialect;
62
+ for (const collection of [...collections].reverse()) {
63
+ const drop = dialect === 'mysql' ? dropReviewIndexOnTable : dropReviewIndexStandalone;
64
+ await tx.query(drop(collection, dialect)).catch(() => undefined);
65
+ await tx.query(dropAssignedReviewer(collection, dialect));
66
+ await tx.query(dropReviewState(collection, dialect));
67
+ }
68
+ },
69
+ };
70
+ }
71
+ //# sourceMappingURL=schema-2-1-migration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-2-1-migration.js","sourceRoot":"","sources":["../../src/store/schema-2-1-migration.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { type DatabaseHandle } from '@cogenta/core';
2
+ /**
3
+ * The one Google Search Console connection a site can have (fiche 70 task
4
+ * 4, ADR-0032). Same encryption discipline as `@cogenta/agents`'
5
+ * `ProviderConfigStore` and `@cogenta/mcp`'s connection store — AES-256-GCM
6
+ * via `node:crypto`, key derived from `COGENTA_AUTH_SIGNING_KEY` with a
7
+ * purpose-specific salt so the derived key is never the literal signing key
8
+ * reused across purposes (R7: no second secret to generate, rotate or
9
+ * lose). The refresh token is the only thing encrypted; `siteUrl` is not a
10
+ * secret (it is the property URL, already public knowledge to anyone who
11
+ * knows the site's own domain).
12
+ *
13
+ * Real database table (`ensureSearchConsoleConnectionTable`), not a file
14
+ * store: a Search Console connection is exactly the kind of "fixed row, not
15
+ * schema-declared content" `menu-tables.ts`/`pattern-tables.ts` already
16
+ * established a home for in this package, and it gets the same
17
+ * SQLite/Postgres/MySQL portability their tables already have for free.
18
+ */
19
+ export interface SearchConsoleConnectionSummary {
20
+ readonly siteUrl: string;
21
+ readonly connectedAt: string;
22
+ readonly updatedAt: string;
23
+ }
24
+ export interface ConnectSearchConsoleInput {
25
+ readonly siteUrl: string;
26
+ readonly refreshToken: string;
27
+ }
28
+ export interface SearchConsoleConnectionStore {
29
+ /** `null` when no site has ever completed the OAuth flow. */
30
+ read(): Promise<SearchConsoleConnectionSummary | null>;
31
+ /** Encrypts and persists `refreshToken`; replaces any existing connection (a site connects to at most one GSC property at a time). */
32
+ connect(input: ConnectSearchConsoleInput): Promise<SearchConsoleConnectionSummary>;
33
+ disconnect(): Promise<void>;
34
+ /** The one place the real refresh token is ever decrypted. Throws `SEARCH_CONSOLE_NOT_CONNECTED` if there is none. */
35
+ decryptRefreshToken(): Promise<string>;
36
+ }
37
+ export interface SearchConsoleConnectionStoreOptions {
38
+ readonly db: DatabaseHandle;
39
+ /** `COGENTA_AUTH_SIGNING_KEY` — the encryption key is derived from it, never stored itself. */
40
+ readonly signingKey: string;
41
+ readonly now?: () => Date;
42
+ }
43
+ export declare function createSearchConsoleConnectionStore(options: SearchConsoleConnectionStoreOptions): SearchConsoleConnectionStore;
44
+ //# sourceMappingURL=search-console-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-console-store.d.ts","sourceRoot":"","sources":["../../src/store/search-console-store.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,cAAc,EAAmB,MAAM,eAAe,CAAA;AAMlF;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B;AAED,MAAM,WAAW,4BAA4B;IAC3C,6DAA6D;IAC7D,IAAI,IAAI,OAAO,CAAC,8BAA8B,GAAG,IAAI,CAAC,CAAA;IACtD,sIAAsI;IACtI,OAAO,CAAC,KAAK,EAAE,yBAAyB,GAAG,OAAO,CAAC,8BAA8B,CAAC,CAAA;IAClF,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3B,sHAAsH;IACtH,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;CACvC;AA+BD,MAAM,WAAW,mCAAmC;IAClD,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAA;IAC3B,+FAA+F;IAC/F,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;CAC1B;AAED,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,mCAAmC,GAC3C,4BAA4B,CA+E9B"}
@@ -0,0 +1,90 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto';
2
+ import { CogentaError, identifier, sql } from '@cogenta/core';
3
+ import { SEARCH_CONSOLE_CONNECTION_ID, SEARCH_CONSOLE_CONNECTION_TABLE, } from './search-console-tables.js';
4
+ const KEY_DERIVATION_SALT = 'cogenta-search-console-secrets-v1';
5
+ const ALGORITHM = 'aes-256-gcm';
6
+ function deriveKey(signingKey) {
7
+ return scryptSync(signingKey, KEY_DERIVATION_SALT, 32);
8
+ }
9
+ function notConnected() {
10
+ return new CogentaError({
11
+ code: 'SEARCH_CONSOLE_NOT_CONNECTED',
12
+ message: 'No site has connected a Google Search Console property yet.',
13
+ hint: "Connect one from the SEO screen's Diagnostics tab first.",
14
+ });
15
+ }
16
+ function toSummary(row) {
17
+ return { siteUrl: row.site_url, connectedAt: row.connected_at, updatedAt: row.updated_at };
18
+ }
19
+ export function createSearchConsoleConnectionStore(options) {
20
+ const { db } = options;
21
+ const now = options.now ?? (() => new Date());
22
+ const key = deriveKey(options.signingKey);
23
+ const table = identifier(SEARCH_CONSOLE_CONNECTION_TABLE, db.dialect);
24
+ function encrypt(plaintext) {
25
+ const iv = randomBytes(12);
26
+ const cipher = createCipheriv(ALGORITHM, key, iv);
27
+ const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
28
+ return {
29
+ iv: iv.toString('base64'),
30
+ authTag: cipher.getAuthTag().toString('base64'),
31
+ ciphertext: ciphertext.toString('base64'),
32
+ };
33
+ }
34
+ function decrypt(row) {
35
+ const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(row.refresh_token_iv, 'base64'));
36
+ decipher.setAuthTag(Buffer.from(row.refresh_token_auth_tag, 'base64'));
37
+ const plaintext = Buffer.concat([
38
+ decipher.update(Buffer.from(row.refresh_token_ciphertext, 'base64')),
39
+ decipher.final(),
40
+ ]);
41
+ return plaintext.toString('utf8');
42
+ }
43
+ async function findRow() {
44
+ const found = await db.query(sql `select * from ${table} where id = ${SEARCH_CONSOLE_CONNECTION_ID}`);
45
+ return found.rows[0] ?? null;
46
+ }
47
+ return {
48
+ async read() {
49
+ const row = await findRow();
50
+ return row === null ? null : toSummary(row);
51
+ },
52
+ async connect(input) {
53
+ const { iv, authTag, ciphertext } = encrypt(input.refreshToken);
54
+ const existing = await findRow();
55
+ const nowIso = now().toISOString();
56
+ const connectedAt = existing?.connected_at ?? nowIso;
57
+ if (existing === null) {
58
+ await db.query(sql `
59
+ insert into ${table} (
60
+ id, site_url, refresh_token_iv, refresh_token_auth_tag,
61
+ refresh_token_ciphertext, connected_at, updated_at
62
+ ) values (
63
+ ${SEARCH_CONSOLE_CONNECTION_ID}, ${input.siteUrl}, ${iv}, ${authTag},
64
+ ${ciphertext}, ${connectedAt}, ${nowIso}
65
+ )`);
66
+ }
67
+ else {
68
+ await db.query(sql `
69
+ update ${table} set
70
+ site_url = ${input.siteUrl},
71
+ refresh_token_iv = ${iv},
72
+ refresh_token_auth_tag = ${authTag},
73
+ refresh_token_ciphertext = ${ciphertext},
74
+ updated_at = ${nowIso}
75
+ where id = ${SEARCH_CONSOLE_CONNECTION_ID}`);
76
+ }
77
+ return { siteUrl: input.siteUrl, connectedAt, updatedAt: nowIso };
78
+ },
79
+ async disconnect() {
80
+ await db.query(sql `delete from ${table} where id = ${SEARCH_CONSOLE_CONNECTION_ID}`);
81
+ },
82
+ async decryptRefreshToken() {
83
+ const row = await findRow();
84
+ if (row === null)
85
+ throw notConnected();
86
+ return decrypt(row);
87
+ },
88
+ };
89
+ }
90
+ //# sourceMappingURL=search-console-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-console-store.js","sourceRoot":"","sources":["../../src/store/search-console-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { type DatabaseHandle } from '@cogenta/core';
2
+ /**
3
+ * Google Search Console connector (fiche 70 task 4, ADR-0032) — same
4
+ * one-fixed-table treatment as `menu-tables.ts`/`pattern-tables.ts`: this is
5
+ * not schema-declared content, it is one site-wide connection record. At
6
+ * most one row ever exists (`id` is always `'default'`), because a Cogenta
7
+ * install is one site with one Search Console property, not a multi-tenant
8
+ * registry.
9
+ */
10
+ export declare const SEARCH_CONSOLE_CONNECTION_TABLE = "cogenta_seo_search_console_connection";
11
+ /** The table's one and only possible row id. */
12
+ export declare const SEARCH_CONSOLE_CONNECTION_ID = "default";
13
+ export declare function ensureSearchConsoleConnectionTable(db: DatabaseHandle): Promise<void>;
14
+ //# sourceMappingURL=search-console-tables.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-console-tables.d.ts","sourceRoot":"","sources":["../../src/store/search-console-tables.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAA8B,MAAM,eAAe,CAAA;AAG/E;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,0CAA0C,CAAA;AAEtF,gDAAgD;AAChD,eAAO,MAAM,4BAA4B,YAAY,CAAA;AAKrD,wBAAsB,kCAAkC,CAAC,EAAE,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAa1F"}
@@ -0,0 +1,29 @@
1
+ import { identifier, sql, unsafeRaw } from '@cogenta/core';
2
+ import { textColumn, timestampColumn } from './columns.js';
3
+ /**
4
+ * Google Search Console connector (fiche 70 task 4, ADR-0032) — same
5
+ * one-fixed-table treatment as `menu-tables.ts`/`pattern-tables.ts`: this is
6
+ * not schema-declared content, it is one site-wide connection record. At
7
+ * most one row ever exists (`id` is always `'default'`), because a Cogenta
8
+ * install is one site with one Search Console property, not a multi-tenant
9
+ * registry.
10
+ */
11
+ export const SEARCH_CONSOLE_CONNECTION_TABLE = 'cogenta_seo_search_console_connection';
12
+ /** The table's one and only possible row id. */
13
+ export const SEARCH_CONSOLE_CONNECTION_ID = 'default';
14
+ /** How long a GSC `siteUrl` (`https://example.com/` or `sc-domain:example.com`) may be. */
15
+ const SITE_URL_LENGTH = 255;
16
+ export async function ensureSearchConsoleConnectionTable(db) {
17
+ const dialect = db.dialect;
18
+ const table = identifier(SEARCH_CONSOLE_CONNECTION_TABLE, dialect);
19
+ await db.query(sql `create table if not exists ${table} (
20
+ ${identifier('id', dialect)} ${textColumn(dialect, 16)} not null primary key,
21
+ ${identifier('site_url', dialect)} ${textColumn(dialect, SITE_URL_LENGTH)} not null,
22
+ ${identifier('refresh_token_iv', dialect)} ${textColumn(dialect, 64)} not null,
23
+ ${identifier('refresh_token_auth_tag', dialect)} ${textColumn(dialect, 64)} not null,
24
+ ${identifier('refresh_token_ciphertext', dialect)} ${unsafeRaw('text')} not null,
25
+ ${identifier('connected_at', dialect)} ${timestampColumn(dialect)} not null,
26
+ ${identifier('updated_at', dialect)} ${timestampColumn(dialect)} not null
27
+ )`);
28
+ }
29
+ //# sourceMappingURL=search-console-tables.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-console-tables.js","sourceRoot":"","sources":["../../src/store/search-console-tables.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,UAAU,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC/E,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAE1D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,uCAAuC,CAAA;AAEtF,gDAAgD;AAChD,MAAM,CAAC,MAAM,4BAA4B,GAAG,SAAS,CAAA;AAErD,2FAA2F;AAC3F,MAAM,eAAe,GAAG,GAAG,CAAA;AAE3B,MAAM,CAAC,KAAK,UAAU,kCAAkC,CAAC,EAAkB;IACzE,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,CAAA;IAC1B,MAAM,KAAK,GAAG,UAAU,CAAC,+BAA+B,EAAE,OAAO,CAAC,CAAA;IAElE,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,CAAA,8BAA8B,KAAK;MACjD,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC;MACpD,UAAU,CAAC,UAAU,EAAE,OAAO,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,eAAe,CAAC;MACvE,UAAU,CAAC,kBAAkB,EAAE,OAAO,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC;MAClE,UAAU,CAAC,wBAAwB,EAAE,OAAO,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC;MACxE,UAAU,CAAC,0BAA0B,EAAE,OAAO,CAAC,IAAI,SAAS,CAAC,MAAM,CAAC;MACpE,UAAU,CAAC,cAAc,EAAE,OAAO,CAAC,IAAI,eAAe,CAAC,OAAO,CAAC;MAC/D,UAAU,CAAC,YAAY,EAAE,OAAO,CAAC,IAAI,eAAe,CAAC,OAAO,CAAC;IAC/D,CAAC,CAAA;AACL,CAAC"}
@@ -30,5 +30,41 @@ export interface SearchIndexingOptions {
30
30
  */
31
31
  readonly onError?: (error: unknown) => void;
32
32
  }
33
+ /**
34
+ * Re-derives the indexed document for one entry.
35
+ *
36
+ * The published face is asked for **first**, and it is the one indexed
37
+ * whenever it exists. That ordering is the whole safety property: with drafts
38
+ * enabled, `update()` returns the working face of an entry whose `status` is
39
+ * still `published`, and indexing *that* would file unreviewed text under a
40
+ * status a public search is allowed to reach. Reading back through
41
+ * `state: 'published'` makes it structurally impossible — the store only
42
+ * returns a row there when its status really is `published`.
43
+ *
44
+ * When there is no published face, the working one is indexed under its real
45
+ * status (`draft`, `scheduled`, `archived`), which is what lets the admin
46
+ * find a draft while `SearchQuery`'s `published` default keeps it away from
47
+ * anyone who did not explicitly ask for it and hold the permission to.
48
+ */
49
+ declare function reindex<TValues extends ContentValues>(store: ContentStore<TValues>, options: SearchIndexingOptions, id: string): Promise<void>;
50
+ /**
51
+ * Re-derives the indexed document for one entry — exported so a full rebuild
52
+ * (`reindexAll`, and `cogenta`'s "Reindex search" tool, fiche 24 task 3) uses
53
+ * the exact same per-entry logic as the write path, rather than a second
54
+ * copy that could drift from what `withSearchIndexing` actually does on save.
55
+ */
56
+ export declare const reindexEntry: typeof reindex;
57
+ /**
58
+ * Walks every entry of a collection — trashed included, since a trashed entry
59
+ * can still be `published` (ADR-0022: trash is orthogonal to status) and a
60
+ * stale search hit for it is exactly the kind of thing a full rebuild exists
61
+ * to fix — and re-derives its indexed document.
62
+ *
63
+ * This is the "Reindexer la recherche" tool (fiche 24 task 3): after an
64
+ * import, or after `SearchDriver.clear()`, nothing else ever repopulates the
65
+ * index for content that already existed before the write path saw it.
66
+ */
67
+ export declare function reindexAll<TValues extends ContentValues = ContentValues>(store: ContentStore<TValues>, options: SearchIndexingOptions, onProgress?: (count: number) => void): Promise<number>;
33
68
  export declare function withSearchIndexing<TValues extends ContentValues = ContentValues>(store: ContentStore<TValues>, options: SearchIndexingOptions): ContentStore<TValues>;
69
+ export {};
34
70
  //# sourceMappingURL=search-indexing.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"search-indexing.d.ts","sourceRoot":"","sources":["../../src/store/search-indexing.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,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;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,UAAU,EAAE,oBAAoB,CAAA;IACzC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAA;IAC5B;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CAC5C;AA0CD,wBAAgB,kBAAkB,CAAC,OAAO,SAAS,aAAa,GAAG,aAAa,EAC9E,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,EAC5B,OAAO,EAAE,qBAAqB,GAC7B,YAAY,CAAC,OAAO,CAAC,CA2BvB"}
1
+ {"version":3,"file":"search-indexing.d.ts","sourceRoot":"","sources":["../../src/store/search-indexing.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,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;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,UAAU,EAAE,oBAAoB,CAAA;IACzC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAA;IAC5B;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CAC5C;AAED;;;;;;;;;;;;;;;GAeG;AACH,iBAAe,OAAO,CAAC,OAAO,SAAS,aAAa,EAClD,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,EAC5B,OAAO,EAAE,qBAAqB,EAC9B,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED;;;;;GAKG;AACH,eAAO,MAAM,YAAY,gBAAU,CAAA;AAEnC;;;;;;;;;GASG;AACH,wBAAsB,UAAU,CAAC,OAAO,SAAS,aAAa,GAAG,aAAa,EAC5E,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,EAC5B,OAAO,EAAE,qBAAqB,EAC9B,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GACnC,OAAO,CAAC,MAAM,CAAC,CAcjB;AAED,wBAAgB,kBAAkB,CAAC,OAAO,SAAS,aAAa,GAAG,aAAa,EAC9E,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,EAC5B,OAAO,EAAE,qBAAqB,GAC7B,YAAY,CAAC,OAAO,CAAC,CA2BvB"}
@@ -34,6 +34,39 @@ async function reindex(store, options, id) {
34
34
  options.onError?.(error);
35
35
  }
36
36
  }
37
+ /**
38
+ * Re-derives the indexed document for one entry — exported so a full rebuild
39
+ * (`reindexAll`, and `cogenta`'s "Reindex search" tool, fiche 24 task 3) uses
40
+ * the exact same per-entry logic as the write path, rather than a second
41
+ * copy that could drift from what `withSearchIndexing` actually does on save.
42
+ */
43
+ export const reindexEntry = reindex;
44
+ /**
45
+ * Walks every entry of a collection — trashed included, since a trashed entry
46
+ * can still be `published` (ADR-0022: trash is orthogonal to status) and a
47
+ * stale search hit for it is exactly the kind of thing a full rebuild exists
48
+ * to fix — and re-derives its indexed document.
49
+ *
50
+ * This is the "Reindexer la recherche" tool (fiche 24 task 3): after an
51
+ * import, or after `SearchDriver.clear()`, nothing else ever repopulates the
52
+ * index for content that already existed before the write path saw it.
53
+ */
54
+ export async function reindexAll(store, options, onProgress) {
55
+ let cursor;
56
+ let count = 0;
57
+ for (;;) {
58
+ const page = await store.list({ trashed: 'include', limit: 100, ...(cursor ? { cursor } : {}) });
59
+ for (const entry of page.items) {
60
+ await reindex(store, options, entry.id);
61
+ count += 1;
62
+ onProgress?.(count);
63
+ }
64
+ if (!page.hasMore || page.nextCursor === null)
65
+ break;
66
+ cursor = page.nextCursor;
67
+ }
68
+ return count;
69
+ }
37
70
  export function withSearchIndexing(store, options) {
38
71
  async function after(entry) {
39
72
  await reindex(store, options, entry.id);