@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,318 @@
1
+ import { CogentaError, identifier, limit, newId, sql, } from '@cogenta/core';
2
+ /**
3
+ * The scheduled-task registry (fiche 28 task 1).
4
+ *
5
+ * Before this, `cogenta serve` registered four unrelated pieces of recurring
6
+ * work — scheduled publication, the 404 log purge, the audit-integrity
7
+ * check, the trash sweep — each on its own bare `setInterval`, with no shared
8
+ * bookkeeping. Nothing recorded whether a run had happened, how long it took,
9
+ * or whether it had failed; the only way to know a scheduled publication had
10
+ * stopped working was a reader noticing a page never went live.
11
+ *
12
+ * A registered task declares a name, a description, an interval and a
13
+ * function. This module owns everything else: running due tasks, recording
14
+ * every run, and answering "what is scheduled, and did it work" for the
15
+ * admin screen fiche 28 task 2 builds over it.
16
+ *
17
+ * **Persistence is the point of task 3.** A task's "last run" is a row in
18
+ * this table, not a variable in the process — a `setInterval` handle dies
19
+ * with the process, and a site restarted after being down all night must be
20
+ * able to say, immediately, "the trash sweep has not run in fourteen hours"
21
+ * rather than resetting the clock the moment it comes back up. `tick()`
22
+ * itself is equally indifferent to *why* it is being called: `cogenta
23
+ * serve`'s own loop and the one-shot `cogenta cron` command both just call
24
+ * it, which is what makes the second a real alternative to the first rather
25
+ * than a separate reimplementation (fiche 28 task 5).
26
+ */
27
+ export const SCHEDULED_TASK_RUNS_TABLE = 'cogenta_scheduled_task_runs';
28
+ /**
29
+ * One row per task: the compare-and-set lock `tick()` claims before running a
30
+ * task (L22 task 6). Kept separate from the run-history table above —
31
+ * history answers "what happened", this answers "is anyone already doing
32
+ * this right now", and conflating the two would make every read of the
33
+ * admin history screen reason about lock state.
34
+ *
35
+ * Before this table existed, `tick()` read the last run from the history
36
+ * table, decided a task was due, and only recorded the new run *after* it
37
+ * finished — nothing made that read-then-act atomic. With more than one
38
+ * `cogenta serve` replica (or a replica racing a `cogenta cron` invocation)
39
+ * against the same database, two processes could both read "due" and both
40
+ * start executing the same task, including the destructive trash-purge
41
+ * sweep. The fix is the same shape as `takeOne`'s guarded stock `UPDATE` in
42
+ * `@cogenta/commerce` and `claimOnce`'s guarded job `UPDATE` in
43
+ * `@cogenta/core`'s database queue: read `last_claim`, then issue a single
44
+ * `UPDATE ... WHERE last_claim = <the value just read>`. Only one of two
45
+ * racing `UPDATE`s can match that `WHERE` — the loser sees
46
+ * `rowsAffected === 0` and skips the task entirely. No dialect-specific
47
+ * locking primitive is needed: a single `UPDATE` with an equality `WHERE` is
48
+ * atomic per row on SQLite, Postgres and MySQL/MariaDB alike, so the exact
49
+ * same query runs unmodified on all three.
50
+ */
51
+ export const SCHEDULED_TASK_CLAIMS_TABLE = 'cogenta_scheduled_task_claims';
52
+ /** Runs kept per task. Enough to see a pattern across a week of daily tasks, never unbounded. */
53
+ const DEFAULT_KEEP_RUNS = 50;
54
+ function toRun(row) {
55
+ return {
56
+ id: row.id,
57
+ taskName: row.task_name,
58
+ startedAt: Number(row.started_at),
59
+ finishedAt: Number(row.finished_at),
60
+ durationMs: Number(row.duration_ms),
61
+ outcome: row.outcome,
62
+ summary: row.summary,
63
+ error: row.error,
64
+ triggeredBy: row.triggered_by,
65
+ actor: row.actor,
66
+ };
67
+ }
68
+ function unknownTask(name) {
69
+ return new CogentaError({
70
+ code: 'SCHEDULER_TASK_UNKNOWN',
71
+ message: `No scheduled task named "${name}" is registered.`,
72
+ hint: 'Check the task name against GET /api/scheduled-tasks — a task only appears there once something has called register() for it.',
73
+ details: { name },
74
+ });
75
+ }
76
+ export function createScheduledTaskRegistry(options) {
77
+ const { db } = options;
78
+ const now = options.now ?? Date.now;
79
+ const keepRuns = options.keepRuns ?? DEFAULT_KEEP_RUNS;
80
+ const logger = options.logger?.child({ component: 'scheduler' });
81
+ const table = identifier(options.table ?? SCHEDULED_TASK_RUNS_TABLE, db.dialect);
82
+ const claimsTable = identifier(options.claimsTable ?? SCHEDULED_TASK_CLAIMS_TABLE, db.dialect);
83
+ const definitions = new Map();
84
+ let ready = false;
85
+ async function ensureTable() {
86
+ if (ready)
87
+ return;
88
+ await db.query(sql `
89
+ create table if not exists ${table} (
90
+ id varchar(64) not null primary key,
91
+ task_name varchar(255) not null,
92
+ started_at bigint not null,
93
+ finished_at bigint not null,
94
+ duration_ms bigint not null,
95
+ outcome varchar(16) not null,
96
+ summary text,
97
+ error text,
98
+ triggered_by varchar(16) not null,
99
+ actor varchar(255)
100
+ )`);
101
+ await db
102
+ .query(sql `create index ${identifier('cogenta_scheduled_task_runs_task', db.dialect)}
103
+ on ${table} (task_name, started_at)`)
104
+ .catch(() => undefined); // already there
105
+ // The compare-and-set lock table (L22 task 6). `last_claim` starts out
106
+ // `null` — "never claimed" — for a brand-new row, matching the meaning
107
+ // `lastRunFor` gives a task that has never run.
108
+ await db.query(sql `
109
+ create table if not exists ${claimsTable} (
110
+ task_name varchar(255) not null primary key,
111
+ last_claim bigint
112
+ )`);
113
+ ready = true;
114
+ }
115
+ async function lastRunFor(name) {
116
+ await ensureTable();
117
+ const found = await db.query(sql `
118
+ select id, task_name, started_at, finished_at, duration_ms, outcome, summary, error, triggered_by, actor
119
+ from ${table}
120
+ where task_name = ${name}
121
+ order by started_at desc
122
+ limit ${limit(1)}`);
123
+ const row = found.rows[0];
124
+ return row === undefined ? null : toRun(row);
125
+ }
126
+ async function recentRunsFor(name) {
127
+ await ensureTable();
128
+ const found = await db.query(sql `
129
+ select id, task_name, started_at, finished_at, duration_ms, outcome, summary, error, triggered_by, actor
130
+ from ${table}
131
+ where task_name = ${name}
132
+ order by started_at desc
133
+ limit ${limit(keepRuns)}`);
134
+ return found.rows.map(toRun);
135
+ }
136
+ async function pruneOldRuns(name) {
137
+ // Keep the newest `keepRuns`, drop the rest — bounded history without a
138
+ // separate cleanup task of its own.
139
+ const kept = await db.query(sql `
140
+ select id from ${table}
141
+ where task_name = ${name}
142
+ order by started_at desc
143
+ limit ${limit(keepRuns)}`);
144
+ if (kept.rows.length < keepRuns)
145
+ return;
146
+ const keptIds = kept.rows.map((row) => row.id);
147
+ const inList = keptIds.map((id) => sql `${id}`).reduce((left, right) => sql `${left}, ${right}`);
148
+ await db.query(sql `delete from ${table} where task_name = ${name} and id not in (${inList})`);
149
+ }
150
+ /**
151
+ * Makes sure a claim row exists for a task, seeded from the run history so
152
+ * an upgrade from a database that already has runs (but no claims table
153
+ * yet) does not make every existing task look newly-never-run and fire
154
+ * immediately. A race between two processes both seeding the same row for
155
+ * the first time is harmless: the loser's `insert` hits the primary key
156
+ * and is swallowed, exactly like the "already there" index-creation guard
157
+ * above — the row itself, not who created it, is what matters.
158
+ */
159
+ async function ensureClaimRow(name) {
160
+ await ensureTable();
161
+ const existing = await db.query(sql `select task_name from ${claimsTable} where task_name = ${name}`);
162
+ if (existing.rows.length > 0)
163
+ return;
164
+ const priorRun = await lastRunFor(name);
165
+ const seed = priorRun === null ? null : priorRun.startedAt;
166
+ await db
167
+ .query(sql `insert into ${claimsTable} (task_name, last_claim) values (${name}, ${seed})`)
168
+ .catch(() => undefined); // another process seeded it first — fine, the row exists either way
169
+ }
170
+ /**
171
+ * The compare-and-set claim `tick()` takes before running a due task.
172
+ *
173
+ * Reads the task's current `last_claim`, decides whether that makes it
174
+ * due, and — only if so — tries to move `last_claim` forward with a single
175
+ * guarded `UPDATE` whose `WHERE` re-checks the exact value just read. If
176
+ * another process already claimed this task between the read and the
177
+ * write, `last_claim` no longer matches, `rowsAffected` comes back `0`,
178
+ * and this returns `false` — the caller must not run the task. Only one of
179
+ * two racing callers can ever see `true` for the same task and instant,
180
+ * because a single-row `UPDATE` with an equality `WHERE` is atomic on
181
+ * SQLite, Postgres and MySQL/MariaDB alike; nothing dialect-specific is
182
+ * needed here.
183
+ */
184
+ async function tryClaim(definition, at) {
185
+ await ensureClaimRow(definition.name);
186
+ const found = await db.query(sql `select task_name, last_claim from ${claimsTable} where task_name = ${definition.name}`);
187
+ // Normalised the same way `toRun` normalises the run-history table's
188
+ // bigint columns: drivers disagree on whether a bigint column comes back
189
+ // as a JS number, a string or a `bigint`, and comparing/subtracting the
190
+ // wrong one silently produces `NaN` or a string mismatch rather than an
191
+ // error.
192
+ const raw = found.rows[0]?.last_claim;
193
+ const lastClaim = raw === null || raw === undefined ? null : Number(raw);
194
+ const due = lastClaim === null || at - lastClaim >= definition.intervalMs;
195
+ if (!due)
196
+ return false;
197
+ const result = lastClaim === null
198
+ ? await db.query(sql `
199
+ update ${claimsTable} set last_claim = ${at}
200
+ where task_name = ${definition.name} and last_claim is null`)
201
+ : await db.query(sql `
202
+ update ${claimsTable} set last_claim = ${at}
203
+ where task_name = ${definition.name} and last_claim = ${lastClaim}`);
204
+ return result.rowsAffected > 0;
205
+ }
206
+ /**
207
+ * Keeps the claim row in step with the run it just recorded, whether the
208
+ * run was scheduled or manual (`runNow`) — a manual run must push the next
209
+ * scheduled run out exactly as it always has, since `tick()` used to base
210
+ * "is it due" on the run-history table, which every trigger writes to.
211
+ */
212
+ async function upsertClaim(name, startedAt) {
213
+ await ensureClaimRow(name);
214
+ await db.query(sql `update ${claimsTable} set last_claim = ${startedAt} where task_name = ${name}`);
215
+ }
216
+ async function stateFor(definition) {
217
+ const lastRun = await lastRunFor(definition.name);
218
+ const at = now();
219
+ const nextRunAt = lastRun === null ? at : lastRun.startedAt + definition.intervalMs;
220
+ const overdue = lastRun === null ? true : at - lastRun.startedAt > definition.intervalMs * 2;
221
+ return {
222
+ name: definition.name,
223
+ description: definition.description,
224
+ intervalMs: definition.intervalMs,
225
+ destructive: definition.destructive ?? false,
226
+ lastRun,
227
+ nextRunAt,
228
+ overdue,
229
+ recentRuns: await recentRunsFor(definition.name),
230
+ };
231
+ }
232
+ async function execute(definition, triggeredBy, actor) {
233
+ await ensureTable();
234
+ const startedAt = now();
235
+ let outcome = 'success';
236
+ let summary = null;
237
+ let error = null;
238
+ try {
239
+ const result = await definition.run();
240
+ summary = result?.summary ?? null;
241
+ }
242
+ catch (caught) {
243
+ outcome = 'error';
244
+ error = caught instanceof Error ? caught.message : String(caught);
245
+ logger?.error('scheduled task failed', { task: definition.name, error });
246
+ }
247
+ const finishedAt = now();
248
+ const run = {
249
+ id: newId(now),
250
+ taskName: definition.name,
251
+ startedAt,
252
+ finishedAt,
253
+ durationMs: Math.max(0, finishedAt - startedAt),
254
+ outcome,
255
+ summary,
256
+ error,
257
+ triggeredBy,
258
+ actor,
259
+ };
260
+ await db.query(sql `
261
+ insert into ${table}
262
+ (id, task_name, started_at, finished_at, duration_ms, outcome, summary, error, triggered_by, actor)
263
+ values
264
+ (${run.id}, ${run.taskName}, ${run.startedAt}, ${run.finishedAt}, ${run.durationMs},
265
+ ${run.outcome}, ${run.summary}, ${run.error}, ${run.triggeredBy}, ${run.actor})`);
266
+ await pruneOldRuns(definition.name);
267
+ await upsertClaim(definition.name, run.startedAt);
268
+ return run;
269
+ }
270
+ return {
271
+ register: (definition) => {
272
+ if (definitions.has(definition.name)) {
273
+ throw new CogentaError({
274
+ code: 'SCHEDULER_TASK_DUPLICATE',
275
+ message: `A scheduled task named "${definition.name}" is already registered.`,
276
+ hint: 'Task names must be unique. Pick a different name, or remove the duplicate registration.',
277
+ details: { name: definition.name },
278
+ });
279
+ }
280
+ definitions.set(definition.name, definition);
281
+ },
282
+ list: async () => {
283
+ const states = [];
284
+ for (const definition of definitions.values()) {
285
+ states.push(await stateFor(definition));
286
+ }
287
+ return states;
288
+ },
289
+ get: async (name) => {
290
+ const definition = definitions.get(name);
291
+ return definition === undefined ? null : stateFor(definition);
292
+ },
293
+ runNow: async (name, runOptions = {}) => {
294
+ const definition = definitions.get(name);
295
+ if (definition === undefined)
296
+ throw unknownTask(name);
297
+ return execute(definition, 'manual', runOptions.actor ?? null);
298
+ },
299
+ tick: async (at = now()) => {
300
+ const ran = [];
301
+ // Sequenced, not concurrent within this process: this may share one
302
+ // SQLite connection with whatever else calls `db.transaction`, which
303
+ // cannot have two writers open at the same instant. Across processes,
304
+ // `tryClaim` is what actually prevents two replicas from both running
305
+ // the same task — this loop alone would not (L22 task 6).
306
+ for (const definition of definitions.values()) {
307
+ const claimed = await tryClaim(definition, at);
308
+ if (!claimed)
309
+ continue;
310
+ await execute(definition, 'schedule', null);
311
+ ran.push(definition.name);
312
+ }
313
+ return { ran };
314
+ },
315
+ ensureTable,
316
+ };
317
+ }
318
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/scheduling/registry.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,7 @@ import type { SearchDocument } from './types.js';
12
12
  export declare function extractRichText(value: unknown): string;
