@beechcms/core 0.9.5 → 0.9.7

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 (217) hide show
  1. package/LICENSE +21 -0
  2. package/dist/auth/login-request.d.ts +16 -0
  3. package/dist/auth/login-request.d.ts.map +1 -0
  4. package/dist/auth/login-request.js +22 -0
  5. package/dist/automations-grammar-words.d.ts +2 -0
  6. package/dist/automations-grammar-words.d.ts.map +1 -0
  7. package/dist/automations-grammar-words.js +8 -0
  8. package/dist/automations.repository.interface.d.ts +19 -0
  9. package/dist/automations.repository.interface.d.ts.map +1 -0
  10. package/dist/automations.repository.interface.js +3 -0
  11. package/dist/automations.runner.interface.d.ts +10 -0
  12. package/dist/automations.runner.interface.d.ts.map +1 -0
  13. package/dist/automations.runner.interface.js +3 -0
  14. package/dist/automations.runner.stub.d.ts +5 -0
  15. package/dist/automations.runner.stub.d.ts.map +1 -0
  16. package/dist/automations.runner.stub.js +7 -0
  17. package/dist/automations.types.d.ts +75 -0
  18. package/dist/automations.types.d.ts.map +1 -0
  19. package/dist/automations.types.js +3 -0
  20. package/dist/clock.d.ts +25 -0
  21. package/dist/clock.d.ts.map +1 -0
  22. package/dist/clock.js +11 -0
  23. package/dist/content/content.repository.d.ts +22 -22
  24. package/dist/content/content.repository.d.ts.map +1 -1
  25. package/dist/content/retention.d.ts +13 -0
  26. package/dist/content/retention.d.ts.map +1 -0
  27. package/dist/content/retention.js +20 -0
  28. package/dist/content/slug-utils.d.ts +7 -0
  29. package/dist/content/slug-utils.d.ts.map +1 -1
  30. package/dist/content/slug-utils.js +12 -0
  31. package/dist/content-scan.repository.d.ts +9 -0
  32. package/dist/content-scan.repository.d.ts.map +1 -0
  33. package/dist/content-scan.repository.js +3 -0
  34. package/dist/content.repository.d.ts +247 -0
  35. package/dist/content.repository.d.ts.map +1 -0
  36. package/dist/content.repository.js +59 -0
  37. package/dist/dashboard-layout/dashboard-layout.d.ts +2 -2
  38. package/dist/dashboard-layout/dashboard-layout.d.ts.map +1 -1
  39. package/dist/dashboard-layout/dashboard-layout.js +3 -3
  40. package/dist/dashboard-layout/relation-avatar.d.ts +12 -0
  41. package/dist/dashboard-layout/relation-avatar.d.ts.map +1 -0
  42. package/dist/dashboard-layout/relation-avatar.js +18 -0
  43. package/dist/dashboard-layout/seed-layout.d.ts.map +1 -1
  44. package/dist/dashboard-layout/seed-layout.js +17 -1
  45. package/dist/dashboard-layout.d.ts +154 -0
  46. package/dist/dashboard-layout.d.ts.map +1 -0
  47. package/dist/dashboard-layout.js +186 -0
  48. package/dist/dashboard-layout.repository.d.ts +18 -0
  49. package/dist/dashboard-layout.repository.d.ts.map +1 -0
  50. package/dist/dashboard-layout.repository.js +3 -0
  51. package/dist/dashboard-permissions.d.ts +3 -0
  52. package/dist/dashboard-permissions.d.ts.map +1 -0
  53. package/dist/dashboard-permissions.js +11 -0
  54. package/dist/dashboard-scopes.d.ts +12 -0
  55. package/dist/dashboard-scopes.d.ts.map +1 -0
  56. package/dist/dashboard-scopes.js +30 -0
  57. package/dist/define-seed.d.ts +3 -0
  58. package/dist/define-seed.d.ts.map +1 -0
  59. package/dist/define-seed.js +5 -0
  60. package/dist/demo-data.repository.d.ts +5 -0
  61. package/dist/demo-data.repository.d.ts.map +1 -0
  62. package/dist/demo-data.repository.js +3 -0
  63. package/dist/email.provider.d.ts +32 -0
  64. package/dist/email.provider.d.ts.map +1 -0
  65. package/dist/email.provider.js +4 -0
  66. package/dist/engine/ddl/ddl.d.ts +4 -6
  67. package/dist/engine/ddl/ddl.d.ts.map +1 -1
  68. package/dist/engine/ddl/ddl.js +13 -27
  69. package/dist/engine/ddl/seed-ddl.d.ts.map +1 -1
  70. package/dist/engine/ddl/seed-ddl.js +10 -2
  71. package/dist/engine/ddl.d.ts +0 -23
  72. package/dist/engine/ddl.d.ts.map +1 -1
  73. package/dist/engine/ddl.js +3 -59
  74. package/dist/engine/localization.d.ts +127 -0
  75. package/dist/engine/localization.d.ts.map +1 -0
  76. package/dist/engine/localization.js +212 -0
  77. package/dist/engine/privacy.service.d.ts.map +1 -1
  78. package/dist/engine/query/active-clause.d.ts +2 -3
  79. package/dist/engine/query/active-clause.d.ts.map +1 -1
  80. package/dist/engine/query/active-clause.js +3 -4
  81. package/dist/engine/query/query.js +8 -8
  82. package/dist/engine/query/search-support.d.ts +32 -0
  83. package/dist/engine/query/search-support.d.ts.map +1 -0
  84. package/dist/engine/query/search-support.js +34 -0
  85. package/dist/engine/query.d.ts.map +1 -1
  86. package/dist/engine/query.js +0 -12
  87. package/dist/engine/schema-fingerprint.d.ts +2 -0
  88. package/dist/engine/schema-fingerprint.d.ts.map +1 -1
  89. package/dist/engine/schema-fingerprint.js +2 -0
  90. package/dist/engine/seed-ddl.d.ts +1 -4
  91. package/dist/engine/seed-ddl.d.ts.map +1 -1
  92. package/dist/engine/seed-ddl.js +2 -17
  93. package/dist/engine/seed-registry.d.ts.map +1 -1
  94. package/dist/engine/seed-types-generator.d.ts +2 -17
  95. package/dist/engine/seed-types-generator.d.ts.map +1 -1
  96. package/dist/engine/seed-types-generator.js +3 -31
  97. package/dist/engine/seed-validation.d.ts.map +1 -1
  98. package/dist/engine/seed-validation.js +0 -10
  99. package/dist/engine/seeds/seed-definition-diff.d.ts +1 -1
  100. package/dist/engine/seeds/seed-definition-diff.d.ts.map +1 -1
  101. package/dist/engine/seeds/seed-definition-diff.js +2 -2
  102. package/dist/engine/seeds/seed-validation.d.ts.map +1 -1
  103. package/dist/engine/seeds/seed-validation.js +3 -12
  104. package/dist/engine/types.d.ts +7 -7
  105. package/dist/engine/types.d.ts.map +1 -1
  106. package/dist/engine.d.ts +144 -0
  107. package/dist/engine.d.ts.map +1 -0
  108. package/dist/engine.js +741 -0
  109. package/dist/file-types.d.ts +13 -0
  110. package/dist/file-types.d.ts.map +1 -0
  111. package/dist/file-types.js +85 -0
  112. package/dist/hooks.d.ts +29 -0
  113. package/dist/hooks.d.ts.map +1 -0
  114. package/dist/hooks.js +3 -0
  115. package/dist/id-generator.d.ts +26 -0
  116. package/dist/id-generator.d.ts.map +1 -0
  117. package/dist/id-generator.js +12 -0
  118. package/dist/idempotency.repository.d.ts +13 -0
  119. package/dist/idempotency.repository.d.ts.map +1 -0
  120. package/dist/idempotency.repository.js +3 -0
  121. package/dist/index.d.ts +1 -0
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/index.js +1 -0
  124. package/dist/kanban-position.repository.d.ts +19 -0
  125. package/dist/kanban-position.repository.d.ts.map +1 -0
  126. package/dist/kanban-position.repository.js +3 -0
  127. package/dist/kanban.d.ts +75 -0
  128. package/dist/kanban.d.ts.map +1 -0
  129. package/dist/kanban.js +82 -0
  130. package/dist/layout-permissions.d.ts +3 -0
  131. package/dist/layout-permissions.d.ts.map +1 -0
  132. package/dist/layout-permissions.js +11 -0
  133. package/dist/media.repository.d.ts +61 -0
  134. package/dist/media.repository.d.ts.map +1 -0
  135. package/dist/media.repository.js +3 -0
  136. package/dist/notifications/notification.repository.d.ts +6 -3
  137. package/dist/notifications/notification.repository.d.ts.map +1 -1
  138. package/dist/policies.d.ts +10 -0
  139. package/dist/policies.d.ts.map +1 -0
  140. package/dist/policies.js +33 -0
  141. package/dist/queue.interface.d.ts +39 -0
  142. package/dist/queue.interface.d.ts.map +1 -0
  143. package/dist/queue.interface.js +3 -0
  144. package/dist/queue.stub.d.ts +6 -0
  145. package/dist/queue.stub.d.ts.map +1 -0
  146. package/dist/queue.stub.js +8 -0
  147. package/dist/relations.d.ts +28 -0
  148. package/dist/relations.d.ts.map +1 -0
  149. package/dist/relations.js +25 -0
  150. package/dist/richtext-render.d.ts +11 -0
  151. package/dist/richtext-render.d.ts.map +1 -0
  152. package/dist/richtext-render.js +87 -0
  153. package/dist/richtext.d.ts +11 -0
  154. package/dist/richtext.d.ts.map +1 -0
  155. package/dist/richtext.js +13 -0
  156. package/dist/scheduler.interface.d.ts +4 -0
  157. package/dist/scheduler.interface.d.ts.map +1 -0
  158. package/dist/scheduler.interface.js +3 -0
  159. package/dist/scheduler.stub.d.ts +5 -0
  160. package/dist/scheduler.stub.d.ts.map +1 -0
  161. package/dist/scheduler.stub.js +7 -0
  162. package/dist/schema-mutator.d.ts +26 -0
  163. package/dist/schema-mutator.d.ts.map +1 -0
  164. package/dist/schema-mutator.js +3 -0
  165. package/dist/security/time-trap.d.ts +3 -2
  166. package/dist/security/time-trap.d.ts.map +1 -1
  167. package/dist/security/time-trap.js +5 -4
  168. package/dist/seed-ddl-destructive.d.ts +40 -0
  169. package/dist/seed-ddl-destructive.d.ts.map +1 -0
  170. package/dist/seed-ddl-destructive.js +149 -0
  171. package/dist/seed-ddl.d.ts +40 -0
  172. package/dist/seed-ddl.d.ts.map +1 -0
  173. package/dist/seed-ddl.js +100 -0
  174. package/dist/seed-layout.d.ts +145 -0
  175. package/dist/seed-layout.d.ts.map +1 -0
  176. package/dist/seed-layout.js +192 -0
  177. package/dist/seed-layout.repository.d.ts +22 -0
  178. package/dist/seed-layout.repository.d.ts.map +1 -0
  179. package/dist/seed-layout.repository.js +3 -0
  180. package/dist/seed-registry.d.ts +71 -0
  181. package/dist/seed-registry.d.ts.map +1 -0
  182. package/dist/seed-registry.js +143 -0
  183. package/dist/seed-types-generator.d.ts +1 -1
  184. package/dist/seed-types-generator.d.ts.map +1 -1
  185. package/dist/seed-validation.d.ts +23 -0
  186. package/dist/seed-validation.d.ts.map +1 -0
  187. package/dist/seed-validation.js +224 -0
  188. package/dist/seed.repository.d.ts +37 -0
  189. package/dist/seed.repository.d.ts.map +1 -0
  190. package/dist/seed.repository.js +3 -0
  191. package/dist/seeds.d.ts +20 -0
  192. package/dist/seeds.d.ts.map +1 -0
  193. package/dist/seeds.js +21 -0
  194. package/dist/settings/site-settings.repository.d.ts +2 -0
  195. package/dist/settings/site-settings.repository.d.ts.map +1 -1
  196. package/dist/site-settings.repository.d.ts +16 -0
  197. package/dist/site-settings.repository.d.ts.map +1 -0
  198. package/dist/site-settings.repository.js +3 -0
  199. package/dist/slug-utils.d.ts +18 -0
  200. package/dist/slug-utils.d.ts.map +1 -0
  201. package/dist/slug-utils.js +32 -0
  202. package/dist/storage.d.ts +46 -0
  203. package/dist/storage.d.ts.map +1 -0
  204. package/dist/storage.js +3 -0
  205. package/dist/types.d.ts +243 -0
  206. package/dist/types.d.ts.map +1 -0
  207. package/dist/types.js +3 -0
  208. package/dist/validation.d.ts +50 -0
  209. package/dist/validation.d.ts.map +1 -0
  210. package/dist/validation.js +660 -0
  211. package/dist/webhook-validation.d.ts +3 -0
  212. package/dist/webhook-validation.d.ts.map +1 -0
  213. package/dist/webhook-validation.js +6 -0
  214. package/package.json +1 -1
  215. package/dist/content/content-lock.repository.d.ts +0 -63
  216. package/dist/content/content-lock.repository.d.ts.map +0 -1
  217. package/dist/content/content-lock.repository.js +0 -15
