@beechcms/core 0.5.0 → 0.6.0-preview.2
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.
- package/dist/auth/hash-provider.d.ts.map +1 -1
- package/dist/auth/hash-provider.js +2 -0
- package/dist/auth/password-reset-token.repository.d.ts.map +1 -1
- package/dist/auth/password-reset-token.repository.js +2 -0
- package/dist/auth/session.repository.d.ts.map +1 -1
- package/dist/auth/session.repository.js +2 -0
- package/dist/auth/token-service.d.ts +2 -0
- package/dist/auth/token-service.d.ts.map +1 -1
- package/dist/auth/token-service.js +2 -0
- package/dist/auth/user.repository.d.ts +4 -1
- package/dist/auth/user.repository.d.ts.map +1 -1
- package/dist/auth/user.repository.js +2 -0
- package/dist/automations-grammar-words.d.ts.map +1 -1
- package/dist/automations-grammar-words.js +2 -0
- package/dist/automations.repository.interface.d.ts.map +1 -1
- package/dist/automations.repository.interface.js +2 -0
- package/dist/automations.runner.interface.d.ts.map +1 -1
- package/dist/automations.runner.interface.js +2 -0
- package/dist/automations.runner.stub.d.ts.map +1 -1
- package/dist/automations.runner.stub.js +2 -0
- package/dist/automations.types.d.ts +2 -0
- package/dist/automations.types.d.ts.map +1 -1
- package/dist/automations.types.js +2 -0
- package/dist/clock.d.ts.map +1 -1
- package/dist/clock.js +2 -0
- package/dist/content-scan.repository.d.ts.map +1 -1
- package/dist/content-scan.repository.js +2 -0
- package/dist/content.repository.d.ts +62 -0
- package/dist/content.repository.d.ts.map +1 -1
- package/dist/content.repository.js +18 -0
- package/dist/dashboard-layout.d.ts +154 -0
- package/dist/dashboard-layout.d.ts.map +1 -0
- package/dist/dashboard-layout.js +186 -0
- package/dist/dashboard-layout.repository.d.ts +18 -0
- package/dist/dashboard-layout.repository.d.ts.map +1 -0
- package/dist/dashboard-layout.repository.js +3 -0
- package/dist/dashboard-permissions.d.ts +3 -0
- package/dist/dashboard-permissions.d.ts.map +1 -0
- package/dist/dashboard-permissions.js +11 -0
- package/dist/dashboard-scopes.d.ts +12 -0
- package/dist/dashboard-scopes.d.ts.map +1 -0
- package/dist/dashboard-scopes.js +30 -0
- package/dist/define-seed.d.ts.map +1 -1
- package/dist/define-seed.js +2 -0
- package/dist/demo-data.repository.d.ts +5 -0
- package/dist/demo-data.repository.d.ts.map +1 -0
- package/dist/demo-data.repository.js +3 -0
- package/dist/engine.d.ts +76 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +259 -5
- package/dist/file-types.d.ts +13 -0
- package/dist/file-types.d.ts.map +1 -0
- package/dist/file-types.js +85 -0
- package/dist/id-generator.d.ts +7 -0
- package/dist/id-generator.d.ts.map +1 -1
- package/dist/id-generator.js +5 -0
- package/dist/idempotency.repository.d.ts.map +1 -1
- package/dist/idempotency.repository.js +2 -0
- package/dist/index.d.ts +16 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -1
- package/dist/layout-permissions.d.ts +3 -0
- package/dist/layout-permissions.d.ts.map +1 -0
- package/dist/layout-permissions.js +11 -0
- package/dist/media.repository.d.ts.map +1 -1
- package/dist/media.repository.js +2 -0
- package/dist/notifications/notification-service.d.ts.map +1 -1
- package/dist/notifications/notification-service.js +2 -0
- package/dist/notifications/notification.repository.d.ts.map +1 -1
- package/dist/notifications/notification.repository.js +2 -9
- package/dist/observability/activity-log.repository.d.ts.map +1 -1
- package/dist/observability/activity-log.repository.js +2 -0
- package/dist/observability/activity-logger.d.ts +2 -2
- package/dist/observability/activity-logger.d.ts.map +1 -1
- package/dist/observability/activity-logger.js +2 -11
- package/dist/observability/analytics.repository.d.ts.map +1 -1
- package/dist/observability/analytics.repository.js +2 -0
- package/dist/policies.d.ts.map +1 -1
- package/dist/policies.js +8 -3
- package/dist/rate-limit/rate-limiter.d.ts.map +1 -1
- package/dist/rate-limit/rate-limiter.js +2 -0
- package/dist/relations.d.ts +28 -0
- package/dist/relations.d.ts.map +1 -0
- package/dist/relations.js +25 -0
- package/dist/richtext-render.d.ts.map +1 -1
- package/dist/richtext-render.js +2 -0
- package/dist/richtext.d.ts.map +1 -1
- package/dist/richtext.js +2 -0
- package/dist/scheduler.interface.d.ts.map +1 -1
- package/dist/scheduler.interface.js +2 -0
- package/dist/scheduler.stub.d.ts.map +1 -1
- package/dist/scheduler.stub.js +2 -0
- package/dist/schema-mutator.d.ts +22 -0
- package/dist/schema-mutator.d.ts.map +1 -0
- package/dist/schema-mutator.js +3 -0
- package/dist/search/search.repository.d.ts.map +1 -1
- package/dist/search/search.repository.js +2 -0
- package/dist/seed-ddl-destructive.d.ts +40 -0
- package/dist/seed-ddl-destructive.d.ts.map +1 -0
- package/dist/seed-ddl-destructive.js +149 -0
- package/dist/seed-ddl.d.ts +40 -0
- package/dist/seed-ddl.d.ts.map +1 -0
- package/dist/seed-ddl.js +100 -0
- package/dist/seed-layout.d.ts +117 -0
- package/dist/seed-layout.d.ts.map +1 -0
- package/dist/seed-layout.js +169 -0
- package/dist/seed-layout.repository.d.ts +18 -0
- package/dist/seed-layout.repository.d.ts.map +1 -0
- package/dist/seed-layout.repository.js +3 -0
- package/dist/seed-registry.d.ts +23 -1
- package/dist/seed-registry.d.ts.map +1 -1
- package/dist/seed-registry.js +105 -0
- package/dist/seed-validation.d.ts +23 -0
- package/dist/seed-validation.d.ts.map +1 -0
- package/dist/seed-validation.js +224 -0
- package/dist/seed.repository.d.ts +37 -0
- package/dist/seed.repository.d.ts.map +1 -0
- package/dist/seed.repository.js +3 -0
- package/dist/seeds.d.ts.map +1 -1
- package/dist/seeds.js +2 -0
- package/dist/site-settings.repository.d.ts +16 -0
- package/dist/site-settings.repository.d.ts.map +1 -0
- package/dist/site-settings.repository.js +3 -0
- package/dist/slug-utils.d.ts.map +1 -1
- package/dist/slug-utils.js +2 -0
- package/dist/storage.d.ts +12 -38
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +2 -0
- package/dist/types.d.ts +81 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -0
- package/dist/validation.d.ts +23 -17
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +487 -381
- package/dist/webhook-validation.d.ts +3 -0
- package/dist/webhook-validation.d.ts.map +1 -0
- package/dist/webhook-validation.js +6 -0
- package/dist/widget/widget.repository.d.ts +32 -3
- package/dist/widget/widget.repository.d.ts.map +1 -1
- package/dist/widget/widget.repository.js +6 -1
- package/package.json +5 -4
|
@@ -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 @@
|
|
|
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
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define-seed.d.ts","sourceRoot":"","sources":["../src/define-seed.ts"],"names":[],"mappings":"
|
|
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"}
|
package/dist/define-seed.js
CHANGED
|
@@ -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"}
|
package/dist/engine.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* It does not know about HTTP, auth, or UI — it's a pure TypeScript library.
|
|
7
7
|
*/
|
|
8
8
|
import type { Seed, Branch, SelectOptions, ParameterizedQuery } from './types.js';
|
|
9
|
+
export declare function indexableSearchBranches(seed: Seed): Branch[];
|
|
9
10
|
/**
|
|
10
11
|
* Generates `CREATE TABLE IF NOT EXISTS content_{slug}` with system columns
|
|
11
12
|
* + one column per Branch. Pure function: same Seed → same SQL.
|
|
@@ -60,6 +61,81 @@ export declare function getExpectedColumns(seed: Seed): SchemaColumn[];
|
|
|
60
61
|
* boolean → 0/1 | date → Unix timestamp | json/asset-list → JSON string
|
|
61
62
|
*/
|
|
62
63
|
export declare function serializeForDb(branch: Branch, value: unknown): string | number | null;
|
|
64
|
+
/**
|
|
65
|
+
* Returns the junction table name for a many-to-many relation branch.
|
|
66
|
+
* Format: `rel_<seedSlug>_<branchAlias>`.
|
|
67
|
+
*/
|
|
68
|
+
export declare function junctionTableName(seedSlug: string, branchAlias: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* Generates `CREATE TABLE IF NOT EXISTS rel_<seed>_<alias>` for a multi-relation
|
|
71
|
+
* branch. The parent FK always cascades (parent lifecycle owns the edge);
|
|
72
|
+
* the target FK uses `branch.onDelete` (default CASCADE).
|
|
73
|
+
*
|
|
74
|
+
* @throws {Error} when called on a non-multi-relation branch
|
|
75
|
+
* @throws {Error} when `branch.targetSeed` is missing
|
|
76
|
+
*/
|
|
77
|
+
export declare function generateJunctionTable(seed: Seed, branch: Branch): string;
|
|
78
|
+
/**
|
|
79
|
+
* Generates the two B-tree indexes for a junction table:
|
|
80
|
+
* one on `parent_id` (list entries by parent) and one on `target_id`
|
|
81
|
+
* (cascade checks from target side).
|
|
82
|
+
*/
|
|
83
|
+
export declare function generateJunctionIndexes(seed: Seed, branch: Branch): string[];
|
|
84
|
+
/**
|
|
85
|
+
* Generates the drafts junction table `rel_<seed>_<alias>_drafts` for a
|
|
86
|
+
* many-to-many relation branch. Note: no FK on `target_id` — targets may be
|
|
87
|
+
* deleted between draft save and publish; the FK would be wrong here.
|
|
88
|
+
* Returns null when `seed.allowDrafts` is falsy.
|
|
89
|
+
*/
|
|
90
|
+
export declare function generateJunctionDraftTable(seed: Seed, branch: Branch): string | null;
|
|
91
|
+
/**
|
|
92
|
+
* Returns every `DROP TABLE IF EXISTS` needed to fully remove a content type:
|
|
93
|
+
* the main `content_{slug}` table, its `fts_{slug}` virtual table, the
|
|
94
|
+
* `content_{slug}_drafts` mirror (when drafts are enabled), and each
|
|
95
|
+
* multi-relation junction table `rel_{slug}_{alias}` (+ its `_drafts` mirror).
|
|
96
|
+
*
|
|
97
|
+
* FTS triggers reference the main table; dropping the main table also drops
|
|
98
|
+
* its triggers in SQLite, but we drop the FTS table explicitly so no orphaned
|
|
99
|
+
* virtual table survives. Order: junction (children) → drafts → fts → main.
|
|
100
|
+
*/
|
|
101
|
+
export declare function generateDropTable(seed: Seed): string[];
|
|
102
|
+
/**
|
|
103
|
+
* Returns the `DROP COLUMN` statements for removing a single field.
|
|
104
|
+
*
|
|
105
|
+
* - For a multi-relation branch (no column on the parent table) it drops the
|
|
106
|
+
* junction table `rel_{slug}_{alias}` (+ `_drafts`) instead.
|
|
107
|
+
* - For a normal branch it drops the column from `content_{slug}` and, when
|
|
108
|
+
* drafts are enabled, the mirror column from `content_{slug}_drafts`.
|
|
109
|
+
* - For an orphan column (no matching branch in the definition) it falls back
|
|
110
|
+
* to a plain `ALTER TABLE … DROP COLUMN`, never touching drafts.
|
|
111
|
+
*
|
|
112
|
+
* Callers must rebuild FTS separately (planFtsRebuild) when the dropped column
|
|
113
|
+
* was searchable, since SQLite cannot ALTER an fts5 table's columns.
|
|
114
|
+
*/
|
|
115
|
+
export declare function generateDropColumn(seed: Seed, alias: string): string[];
|
|
116
|
+
/**
|
|
117
|
+
* Returns the `RENAME COLUMN` statements for renaming a field's alias while
|
|
118
|
+
* keeping the stable `branch.id`. Renames the column on `content_{slug}` and,
|
|
119
|
+
* when drafts are enabled, the mirror column on `content_{slug}_drafts`.
|
|
120
|
+
*
|
|
121
|
+
* For a multi-relation branch the alias is part of the junction table name, so
|
|
122
|
+
* the junction table (+ `_drafts`) is renamed instead of a column.
|
|
123
|
+
*
|
|
124
|
+
* Callers must rebuild FTS separately (planFtsRebuild) when the renamed column
|
|
125
|
+
* was searchable.
|
|
126
|
+
*/
|
|
127
|
+
export declare function generateRenameColumn(seed: Seed, from: string, to: string): string[];
|
|
128
|
+
/**
|
|
129
|
+
* Returns the add/copy/drop/rename statements that change a column's SQL type
|
|
130
|
+
* in place, preserving data via `CAST`. This is the documented simpler
|
|
131
|
+
* alternative to SQLite's full 12-step table rebuild.
|
|
132
|
+
*
|
|
133
|
+
* The `branch` passed in is the *target* definition (already carrying the new
|
|
134
|
+
* `type`); its SQL type is read from BRANCH_TYPE_SQL. Multi-relation branches
|
|
135
|
+
* have no column to retype and are rejected. Mirrors the change onto the drafts
|
|
136
|
+
* table when drafts are enabled. Callers rebuild FTS separately when needed.
|
|
137
|
+
*/
|
|
138
|
+
export declare function generateRetypeColumn(seed: Seed, branch: Branch): string[];
|
|
63
139
|
/**
|
|
64
140
|
* Deserializes a value read from the DB for the API response.
|
|
65
141
|
* 0/1 → boolean | Unix timestamp → ISO 8601 | JSON string → object
|
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,OAAO,KAAK,EACV,IAAI,EACJ,MAAM,EAKN,aAAa,EACb,kBAAkB,EACnB,MAAM,YAAY,CAAA;AAsCnB,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CAI5D;AAkED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CA0BtD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAyB5D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGpE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CAuBpD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAc1D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CAgCxD;AAID;;;;GAIG;AAGH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,aAAkB,GAAG,kBAAkB,CAqE5F;AAgHD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IACpC,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,OAAO,CAAA;CACd;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,YAAY,EAAE,CAiB7D;AAID;;;GAGG;AAEH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAwCrF;AAID;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAE/E;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAkBxE;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAM5E;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAapF;AAcD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CA0BtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAiBtE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBnF;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBzE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAqCzE"}
|