13
13
  /** The prose of one block. Its `data` is contract B, so its shape is open. */
14
14
  export declare function extractBlockText(block: ContentBlock): string;
15
+ export declare function titleOf(collection: CollectionDefinition, entry: ContentEntry): string;
15
16
  /**
16
17
  * The indexable document for an entry.
17
18
  *
@@ -22,4 +23,27 @@ export declare function extractBlockText(block: ContentBlock): string;
22
23
  * twice in the text every engine ranks.
23
24
  */
24
25
  export declare function searchDocumentFor(collection: CollectionDefinition, entry: ContentEntry): SearchDocument;
26
+ /** One run of characters inside an excerpt that matched a query term. */
27
+ export interface ExcerptMatch {
28
+ readonly start: number;
29
+ readonly end: number;
30
+ }
31
+ export interface Excerpt {
32
+ readonly text: string;
33
+ readonly matches: readonly ExcerptMatch[];
34
+ }
35
+ /**
36
+ * A window of prose around the first query term found in `text`, with the
37
+ * offsets of every match **inside that window** — never inside the full text,
38
+ * which the caller has already thrown away by the time it renders anything.
39
+ *
40
+ * `text` is expected to be the *display* text — `SearchDocument.body`, which
41
+ * `searchDocumentFor` builds with `condense` but never `foldText` (only the
42
+ * three drivers fold it, at the point they hand it to their own engine, for
43
+ * matching). That is what lets an excerpt keep real casing and accents while
44
+ * still being found by a folded, prefix-matching query: each candidate word is
45
+ * folded here, once, only to decide whether it counts as a match — never to
46
+ * change what is shown (fiche 36, task 3).
47
+ */
48
+ export declare function buildExcerpt(text: string, tokens: readonly string[]): Excerpt;
25
49
  //# sourceMappingURL=extract.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/search/extract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAC/E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAEvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AA0EhD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CA6BtD;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAI5D;AAoCD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,oBAAoB,EAChC,KAAK,EAAE,YAAY,GAClB,cAAc,CA8BhB"}