@@ -0,0 +1,186 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ import { z } from 'zod';
4
+ // ---------------------------------------------------------------------------
5
+ // Shared rules used by both the Zod schemas and the semantic validator.
6
+ // ---------------------------------------------------------------------------
7
+ /** Namespaced widget type — lowercase npm-name-compatible.
8
+ * Built-ins use the 'core/' prefix; custom widgets '@scope/name'. */
9
+ export const WIDGET_TYPE_REGEX = /^[a-z0-9@][a-z0-9@/_-]*$/;
10
+ /** Max serialized size of a single widget `config`
11
+ * (`JSON.stringify(widget.config).length`) — guards D1 row bloat. */
12
+ export const MAX_WIDGET_CONFIG_BYTES = 8192;
13
+ // ---------------------------------------------------------------------------
14
+ // Zod schemas for shape validation
15
+ // ---------------------------------------------------------------------------
16
+ export const dashboardWidgetInstanceSchema = z.object({
17
+ id: z.string().min(1),
18
+ type: z.string().regex(WIDGET_TYPE_REGEX),
19
+ title: z.string().max(80).optional(),
20
+ config: z.record(z.string(), z.unknown()),
21
+ });
22
+ export const dashboardColumnSchema = z.object({
23
+ id: z.string().min(1),
24
+ widgets: z.array(dashboardWidgetInstanceSchema),
25
+ });
26
+ export const dashboardSectionSchema = z.object({
27
+ id: z.string().min(1),
28
+ label: z.string().max(60).optional(),
29
+ hideLabel: z.boolean().optional(),
30
+ collapsible: z.boolean().optional(),
31
+ columns: z.array(dashboardColumnSchema).min(1).max(4),
32
+ columnSpans: z.array(z.number().int().min(1).max(12)).optional(),
33
+ });
34
+ export const dashboardPageSchema = z.object({
35
+ id: z.string().min(1),
36
+ slug: z.string().regex(/^[a-z0-9][a-z0-9-]*$/).max(40),
37
+ label: z.string().min(1).max(60),
38
+ icon: z.string().max(40).optional(),
39
+ sections: z.array(dashboardSectionSchema).min(1),
40
+ });
41
+ export const dashboardLayoutSchema = z.object({
42
+ version: z.literal(1),
43
+ pages: z.array(dashboardPageSchema).min(1).max(8),
44
+ });
45
+ // ---------------------------------------------------------------------------
46
+ // Default dashboard layout generator
47
+ // ---------------------------------------------------------------------------
48
+ const defaultIdFactory = () => crypto.randomUUID();
49
+ /** Structural port of the legacy hardcoded dashboard config into a single
50
+ * 'Overview' page. Widgets fetch their own data: configs carry only the
51
+ * variant and (where relevant) the default seed binding. */
52
+ export function generateDefaultDashboardLayout(seeds, opts) {
53
+ const newId = opts?.newId ?? defaultIdFactory;
54
+ const widget = (type, config) => ({ id: newId(), type, config });
55
+ const singleWidgetColumn = (w) => ({
56
+ id: newId(),
57
+ widgets: [w],
58
+ });
59
+ const sections = [];
60
+ // Setup checklist, full width.
61
+ sections.push({
62
+ id: newId(),
63
+ hideLabel: true,
64
+ columns: [singleWidgetColumn(widget('core/setup-checklist', { variant: 'full' }))],
65
+ });
66
+ // Status row: equal columns; quick-draft needs at least one seed to target.
67
+ const statusWidgets = [
68
+ widget('core/site-status', { variant: 'badge' }),
69
+ widget('core/storage', { variant: 'gauge' }),
70
+ widget('core/publication-stats', { variant: 'trio' }),
71
+ ];
72
+ if (seeds.length > 0) {
73
+ statusWidgets.push(widget('core/quick-draft', { variant: 'minimal' }));
74
+ }
75
+ sections.push({
76
+ id: newId(),
77
+ hideLabel: true,
78
+ columns: statusWidgets.map(singleWidgetColumn),
79
+ });
80
+ if (seeds.length > 0) {
81
+ const seedSlug = seeds.find((s) => s.slug === 'articoli')?.slug ?? seeds[0].slug;
82
+ // Content row: recent content | pending drafts.
83
+ sections.push({
84
+ id: newId(),
85
+ hideLabel: true,
86
+ columns: [
87
+ singleWidgetColumn(widget('core/recent-content', { seedSlug, variant: 'list' })),
88
+ singleWidgetColumn(widget('core/pending-drafts', { seedSlug, variant: 'list' })),
89
+ ],
90
+ columnSpans: [6, 6],
91
+ });
92
+ // Media & activity row: media gallery | activity feed.
93
+ sections.push({
94
+ id: newId(),
95
+ hideLabel: true,
96
+ columns: [
97
+ singleWidgetColumn(widget('core/media-gallery', { seedSlug, variant: 'grid' })),
98
+ singleWidgetColumn(widget('core/activity-feed', { seedSlug, variant: 'feed' })),
99
+ ],
100
+ columnSpans: [6, 6],
101
+ });
102
+ }
103
+ return {
104
+ version: 1,
105
+ pages: [
106
+ {
107
+ id: newId(),
108
+ slug: 'overview',
109
+ label: 'Overview',
110
+ icon: 'LayoutDashboard',
111
+ sections,
112
+ },
113
+ ],
114
+ };
115
+ }
116
+ /** Semantic validation on top of the Zod shape check (the caller's job).
117
+ * Widgets bound to a seed that no longer exists are silently stripped
118
+ * (auto-cleanup, recorded as a warning). Unknown widget types only warn —
119
+ * custom widgets are invisible to the Worker and must never be stripped.
120
+ * `cleaned` is always returned, errors or not. */
121
+ export function validateDashboardLayout(layout, ctx) {
122
+ const errors = [];
123
+ const warnings = [];
124
+ // Auto-cleanup: drop widgets whose config.seedSlug references a missing seed.
125
+ const cleanedPages = layout.pages.map((page) => ({
126
+ ...page,
127
+ sections: page.sections.map((section) => ({
128
+ ...section,
129
+ columns: section.columns.map((col) => ({
130
+ ...col,
131
+ widgets: (col.widgets ?? []).filter((w) => {
132
+ const seedSlug = (w.config ?? {})['seedSlug'];
133
+ if (typeof seedSlug === 'string' && !ctx.seedSlugs.has(seedSlug)) {
134
+ warnings.push(`Widget '${w.type}' (id=${w.id}) removed: seed '${seedSlug}' is not registered.`);
135
+ return false;
136
+ }
137
+ return true;
138
+ }),
139
+ })),
140
+ })),
141
+ }));
142
+ // Semantic checks on the cleaned layout
143
+ const seenWidgetIds = new Set();
144
+ const seenPageSlugs = new Set();
145
+ for (const page of cleanedPages) {
146
+ if (seenPageSlugs.has(page.slug)) {
147
+ errors.push(`Page slug '${page.slug}' appears more than once in the layout.`);
148
+ }
149
+ else {
150
+ seenPageSlugs.add(page.slug);
151
+ }
152
+ for (const section of page.sections) {
153
+ if (section.columnSpans !== undefined) {
154
+ if (section.columnSpans.length !== section.columns.length) {
155
+ errors.push(`Section ${section.id}: columnSpans has ${section.columnSpans.length} entries for ${section.columns.length} columns.`);
156
+ }
157
+ else {
158
+ const sum = section.columnSpans.reduce((acc, span) => acc + span, 0);
159
+ if (sum !== 12) {
160
+ errors.push(`Section ${section.id}: columnSpans must sum to 12, got ${sum}.`);
161
+ }
162
+ }
163
+ }
164
+ for (const col of section.columns) {
165
+ for (const w of col.widgets) {
166
+ if (seenWidgetIds.has(w.id)) {
167
+ errors.push(`Widget id '${w.id}' appears more than once in the layout.`);
168
+ }
169
+ else {
170
+ seenWidgetIds.add(w.id);
171
+ }
172
+ if (JSON.stringify(w.config ?? {}).length > MAX_WIDGET_CONFIG_BYTES) {
173
+ errors.push(`Widget '${w.type}' (id=${w.id}): config exceeds ${MAX_WIDGET_CONFIG_BYTES} bytes when serialized.`);
174
+ }
175
+ if (ctx.knownWidgetTypes !== undefined && !ctx.knownWidgetTypes.has(w.type)) {
176
+ warnings.push(`Widget '${w.type}' (id=${w.id}) is not a known widget type.`);
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }
182
+ const cleaned = { ...layout, pages: cleanedPages };
183
+ if (errors.length > 0)
184
+ return { ok: false, errors, cleaned, warnings };
185
+ return { ok: true, cleaned, warnings };
186
+ }
@@ -0,0 +1,18 @@
1
+ import type { DashboardLayout } from './dashboard-layout.js';
2
+ export interface DashboardLayoutRecord {
3
+ scope: string;
4
+ layout: DashboardLayout;
5
+ updatedAt: number;
6
+ updatedBy: string;
7
+ }
8
+ export interface IDashboardLayoutRepository {
9
+ /** Stored layout for a scope, or null if none was ever saved. */
10
+ get(scope: string): Promise<DashboardLayoutRecord | null>;
11
+ /** Scopes that currently have a stored row — used by the Sprint 06 builder UI. */
12
+ listScopes(): Promise<string[]>;
13
+ /** Upsert. `updatedBy` is the writer's user id. */
14
+ upsert(scope: string, layout: DashboardLayout, updatedBy: string): Promise<void>;
15
+ /** Remove the stored row — the "Reset" action. */
16
+ remove(scope: string): Promise<void>;
17
+ }
18
+ //# sourceMappingURL=dashboard-layout.repository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-layout.repository.d.ts","sourceRoot":"","sources":["../src/dashboard-layout.repository.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAE5D,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,eAAe,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,0BAA0B;IACzC,iEAAiE;IACjE,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAA;IACzD,kFAAkF;IAClF,UAAU,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IAC/B,mDAAmD;IACnD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAChF,kDAAkD;IAClD,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACrC"}
@@ -0,0 +1,3 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ export {};
@@ -0,0 +1,3 @@
1
+ export declare const ROLES_ALLOWED_TO_EDIT_DASHBOARD: ReadonlyArray<string>;
2
+ export declare function canEditDashboard(role: string | undefined | null): boolean;
3
+ //# sourceMappingURL=dashboard-permissions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-permissions.d.ts","sourceRoot":"","sources":["../src/dashboard-permissions.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,+BAA+B,EAAE,aAAa,CAAC,MAAM,CAAa,CAAA;AAE/E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAGzE"}
@@ -0,0 +1,11 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ // NOTE: change this list to extend write-access to other roles
4
+ // (e.g. add 'editor', or introduce a fine-grained 'dashboard:edit' permission).
5
+ // Single source of truth — used by both API guards and dashboard buttons.
6
+ export const ROLES_ALLOWED_TO_EDIT_DASHBOARD = ['admin'];
7
+ export function canEditDashboard(role) {
8
+ if (!role)
9
+ return false;
10
+ return ROLES_ALLOWED_TO_EDIT_DASHBOARD.includes(role);
11
+ }
@@ -0,0 +1,12 @@
1
+ /** Roles that may have their own dashboard layout. Single source of truth —
2
+ * extend this list if a new role is introduced. */
3
+ export declare const KNOWN_DASHBOARD_ROLES: ReadonlyArray<string>;
4
+ export declare const DEFAULT_DASHBOARD_SCOPE = "default";
5
+ /** Builds the per-role scope string, e.g. `roleScope('editor') === 'role:editor'`. */
6
+ export declare function roleScope(role: string): string;
7
+ /** Whether `scope` is one of the closed set: `'default' | 'role:<known role>'`. */
8
+ export declare function isValidDashboardScope(scope: string): boolean;
9
+ /** Read-resolution order for a caller's role: `['role:<role>', 'default']`,
10
+ * or just `['default']` for unknown/missing roles. */
11
+ export declare function resolveDashboardScopeChain(role: string | undefined | null): string[];
12
+ //# sourceMappingURL=dashboard-scopes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-scopes.d.ts","sourceRoot":"","sources":["../src/dashboard-scopes.ts"],"names":[],"mappings":"AAOA;oDACoD;AACpD,eAAO,MAAM,qBAAqB,EAAE,aAAa,CAAC,MAAM,CAAuB,CAAA;AAE/E,eAAO,MAAM,uBAAuB,YAAY,CAAA;AAEhD,sFAAsF;AACtF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAK5D;AAED;uDACuD;AACvD,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,EAAE,CAKpF"}
@@ -0,0 +1,30 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ // ---------------------------------------------------------------------------
4
+ // Dashboard layout scopes (Sprint 06: role-based dashboards)
5
+ // ---------------------------------------------------------------------------
6
+ /** Roles that may have their own dashboard layout. Single source of truth —
7
+ * extend this list if a new role is introduced. */
8
+ export const KNOWN_DASHBOARD_ROLES = ['admin', 'editor'];
9
+ export const DEFAULT_DASHBOARD_SCOPE = 'default';
10
+ /** Builds the per-role scope string, e.g. `roleScope('editor') === 'role:editor'`. */
11
+ export function roleScope(role) {
12
+ return `role:${role}`;
13
+ }
14
+ /** Whether `scope` is one of the closed set: `'default' | 'role:<known role>'`. */
15
+ export function isValidDashboardScope(scope) {
16
+ if (scope === DEFAULT_DASHBOARD_SCOPE)
17
+ return true;
18
+ const match = /^role:(.+)$/.exec(scope);
19
+ if (!match)
20
+ return false;
21
+ return KNOWN_DASHBOARD_ROLES.includes(match[1]);
22
+ }
23
+ /** Read-resolution order for a caller's role: `['role:<role>', 'default']`,
24
+ * or just `['default']` for unknown/missing roles. */
25
+ export function resolveDashboardScopeChain(role) {
26
+ if (role && KNOWN_DASHBOARD_ROLES.includes(role)) {
27
+ return [roleScope(role), DEFAULT_DASHBOARD_SCOPE];
28
+ }
29
+ return [DEFAULT_DASHBOARD_SCOPE];
30
+ }
@@ -0,0 +1,3 @@
1
+ import type { Seed } from './types.js';
2
+ export declare function defineSeed(seed: Seed): Seed;
3
+ //# sourceMappingURL=define-seed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-seed.d.ts","sourceRoot":"","sources":["../src/define-seed.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAEtC,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAE3C"}
@@ -0,0 +1,5 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ export function defineSeed(seed) {
4
+ return seed;
5
+ }
@@ -0,0 +1,5 @@
1
+ export interface IDemoDataRepository {
2
+ /** Executes the compiled demo dataset SQL against the database. */
3
+ loadDemoData(): Promise<void>;
4
+ }
5
+ //# sourceMappingURL=demo-data.repository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"demo-data.repository.d.ts","sourceRoot":"","sources":["../src/demo-data.repository.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,mBAAmB;IAClC,mEAAmE;IACnE,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9B"}
@@ -0,0 +1,3 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ export {};
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The resolved email message that the provider receives and sends.
3
+ * Constructed by the service by combining call parameters with template builder output.
4
+ */
5
+ export interface OutboundEmail {
6
+ /** Sender address in RFC 5321 format (e.g., "Beech CMS <noreply@beechcms.dev>"). */
7
+ from: string;
8
+ /** List of recipient addresses. Must contain at least one element. */
9
+ to: string[];
10
+ subject: string;
11
+ /** Complete HTML body. Must be a valid HTML document (see templates/shell.ts). */
12
+ html: string;
13
+ }
14
+ /**
15
+ * Formal contract for email sending providers.
16
+ *
17
+ * Every implementation (Resend, SMTP, …) must comply with this interface.
18
+ * It is the only point of coupling between the email module and any external service.
19
+ *
20
+ * To add a new provider: create a class in apps/api/src/shared/email/providers/
21
+ * that implements this interface, then wire it in email.service.ts.
22
+ */
23
+ export interface EmailProvider {
24
+ /**
25
+ * Sends a single transactional email.
26
+ *
27
+ * @throws {Error} If the provider rejects the request. The caller is responsible
28
+ * for catching and handling this error appropriately.
29
+ */
30
+ send(email: OutboundEmail): Promise<void>;
31
+ }
32
+ //# sourceMappingURL=email.provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email.provider.d.ts","sourceRoot":"","sources":["../src/email.provider.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,oFAAoF;IACpF,IAAI,EAAE,MAAM,CAAA;IACZ,sEAAsE;IACtE,EAAE,EAAE,MAAM,EAAE,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;;OAKG;IACH,IAAI,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1C"}
@@ -0,0 +1,4 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
+ // See LICENSE in the repository root for license terms.
4
+ export {};
@@ -235,13 +235,11 @@ export declare function generateRetypeColumn(seed: Seed, branch: Branch): string
235
235
  */
236
236
  export declare function generateRetypeIncompatibleCount(seed: Seed, branch: Branch): string[];
237
237
  /**
238
- * Additive statements that turn soft delete on for a table that already exists.
238
+ * Additive statements that upgrade a table predating universal soft delete.
239
239
  *
240
- * The partial unique index is emitted for correctness on tables that were CREATED with
241
- * `softDelete: true`. On a pre-existing table the inline `slug … UNIQUE` survives as a
242
- * sqlite_autoindex that SQLite cannot drop via ALTER; there the partial index is redundant
243
- * and a trashed slug stays reserved until the table is rebuilt (see ROADMAP deferral).
244
- * Returns [] when the seed does not opt in.
240
+ * On a pre-existing table the inline `slug … UNIQUE` survives as a sqlite_autoindex that
241
+ * SQLite cannot drop via ALTER; there the partial index is redundant, and the `_old_<ms>`
242
+ * slug rename on soft delete is what frees the slug. Callers decide when to emit these.
245
243
  */
246
244
  export declare function generateEnableSoftDelete(seed: Seed): string[];
247
245
  //# sourceMappingURL=ddl.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ddl.d.ts","sourceRoot":"","sources":["../../../src/engine/ddl/ddl.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ddl.d.ts","sourceRoot":"","sources":["../../../src/engine/ddl/ddl.ts"],"names":[],"mappings":"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"}
@@ -70,8 +70,6 @@ export function vectorTableName(seed) {
70
70
  * @returns True if the column is valid, false otherwise.
71
71
  */
72
72
  export function isValidColumn(seed, col) {
73
- if (col === 'deleted_at')
74
- return seed.softDelete === true;
75
73
  if (SYSTEM_COLUMNS.has(col))
76
74
  return true;
77
75
  return seed.branches.some(b => b.alias === col);
@@ -127,12 +125,9 @@ export function generateCreateTable(seed) {
127
125
  const lines = [
128
126
  `CREATE TABLE IF NOT EXISTS ${table} (`,
129
127
  ` id TEXT NOT NULL PRIMARY KEY,`,
130
- // Soft-delete tables carry uniqueness in a PARTIAL unique index instead, so a trashed
131
- // row stops reserving its slug. An inline UNIQUE would become a sqlite_autoindex that
132
- // no ALTER TABLE can drop later.
133
- seed.softDelete
134
- ? ` slug TEXT NOT NULL,`
135
- : ` slug TEXT NOT NULL UNIQUE,`,
128
+ // Uniqueness lives in a PARTIAL unique index instead, so a trashed row stops reserving
129
+ // its slug. An inline UNIQUE would become a sqlite_autoindex that no ALTER TABLE can drop later.
130
+ ` slug TEXT NOT NULL,`,
136
131
  ` status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'review', 'published', 'archived')),`,
137
132
  ];
138
133
  for (const branch of seed.branches) {
@@ -151,9 +146,8 @@ export function generateCreateTable(seed) {
151
146
  }
152
147
  }
153
148
  lines.push(` created_at INTEGER NOT NULL DEFAULT (unixepoch()),`);
154
- lines.push(` updated_at INTEGER NOT NULL DEFAULT (unixepoch())` + (seed.softDelete ? ',' : ''));
155
- if (seed.softDelete)
156
- lines.push(` deleted_at INTEGER`);
149
+ lines.push(` updated_at INTEGER NOT NULL DEFAULT (unixepoch()),`);
150
+ lines.push(` deleted_at INTEGER`);
157
151
  lines.push(`);`);
158
152
  return lines.join('\n');
159
153
  }
@@ -271,11 +265,9 @@ export function generateIndexes(seed) {
271
265
  indexes.push(`CREATE INDEX IF NOT EXISTS ${name} ON ${table}(${column});`);
272
266
  }
273
267
  }
274
- if (seed.softDelete) {
275
- indexes.push(`CREATE INDEX IF NOT EXISTS idx_${slug}_deleted_at ON ${table}(deleted_at);`);
276
- // Active rows only: a trashed row releases its slug for reuse (feature brief §4).
277
- indexes.push(`CREATE UNIQUE INDEX IF NOT EXISTS idx_${slug}_slug_active ON ${table}(slug) WHERE deleted_at IS NULL;`);
278
- }
268
+ indexes.push(`CREATE INDEX IF NOT EXISTS idx_${slug}_deleted_at ON ${table}(deleted_at);`);
269
+ // Active rows only: a trashed row releases its slug for reuse (feature brief §4).
270
+ indexes.push(`CREATE UNIQUE INDEX IF NOT EXISTS idx_${slug}_slug_active ON ${table}(slug) WHERE deleted_at IS NULL;`);
279
271
  return indexes;
280
272
  }
281
273
  /**
@@ -389,9 +381,7 @@ export function getExpectedColumns(seed) {
389
381
  ...branchCols,
390
382
  { name: 'created_at', sqlType: 'INTEGER', notNull: true, isPk: false },
391
383
  { name: 'updated_at', sqlType: 'INTEGER', notNull: true, isPk: false },
392
- ...(seed.softDelete
393
- ? [{ name: 'deleted_at', sqlType: 'INTEGER', notNull: false, isPk: false }]
394
- : []),
384
+ { name: 'deleted_at', sqlType: 'INTEGER', notNull: false, isPk: false },
395
385
  ];
396
386
  }
397
387
  /**
@@ -632,17 +622,13 @@ export function generateRetypeIncompatibleCount(seed, branch) {
632
622
  return stmts;
633
623
  }
634
624
  /**
635
- * Additive statements that turn soft delete on for a table that already exists.
625
+ * Additive statements that upgrade a table predating universal soft delete.
636
626
  *
637
- * The partial unique index is emitted for correctness on tables that were CREATED with
638
- * `softDelete: true`. On a pre-existing table the inline `slug … UNIQUE` survives as a
639
- * sqlite_autoindex that SQLite cannot drop via ALTER; there the partial index is redundant
640
- * and a trashed slug stays reserved until the table is rebuilt (see ROADMAP deferral).
641
- * Returns [] when the seed does not opt in.
627
+ * On a pre-existing table the inline `slug … UNIQUE` survives as a sqlite_autoindex that
628
+ * SQLite cannot drop via ALTER; there the partial index is redundant, and the `_old_<ms>`
629
+ * slug rename on soft delete is what frees the slug. Callers decide when to emit these.
642
630
  */
643
631
  export function generateEnableSoftDelete(seed) {
644
- if (!seed.softDelete)
645
- return [];
646
632
  const table = tableName(seed);
647
633
  const slug = seed.slug;
648
634
  return [
@@ -1 +1 @@
1
- {"version":3,"file":"seed-ddl.d.ts","sourceRoot":"","sources":["../../../src/engine/ddl/seed-ddl.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAgBvC;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CAanD;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,IAAI,EACV,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,EAC5B,oBAAoB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,GACxC,UAAU,CAmCZ;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CA0BnD"}
1
+ {"version":3,"file":"seed-ddl.d.ts","sourceRoot":"","sources":["../../../src/engine/ddl/seed-ddl.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAiBvC;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CAenD;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,IAAI,EACV,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,EAC5B,oBAAoB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,GACxC,UAAU,CAwCZ;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CA0BnD"}
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // Copyright (c) 2024–2026 Flavio De Musso
3
- import { generateCreateTable, generateDraftTable, generateIndexes, generateFtsTable, generateFtsTriggers, generateAddColumn, generateAddDraftSnapshotColumn, generateJunctionTable, generateJunctionIndexes, generateJunctionDraftTable, generateEnableSoftDelete, indexableSearchBranches, } from './ddl.js';
3
+ import { generateCreateTable, generateDraftTable, generateIndexes, generateFtsTable, generateFtsTriggers, generateVectorTable, generateAddColumn, generateAddDraftSnapshotColumn, generateJunctionTable, generateJunctionIndexes, generateJunctionDraftTable, generateEnableSoftDelete, indexableSearchBranches, } from './ddl.js';
4
4
  /**
5
5
  * Full create-from-scratch statement set for a seed. Mirrors the CLI's buildStatements.
6
6
  * Order: parent table → indexes → draft table → FTS table → FTS triggers →
@@ -16,6 +16,9 @@ export function planCreateSeed(seed) {
16
16
  const fts = generateFtsTable(seed);
17
17
  if (fts)
18
18
  stmts.push(fts, ...generateFtsTriggers(seed));
19
+ const vector = generateVectorTable(seed);
20
+ if (vector)
21
+ stmts.push(vector);
19
22
  for (const branch of seed.branches) {
20
23
  if (branch.type !== 'relation' || branch.multiple !== true)
21
24
  continue;
@@ -59,7 +62,7 @@ export function planExtendSeed(seed, existingColumns, existingDraftColumns) {
59
62
  }
60
63
  }
61
64
  // System column, not a branch: the branch loop above can never emit it.
62
- if (seed.softDelete && !existingColumns.has('deleted_at')) {
65
+ if (!existingColumns.has('deleted_at')) {
63
66
  statements.push(...generateEnableSoftDelete(seed));
64
67
  }
65
68
  // Draft-table system column. Emitted only when the caller actually introspected
@@ -70,6 +73,11 @@ export function planExtendSeed(seed, existingColumns, existingDraftColumns) {
70
73
  !existingDraftColumns.has('live_snapshot_at')) {
71
74
  statements.push(generateAddDraftSnapshotColumn(seed));
72
75
  }
76
+ // Vector table is CREATE TABLE IF NOT EXISTS — safe and idempotent to emit if seed has indexable branches
77
+ const vector = generateVectorTable(seed);
78
+ if (vector) {
79
+ statements.push(vector);
80
+ }
73
81
  // CREATE INDEX IF NOT EXISTS — idempotent, safe to re-run
74
82
  statements.push(...generateIndexes(seed));
75
83
  return { statements, ftsRebuildNeeded };
@@ -68,19 +68,6 @@ export declare function generateDraftTable(seed: Seed): string | null;
68
68
  * @returns The ALTER TABLE SQL statement.
69
69
  */
70
70
  export declare function generateAddColumn(seed: Seed, branch: Branch): string;
71
- /**
72
- * Generates the `ALTER TABLE content_{slug}_drafts ADD COLUMN live_snapshot_at INTEGER` statement
73
- * for a draft table provisioned before the column existed.
74
- *
75
- * System column, not a branch — `generateAddColumn` iterates `seed.branches` and can never emit it.
76
- * NOT idempotent: `ADD COLUMN` fails when the column is already present and `ISchemaMutator.execDdl`
77
- * aborts the whole batch on the first failing statement, so the caller MUST check the physical
78
- * columns first (see `planExtendSeed`).
79
- *
80
- * @param seed The seed definition. Caller must have verified `seed.allowDrafts`.
81
- * @returns The ALTER TABLE statement.
82
- */
83
- export declare function generateAddDraftSnapshotColumn(seed: Seed): string;
84
71
  /**
85
72
  * Generates SQL index statements for system fields (status, created_at)
86
73
  * and indexable branch columns.
@@ -203,14 +190,4 @@ export declare function generateRenameColumn(seed: Seed, from: string, to: strin
203
190
  * @returns An array of SQL statements.
204
191
  */
205
192
  export declare function generateRetypeColumn(seed: Seed, branch: Branch): string[];
206
- /**
207
- * Additive statements that turn soft delete on for a table that already exists.
208
- *
209
- * The partial unique index is emitted for correctness on tables that were CREATED with
210
- * `softDelete: true`. On a pre-existing table the inline `slug … UNIQUE` survives as a
211
- * sqlite_autoindex that SQLite cannot drop via ALTER; there the partial index is redundant
212
- * and a trashed slug stays reserved until the table is rebuilt (see ROADMAP deferral).
213
- * Returns [] when the seed does not opt in.
214
- */
215
- export declare function generateEnableSoftDelete(seed: Seed): string[];
216
193
  //# sourceMappingURL=ddl.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ddl.d.ts","sourceRoot":"","sources":["../../src/engine/ddl.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ddl.d.ts","sourceRoot":"","sources":["../../src/engine/ddl.ts"],"names":[],"mappings":"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"}