1
+ {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/search/extract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAC/E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAEvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AA0EhD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CA6BtD;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAI5D;AAyBD,wBAAgB,OAAO,CAAC,UAAU,EAAE,oBAAoB,EAAE,KAAK,EAAE,YAAY,GAAG,MAAM,CAerF;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,oBAAoB,EAChC,KAAK,EAAE,YAAY,GAClB,cAAc,CA8BhB;AAED,yEAAyE;AACzE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAA;CAC1C;AAYD;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAmC7E"}
@@ -1,4 +1,4 @@
1
- import { condense } from './text.js';
1
+ import { condense, foldText } from './text.js';
2
2
  /**
3
3
  * Turning an entry into the text that gets indexed.
4
4
  *
@@ -126,14 +126,20 @@ function extractZones(zones) {
126
126
  /**
127
127
  * The field whose value labels a result.
128
128
  *
129
- * `title` by name when the collection has one contract A's own example uses
130
- * it — and the first declared `text` field otherwise, which is what an editor
131
- * sees at the top of the form.
129
+ * Fiche 01 ("Liste de contenu"), task 1: the same priority order
130
+ * `packages/admin/src/lib/entry-title.ts` uses for every other screen that
131
+ * names an entry, so a title reads the same in the collection list, the
132
+ * trash and here. A declared `text` field named `title`, `name` or `label`
133
+ * — in that priority order — and the first declared `text` field
134
+ * otherwise, which is what an editor sees at the top of the form.
132
135
  */
133
- function titleOf(collection, entry) {
134
- const named = collection.fields['title'];
135
- if (named?.kind === 'text') {
136
- const value = entry.values['title'];
136
+ const TITLE_FIELD_PRIORITY = ['title', 'name', 'label'];
137
+ export function titleOf(collection, entry) {
138
+ for (const name of TITLE_FIELD_PRIORITY) {
139
+ const field = collection.fields[name];
140
+ if (field?.kind !== 'text')
141
+ continue;
142
+ const value = entry.values[name];
137
143
  if (typeof value === 'string' && value.length > 0)
138
144
  return condense(value);
139
145
  }
@@ -185,4 +191,53 @@ export function searchDocumentFor(collection, entry) {
185
191
  body: condense(parts.join(' ')),
186
192
  };
187
193
  }
194
+ /** Letters and digits, the same boundary `tokenize` (`text.ts`) uses. */
195
+ const WORD_PATTERN = /[\p{L}\p{N}]+/gu;
196
+ /** Characters kept on each side of the first match — enough prose to show why a
197
+ * result matched without turning the result list into a wall of text. */
198
+ const EXCERPT_RADIUS = 80;
199
+ /** No match found: the caller still gets a real opening, not a raw cutoff of `body`. */
200
+ const EXCERPT_FALLBACK_LENGTH = 200;
201
+ /**
202
+ * A window of prose around the first query term found in `text`, with the
203
+ * offsets of every match **inside that window** — never inside the full text,
204
+ * which the caller has already thrown away by the time it renders anything.
205
+ *
206
+ * `text` is expected to be the *display* text — `SearchDocument.body`, which
207
+ * `searchDocumentFor` builds with `condense` but never `foldText` (only the
208
+ * three drivers fold it, at the point they hand it to their own engine, for
209
+ * matching). That is what lets an excerpt keep real casing and accents while
210
+ * still being found by a folded, prefix-matching query: each candidate word is
211
+ * folded here, once, only to decide whether it counts as a match — never to
212
+ * change what is shown (fiche 36, task 3).
213
+ */
214
+ export function buildExcerpt(text, tokens) {
215
+ if (tokens.length === 0 || text.length === 0) {
216
+ return { text: text.slice(0, EXCERPT_FALLBACK_LENGTH), matches: [] };
217
+ }
218
+ const words = [];
219
+ for (const match of text.matchAll(WORD_PATTERN)) {
220
+ words.push({ start: match.index, end: match.index + match[0].length });
221
+ }
222
+ // The same rule the FTS5/tsquery/boolean-mode drivers all express: a term
223
+ // matches a word that starts with it, not only a word equal to it.
224
+ function isHit(word) {
225
+ const folded = foldText(text.slice(word.start, word.end));
226
+ return tokens.some((token) => folded.startsWith(token));
227
+ }
228
+ const hits = words.filter(isHit);
229
+ if (hits.length === 0) {
230
+ return { text: text.slice(0, EXCERPT_FALLBACK_LENGTH), matches: [] };
231
+ }
232
+ const anchor = hits[0];
233
+ const windowStart = Math.max(0, anchor.start - EXCERPT_RADIUS);
234
+ const windowEnd = Math.min(text.length, anchor.end + EXCERPT_RADIUS);
235
+ const prefix = windowStart > 0 ? '… ' : '';
236
+ const suffix = windowEnd < text.length ? ' …' : '';
237
+ const shift = prefix.length - windowStart;
238
+ const matches = hits
239
+ .filter((word) => word.start >= windowStart && word.end <= windowEnd)
240
+ .map((word) => ({ start: word.start + shift, end: word.end + shift }));
241
+ return { text: `${prefix}${text.slice(windowStart, windowEnd)}${suffix}`, matches };
242
+ }
188
243
  //# sourceMappingURL=extract.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"extract.js","sourceRoot":"","sources":["../../src/search/extract.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"extract.js","sourceRoot":"","sources":["../../src/search/extract.ts"],"names":[],"mappings":"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"}
@@ -8,7 +8,8 @@ import type { SearchDriver } from './types.js';
8
8
  * not ranked at all. What every caller does know is that the locale and the
9
9
  * state it asks for are the only ones it can be given.
10
10
  */
11
- export { extractBlockText, extractRichText, searchDocumentFor } from './extract.js';
11
+ export type { Excerpt, ExcerptMatch } from './extract.js';
12
+ export { buildExcerpt, extractBlockText, extractRichText, searchDocumentFor, titleOf, } from './extract.js';
12
13
  export type { MysqlSearchOptions } from './mysql.js';
13
14
  export { createMysqlSearch } from './mysql.js';
14
15
  export type { PostgresSearchOptions } from './postgres.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/search/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,eAAe,CAAA;AAIjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C;;;;;;;GAOG;AAEH,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AACnF,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC9C,YAAY,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AACtE,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC3E,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,gBAAgB,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AACvF,YAAY,EACV,cAAc,EACd,YAAY,EACZ,SAAS,EACT,WAAW,EACX,eAAe,EACf,aAAa,GACd,MAAM,YAAY,CAAA;AAEnB,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAA;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CACxB;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAe1F"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/search/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,eAAe,CAAA;AAIjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C;;;;;;;GAOG;AAEH,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,OAAO,GACR,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC9C,YAAY,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AACtE,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC3E,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,gBAAgB,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AACvF,YAAY,EACV,cAAc,EACd,YAAY,EACZ,SAAS,EACT,WAAW,EACX,eAAe,EACf,aAAa,GACd,MAAM,YAAY,CAAA;AAEnB,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAA;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CACxB;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAe1F"}
@@ -2,15 +2,7 @@ import { CogentaError } from '@cogenta/core';
2
2
  import { createMysqlSearch } from './mysql.js';
3
3
  import { createPostgresSearch } from './postgres.js';
4
4
  import { createSqliteSearch } from './sqlite.js';
5
- /**
6
- * Full-text search, one interface over three engines (L1, task 16).
7
- *
8
- * Nothing outside this folder knows whether the site is ranked by `ts_rank`,
9
- * by an InnoDB fulltext score or by BM25 — or, on a SQLite build without FTS5,
10
- * not ranked at all. What every caller does know is that the locale and the
11
- * state it asks for are the only ones it can be given.
12
- */
13
- export { extractBlockText, extractRichText, searchDocumentFor } from './extract.js';
5
+ export { buildExcerpt, extractBlockText, extractRichText, searchDocumentFor, titleOf, } from './extract.js';
14
6
  export { createMysqlSearch } from './mysql.js';
15
7
  export { configurationFor, createPostgresSearch } from './postgres.js';
16
8
  export { createSqliteSearch } from './sqlite.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/search/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,eAAe,CAAA;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAGhD;;;;;;;GAOG;AAEH,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAEnF,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAE9C,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AAEtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC3E,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,gBAAgB,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAgBvF;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAA2B;IACjE,MAAM,EAAE,EAAE,EAAE,GAAG,OAAO,CAAA;IAEtB,IAAI,EAAE,CAAC,OAAO,KAAK,UAAU;QAAE,OAAO,oBAAoB,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;IAClE,IAAI,EAAE,CAAC,OAAO,KAAK,OAAO;QAAE,OAAO,iBAAiB,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;IAC5D,IAAI,EAAE,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,kBAAkB,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;IAC7F,CAAC;IAED,MAAM,IAAI,YAAY,CAAC;QACrB,IAAI,EAAE,wBAAwB;QAC9B,OAAO,EAAE,+CAA+C,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,YAAY;QACtF,IAAI,EAAE,gEAAgE;QACtE,OAAO,EAAE,EAAE,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE;KACjC,CAAC,CAAA;AACJ,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/search/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,eAAe,CAAA;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAahD,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,OAAO,GACR,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAE9C,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AAEtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC3E,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,gBAAgB,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAgBvF;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAA2B;IACjE,MAAM,EAAE,EAAE,EAAE,GAAG,OAAO,CAAA;IAEtB,IAAI,EAAE,CAAC,OAAO,KAAK,UAAU;QAAE,OAAO,oBAAoB,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;IAClE,IAAI,EAAE,CAAC,OAAO,KAAK,OAAO;QAAE,OAAO,iBAAiB,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;IAC5D,IAAI,EAAE,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,kBAAkB,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;IAC7F,CAAC;IAED,MAAM,IAAI,YAAY,CAAC;QACrB,IAAI,EAAE,wBAAwB;QAC9B,OAAO,EAAE,+CAA+C,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,YAAY;QACtF,IAAI,EAAE,gEAAgE;QACtE,OAAO,EAAE,EAAE,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE;KACjC,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { type DatabaseHandle } from '@cogenta/core';
2
+ import { type AdminThemeOverrides, type AdminThemeTemplateId, DEFAULT_ADMIN_THEME_TEMPLATE_ID } from './admin-theme-templates.js';
3
+ export interface AdminThemeRecord {
4
+ readonly templateId: AdminThemeTemplateId;
5
+ readonly overrides: AdminThemeOverrides;
6
+ readonly updatedAt: string;
7
+ readonly updatedBy: string | null;
8
+ }
9
+ export interface AdminThemeStoreOptions {
10
+ readonly db: DatabaseHandle;
11
+ readonly now?: () => Date;
12
+ }
13
+ export interface AdminThemeStore {
14
+ /** `null` when nobody has ever written a choice — the caller applies `DEFAULT_ADMIN_THEME_TEMPLATE_ID` with no overrides. */
15
+ get(): Promise<AdminThemeRecord | null>;
16
+ /**
17
+ * Refuses a `templateId` outside `ADMIN_THEME_TEMPLATES`
18
+ * (`ADMIN_THEME_TEMPLATE_UNKNOWN`) or an override object that fails
19
+ * `adminThemeOverridesSchema` (`ADMIN_THEME_INVALID`) — never a loose,
20
+ * unvalidated blob written straight through.
21
+ */
22
+ set(templateId: string, overrides: unknown, updatedBy: string | null): Promise<AdminThemeRecord>;
23
+ }
24
+ export declare function createAdminThemeStore(options: AdminThemeStoreOptions): AdminThemeStore;
25
+ export { DEFAULT_ADMIN_THEME_TEMPLATE_ID };
26
+ //# sourceMappingURL=admin-theme-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-theme-store.d.ts","sourceRoot":"","sources":["../../src/store/admin-theme-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,cAAc,EAAmB,MAAM,eAAe,CAAA;AAElF,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EAGzB,+BAA+B,EAChC,MAAM,4BAA4B,CAAA;AAEnC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,oBAAoB,CAAA;IACzC,QAAQ,CAAC,SAAS,EAAE,mBAAmB,CAAA;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAA;IAC3B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,6HAA6H;IAC7H,GAAG,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;IACvC;;;;;OAKG;IACH,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAA;CACjG;AA8BD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,sBAAsB,GAAG,eAAe,CAqEtF;AAED,OAAO,EAAE,+BAA+B,EAAE,CAAA"}