@beechcms/core 0.9.6 → 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.
- package/LICENSE +21 -0
- package/dist/auth/login-request.d.ts +16 -0
- package/dist/auth/login-request.d.ts.map +1 -0
- package/dist/auth/login-request.js +22 -0
- package/dist/automations-grammar-words.d.ts +2 -0
- package/dist/automations-grammar-words.d.ts.map +1 -0
- package/dist/automations-grammar-words.js +8 -0
- package/dist/automations.repository.interface.d.ts +19 -0
- package/dist/automations.repository.interface.d.ts.map +1 -0
- package/dist/automations.repository.interface.js +3 -0
- package/dist/automations.runner.interface.d.ts +10 -0
- package/dist/automations.runner.interface.d.ts.map +1 -0
- package/dist/automations.runner.interface.js +3 -0
- package/dist/automations.runner.stub.d.ts +5 -0
- package/dist/automations.runner.stub.d.ts.map +1 -0
- package/dist/automations.runner.stub.js +7 -0
- package/dist/automations.types.d.ts +75 -0
- package/dist/automations.types.d.ts.map +1 -0
- package/dist/automations.types.js +3 -0
- package/dist/clock.d.ts +25 -0
- package/dist/clock.d.ts.map +1 -0
- package/dist/clock.js +11 -0
- package/dist/content-scan.repository.d.ts +9 -0
- package/dist/content-scan.repository.d.ts.map +1 -0
- package/dist/content-scan.repository.js +3 -0
- package/dist/content.repository.d.ts +247 -0
- package/dist/content.repository.d.ts.map +1 -0
- package/dist/content.repository.js +59 -0
- package/dist/dashboard-layout/content-view.d.ts +4 -4
- package/dist/dashboard-layout/relation-avatar.d.ts +12 -0
- package/dist/dashboard-layout/relation-avatar.d.ts.map +1 -0
- package/dist/dashboard-layout/relation-avatar.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 +3 -0
- package/dist/define-seed.d.ts.map +1 -0
- package/dist/define-seed.js +5 -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/email.provider.d.ts +32 -0
- package/dist/email.provider.d.ts.map +1 -0
- package/dist/email.provider.js +4 -0
- package/dist/engine/ddl.d.ts +0 -23
- package/dist/engine/ddl.d.ts.map +1 -1
- package/dist/engine/ddl.js +3 -59
- package/dist/engine/localization.d.ts +127 -0
- package/dist/engine/localization.d.ts.map +1 -0
- package/dist/engine/localization.js +212 -0
- package/dist/engine/privacy.service.d.ts.map +1 -1
- package/dist/engine/query/search-support.d.ts +32 -0
- package/dist/engine/query/search-support.d.ts.map +1 -0
- package/dist/engine/query/search-support.js +34 -0
- package/dist/engine/query.d.ts.map +1 -1
- package/dist/engine/query.js +0 -12
- package/dist/engine/schema-fingerprint.d.ts +2 -0
- package/dist/engine/schema-fingerprint.d.ts.map +1 -1
- package/dist/engine/schema-fingerprint.js +2 -0
- package/dist/engine/seed-ddl.d.ts +1 -4
- package/dist/engine/seed-ddl.d.ts.map +1 -1
- package/dist/engine/seed-ddl.js +2 -17
- package/dist/engine/seed-registry.d.ts.map +1 -1
- package/dist/engine/seed-types-generator.d.ts +2 -17
- package/dist/engine/seed-types-generator.d.ts.map +1 -1
- package/dist/engine/seed-types-generator.js +3 -31
- package/dist/engine/seed-validation.d.ts.map +1 -1
- package/dist/engine/seed-validation.js +0 -10
- package/dist/engine.d.ts +144 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +741 -0
- 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/hooks.d.ts +29 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +3 -0
- package/dist/id-generator.d.ts +26 -0
- package/dist/id-generator.d.ts.map +1 -0
- package/dist/id-generator.js +12 -0
- package/dist/idempotency.repository.d.ts +13 -0
- package/dist/idempotency.repository.d.ts.map +1 -0
- package/dist/idempotency.repository.js +3 -0
- package/dist/kanban-position.repository.d.ts +19 -0
- package/dist/kanban-position.repository.d.ts.map +1 -0
- package/dist/kanban-position.repository.js +3 -0
- package/dist/kanban.d.ts +75 -0
- package/dist/kanban.d.ts.map +1 -0
- package/dist/kanban.js +82 -0
- 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 +61 -0
- package/dist/media.repository.d.ts.map +1 -0
- package/dist/media.repository.js +3 -0
- package/dist/notifications/notification-service.d.ts +2 -2
- package/dist/policies.d.ts +10 -0
- package/dist/policies.d.ts.map +1 -0
- package/dist/policies.js +33 -0
- package/dist/queue.interface.d.ts +39 -0
- package/dist/queue.interface.d.ts.map +1 -0
- package/dist/queue.interface.js +3 -0
- package/dist/queue.stub.d.ts +6 -0
- package/dist/queue.stub.d.ts.map +1 -0
- package/dist/queue.stub.js +8 -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 +11 -0
- package/dist/richtext-render.d.ts.map +1 -0
- package/dist/richtext-render.js +87 -0
- package/dist/richtext.d.ts +11 -0
- package/dist/richtext.d.ts.map +1 -0
- package/dist/richtext.js +13 -0
- package/dist/scheduler.interface.d.ts +4 -0
- package/dist/scheduler.interface.d.ts.map +1 -0
- package/dist/scheduler.interface.js +3 -0
- package/dist/scheduler.stub.d.ts +5 -0
- package/dist/scheduler.stub.d.ts.map +1 -0
- package/dist/scheduler.stub.js +7 -0
- package/dist/schema-mutator.d.ts +26 -0
- package/dist/schema-mutator.d.ts.map +1 -0
- package/dist/schema-mutator.js +3 -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 +145 -0
- package/dist/seed-layout.d.ts.map +1 -0
- package/dist/seed-layout.js +192 -0
- package/dist/seed-layout.repository.d.ts +22 -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 +71 -0
- package/dist/seed-registry.d.ts.map +1 -0
- package/dist/seed-registry.js +143 -0
- package/dist/seed-types-generator.d.ts +1 -1
- package/dist/seed-types-generator.d.ts.map +1 -1
- 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 +20 -0
- package/dist/seeds.d.ts.map +1 -0
- package/dist/seeds.js +21 -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 +18 -0
- package/dist/slug-utils.d.ts.map +1 -0
- package/dist/slug-utils.js +32 -0
- package/dist/storage.d.ts +46 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +3 -0
- package/dist/types.d.ts +243 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/validation.d.ts +50 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +660 -0
- 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/package.json +1 -1
- package/dist/content/content-lock.repository.d.ts +0 -63
- package/dist/content/content-lock.repository.d.ts.map +0 -1
- package/dist/content/content-lock.repository.js +0 -15
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
+
import { AUTOMATION_RESERVED_WORDS } from './automations-grammar-words.js';
|
|
4
|
+
import { sortSeedsByDependencies } from './seed-registry.js';
|
|
5
|
+
const BRANCH_ID_RE = /^br_[A-Za-z0-9]+$/;
|
|
6
|
+
/**
|
|
7
|
+
* Allowed charset for a branch alias. An alias becomes a raw SQL column name in
|
|
8
|
+
* CREATE TABLE / ADD COLUMN / CREATE INDEX and in SELECT/WHERE/ORDER BY clauses
|
|
9
|
+
* (see engine.ts), so it MUST be restricted to a safe identifier charset to
|
|
10
|
+
* prevent DDL/query injection. Exported so the seeds API rename route reuses the
|
|
11
|
+
* exact same guard — do not inline a divergent copy.
|
|
12
|
+
*/
|
|
13
|
+
export const BRANCH_ALIAS_RE = /^[a-z][a-zA-Z0-9_]*$/;
|
|
14
|
+
/**
|
|
15
|
+
* Pure, console-free, throw-free validation of a seed set.
|
|
16
|
+
* The single seed being created/edited should be validated in the context of
|
|
17
|
+
* the full active set: call validateSeedDefinitions([...otherActiveSeeds, edited]).
|
|
18
|
+
*/
|
|
19
|
+
export function validateSeedDefinitions(seeds) {
|
|
20
|
+
const result = [];
|
|
21
|
+
// ── Fatal 1: unknown relation targets ────────────────────────────────────
|
|
22
|
+
const slugSet = new Set(seeds.map(s => s.slug));
|
|
23
|
+
for (const seed of seeds) {
|
|
24
|
+
const messages = [];
|
|
25
|
+
for (const branch of seed.branches) {
|
|
26
|
+
if (branch.type === 'relation' && branch.targetSeed) {
|
|
27
|
+
if (!slugSet.has(branch.targetSeed)) {
|
|
28
|
+
messages.push(`branch '${branch.alias}' targets unknown seed '${branch.targetSeed}'`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (messages.length > 0)
|
|
33
|
+
result.push({ slug: seed.slug, messages, fatal: true });
|
|
34
|
+
}
|
|
35
|
+
// ── Fatal 2: multi-relation with SET NULL ────────────────────────────────
|
|
36
|
+
for (const seed of seeds) {
|
|
37
|
+
const messages = [];
|
|
38
|
+
for (const branch of seed.branches) {
|
|
39
|
+
if (branch.type === 'relation' && branch.multiple === true && branch.onDelete === 'SET NULL') {
|
|
40
|
+
messages.push(`Branch '${branch.alias}': multi-relations cannot use ON DELETE SET NULL. ` +
|
|
41
|
+
`Use 'CASCADE' or 'RESTRICT'.`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (messages.length > 0)
|
|
45
|
+
result.push({ slug: seed.slug, messages, fatal: true });
|
|
46
|
+
}
|
|
47
|
+
// ── Fatal 3: junction table name collisions / length ─────────────────────
|
|
48
|
+
{
|
|
49
|
+
const junctionNames = new Set();
|
|
50
|
+
for (const seed of seeds) {
|
|
51
|
+
const messages = [];
|
|
52
|
+
for (const branch of seed.branches) {
|
|
53
|
+
if (branch.type !== 'relation' || branch.multiple !== true)
|
|
54
|
+
continue;
|
|
55
|
+
const name = `rel_${seed.slug}_${branch.alias}`;
|
|
56
|
+
if (name.length > 256) {
|
|
57
|
+
messages.push(`Junction table name '${name}' exceeds 256 characters (${name.length})`);
|
|
58
|
+
}
|
|
59
|
+
if (junctionNames.has(name)) {
|
|
60
|
+
messages.push(`Junction table name collision: '${name}' already used by another seed/branch`);
|
|
61
|
+
}
|
|
62
|
+
junctionNames.add(name);
|
|
63
|
+
}
|
|
64
|
+
if (messages.length > 0)
|
|
65
|
+
result.push({ slug: seed.slug, messages, fatal: true });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// ── Fatal 4: dependency cycles ────────────────────────────────────────────
|
|
69
|
+
const hasUnknownTargets = result.some(e => e.fatal);
|
|
70
|
+
if (!hasUnknownTargets) {
|
|
71
|
+
try {
|
|
72
|
+
sortSeedsByDependencies(seeds);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
76
|
+
result.push({ slug: '<graph>', messages: [msg], fatal: true });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// ── Fatal 5: branch id format / uniqueness ───────────────────────────────
|
|
80
|
+
for (const seed of seeds) {
|
|
81
|
+
const messages = [];
|
|
82
|
+
const idsInSeed = new Set();
|
|
83
|
+
for (const branch of seed.branches) {
|
|
84
|
+
if (!branch.id || !BRANCH_ID_RE.test(branch.id)) {
|
|
85
|
+
messages.push(`branch '${branch.alias}' has invalid id '${branch.id}'. Expected format ^br_[A-Za-z0-9]+$.`);
|
|
86
|
+
}
|
|
87
|
+
else if (idsInSeed.has(branch.id)) {
|
|
88
|
+
messages.push(`duplicate branch id '${branch.id}'`);
|
|
89
|
+
}
|
|
90
|
+
if (branch.id)
|
|
91
|
+
idsInSeed.add(branch.id);
|
|
92
|
+
// Alias becomes a raw SQL column name — reject anything outside the safe
|
|
93
|
+
// identifier charset before it can reach DDL/query string interpolation.
|
|
94
|
+
if (typeof branch.alias !== 'string' || !BRANCH_ALIAS_RE.test(branch.alias)) {
|
|
95
|
+
messages.push(`branch alias '${branch.alias}' is invalid. Expected format ${BRANCH_ALIAS_RE.source} ` +
|
|
96
|
+
`(lowercase letter followed by alphanumeric characters or underscores).`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (messages.length > 0)
|
|
100
|
+
result.push({ slug: seed.slug, messages, fatal: true });
|
|
101
|
+
}
|
|
102
|
+
// ── Fatal 6: reserved aliases ─────────────────────────────────────────────
|
|
103
|
+
for (const seed of seeds) {
|
|
104
|
+
const messages = [];
|
|
105
|
+
for (const branch of seed.branches) {
|
|
106
|
+
if (AUTOMATION_RESERVED_WORDS.has(branch.alias)) {
|
|
107
|
+
messages.push(`branch '${branch.alias}' uses reserved alias. ` +
|
|
108
|
+
`This word is used by the automation template grammar.`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (messages.length > 0)
|
|
112
|
+
result.push({ slug: seed.slug, messages, fatal: true });
|
|
113
|
+
}
|
|
114
|
+
// ── Fatal 10: repeater sub-field constraints ─────────────────────────────
|
|
115
|
+
{
|
|
116
|
+
const REPEATER_DISALLOWED_SUBTYPES = new Set(['repeater', 'relation', 'file']);
|
|
117
|
+
for (const seed of seeds) {
|
|
118
|
+
const messages = [];
|
|
119
|
+
for (const branch of seed.branches) {
|
|
120
|
+
if (branch.type !== 'repeater')
|
|
121
|
+
continue;
|
|
122
|
+
const subIds = new Set();
|
|
123
|
+
const subAliases = new Set();
|
|
124
|
+
for (const sub of branch.fields ?? []) {
|
|
125
|
+
if (REPEATER_DISALLOWED_SUBTYPES.has(sub.type)) {
|
|
126
|
+
messages.push(`branch '${branch.alias}': sub-field '${sub.alias}' has disallowed type '${sub.type}'. ` +
|
|
127
|
+
`Repeater sub-fields cannot be 'repeater', 'relation', or 'file' in v1.`);
|
|
128
|
+
}
|
|
129
|
+
if (!sub.id || !BRANCH_ID_RE.test(sub.id)) {
|
|
130
|
+
messages.push(`branch '${branch.alias}': sub-field '${sub.alias}' has invalid id '${sub.id}'. ` +
|
|
131
|
+
`Expected format ^br_[A-Za-z0-9]+$.`);
|
|
132
|
+
}
|
|
133
|
+
else if (subIds.has(sub.id)) {
|
|
134
|
+
messages.push(`branch '${branch.alias}': duplicate sub-field id '${sub.id}'`);
|
|
135
|
+
}
|
|
136
|
+
if (sub.id)
|
|
137
|
+
subIds.add(sub.id);
|
|
138
|
+
if (typeof sub.alias !== 'string' || !BRANCH_ALIAS_RE.test(sub.alias)) {
|
|
139
|
+
messages.push(`branch '${branch.alias}': sub-field alias '${sub.alias}' is invalid. ` +
|
|
140
|
+
`Expected format ${BRANCH_ALIAS_RE.source}.`);
|
|
141
|
+
}
|
|
142
|
+
else if (subAliases.has(sub.alias)) {
|
|
143
|
+
messages.push(`branch '${branch.alias}': duplicate sub-field alias '${sub.alias}'`);
|
|
144
|
+
}
|
|
145
|
+
subAliases.add(sub.alias);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (messages.length > 0)
|
|
149
|
+
result.push({ slug: seed.slug, messages, fatal: true });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// ── Warning 7: duplicate slug ─────────────────────────────────────────────
|
|
153
|
+
{
|
|
154
|
+
const slugsSeen = new Set();
|
|
155
|
+
for (const seed of seeds) {
|
|
156
|
+
const messages = [];
|
|
157
|
+
if (slugsSeen.has(seed.slug)) {
|
|
158
|
+
messages.push(`duplicate slug "${seed.slug}" — each seed must have a unique slug`);
|
|
159
|
+
}
|
|
160
|
+
slugsSeen.add(seed.slug);
|
|
161
|
+
if (messages.length > 0)
|
|
162
|
+
result.push({ slug: seed.slug, messages, fatal: false });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// ── Warning 8: duplicate branch alias within a seed ───────────────────────
|
|
166
|
+
for (const seed of seeds) {
|
|
167
|
+
const messages = [];
|
|
168
|
+
const aliasesSeen = new Set();
|
|
169
|
+
for (const branch of seed.branches) {
|
|
170
|
+
if (aliasesSeen.has(branch.alias)) {
|
|
171
|
+
messages.push(`duplicate branch alias "${branch.alias}"`);
|
|
172
|
+
}
|
|
173
|
+
aliasesSeen.add(branch.alias);
|
|
174
|
+
}
|
|
175
|
+
if (messages.length > 0)
|
|
176
|
+
result.push({ slug: seed.slug, messages, fatal: false });
|
|
177
|
+
}
|
|
178
|
+
// ── Warning 9: displayNameAlias not in branches ───────────────────────────
|
|
179
|
+
for (const seed of seeds) {
|
|
180
|
+
const allAliases = new Set(seed.branches.map(b => b.alias));
|
|
181
|
+
if (!allAliases.has(seed.displayNameAlias)) {
|
|
182
|
+
result.push({
|
|
183
|
+
slug: seed.slug,
|
|
184
|
+
messages: [`displayNameAlias "${seed.displayNameAlias}" not found in branches`],
|
|
185
|
+
fatal: false,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// ── Fatal 11 / Warning 10: repeater cardinality bounds ───────────────────
|
|
190
|
+
{
|
|
191
|
+
for (const seed of seeds) {
|
|
192
|
+
const fatals = [];
|
|
193
|
+
const warnings = [];
|
|
194
|
+
for (const branch of seed.branches) {
|
|
195
|
+
const hasBounds = branch.minItems !== undefined || branch.maxItems !== undefined;
|
|
196
|
+
if (!hasBounds)
|
|
197
|
+
continue;
|
|
198
|
+
if (branch.type !== 'repeater') {
|
|
199
|
+
warnings.push(`branch '${branch.alias}': minItems/maxItems are ignored on type ` +
|
|
200
|
+
`'${branch.type}' (repeater-only).`);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
for (const [key, val] of [['minItems', branch.minItems], ['maxItems', branch.maxItems]]) {
|
|
204
|
+
if (val !== undefined && (!Number.isInteger(val) || val < 0)) {
|
|
205
|
+
fatals.push(`branch '${branch.alias}': ${key} must be a non-negative integer (got ${val}).`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (Number.isInteger(branch.minItems) && Number.isInteger(branch.maxItems) &&
|
|
209
|
+
branch.minItems > branch.maxItems) {
|
|
210
|
+
fatals.push(`branch '${branch.alias}': minItems (${branch.minItems}) must be <= maxItems (${branch.maxItems}).`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (fatals.length > 0)
|
|
214
|
+
result.push({ slug: seed.slug, messages: fatals, fatal: true });
|
|
215
|
+
if (warnings.length > 0)
|
|
216
|
+
result.push({ slug: seed.slug, messages: warnings, fatal: false });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
221
|
+
/** Convenience: true iff no fatal issues. */
|
|
222
|
+
export function isSeedSetValid(seeds) {
|
|
223
|
+
return validateSeedDefinitions(seeds).every(i => !i.fatal);
|
|
224
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Seed } from './types.js';
|
|
2
|
+
export interface SeedRecord {
|
|
3
|
+
slug: string;
|
|
4
|
+
definition: Seed;
|
|
5
|
+
status: 'active' | 'deleted';
|
|
6
|
+
source: 'code' | 'runtime';
|
|
7
|
+
createdAt: number;
|
|
8
|
+
updatedAt: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Persistence contract for runtime Seed definitions.
|
|
12
|
+
* Implemented by D1SeedRepository in apps/api/src/shared/seed.repository.d1.ts.
|
|
13
|
+
*
|
|
14
|
+
* `listActive()` returns only status='active' rows — this is what the registry is
|
|
15
|
+
* hydrated from. `getRegistryVersion` / `bumpRegistryVersion` back the multi-isolate
|
|
16
|
+
* cache token (see docs/Sprints/runtime-seeds/00-overview.md).
|
|
17
|
+
*/
|
|
18
|
+
export interface ISeedRepository {
|
|
19
|
+
/** All active seed definitions, ordered by created_at ASC. */
|
|
20
|
+
listActive(): Promise<Seed[]>;
|
|
21
|
+
/** Every row including soft-deleted ones (for admin/diff use). */
|
|
22
|
+
listAll(): Promise<SeedRecord[]>;
|
|
23
|
+
/** Single active-or-deleted record by slug, or null. */
|
|
24
|
+
get(slug: string): Promise<SeedRecord | null>;
|
|
25
|
+
/** Insert or replace a definition. Sets source on insert; preserves it on update unless given. */
|
|
26
|
+
upsert(slug: string, definition: Seed, source?: 'code' | 'runtime'): Promise<void>;
|
|
27
|
+
/** Soft-delete: set status='deleted'. Table is NOT dropped (additive-only). */
|
|
28
|
+
softDelete(slug: string): Promise<void>;
|
|
29
|
+
/** Hard-delete (sprint 06): permanently remove the `seeds` row. The caller is
|
|
30
|
+
* responsible for dropping the backing tables first via ISchemaMutator. */
|
|
31
|
+
hardDelete(slug: string): Promise<void>;
|
|
32
|
+
/** Current cache token. */
|
|
33
|
+
getRegistryVersion(): Promise<number>;
|
|
34
|
+
/** Atomically increment and return the new token. Call after any write. */
|
|
35
|
+
bumpRegistryVersion(): Promise<number>;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=seed.repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seed.repository.d.ts","sourceRoot":"","sources":["../src/seed.repository.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAEtC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,IAAI,CAAA;IAChB,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAA;IAC5B,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,8DAA8D;IAC9D,UAAU,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;IAC7B,kEAAkE;IAClE,OAAO,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAAA;IAChC,wDAAwD;IACxD,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAA;IAC7C,kGAAkG;IAClG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClF,+EAA+E;IAC/E,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACvC;gFAC4E;IAC5E,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACvC,2BAA2B;IAC3B,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;IACrC,2EAA2E;IAC3E,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;CACvC"}
|
package/dist/seeds.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seed Registry: configurazione degli schemi di contenuto.
|
|
3
|
+
* In v0.4.0 ogni Seed genera una tabella `content_{slug}` con colonne reali.
|
|
4
|
+
* `branch.alias` è il nome della colonna SQL.
|
|
5
|
+
*/
|
|
6
|
+
import type { Seed } from './types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Registro globale dei Seed.
|
|
9
|
+
* In produzione, questo viene popolato dalle definizioni dell'utente.
|
|
10
|
+
*/
|
|
11
|
+
export declare const SEED_REGISTRY: Record<string, Seed>;
|
|
12
|
+
/**
|
|
13
|
+
* Registra un set di Seed nel registro globale.
|
|
14
|
+
*/
|
|
15
|
+
export declare function registerSeeds(seeds: Seed[]): void;
|
|
16
|
+
/**
|
|
17
|
+
* Ritorna il Seed per lo slug dato, o null se non esiste.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getSeed(slug: string): Seed | null;
|
|
20
|
+
//# sourceMappingURL=seeds.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seeds.d.ts","sourceRoot":"","sources":["../src/seeds.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAEtC;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAM,CAAA;AAErD;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,QAI1C;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAEjD"}
|
package/dist/seeds.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
+
/**
|
|
4
|
+
* Registro globale dei Seed.
|
|
5
|
+
* In produzione, questo viene popolato dalle definizioni dell'utente.
|
|
6
|
+
*/
|
|
7
|
+
export const SEED_REGISTRY = {};
|
|
8
|
+
/**
|
|
9
|
+
* Registra un set di Seed nel registro globale.
|
|
10
|
+
*/
|
|
11
|
+
export function registerSeeds(seeds) {
|
|
12
|
+
seeds.forEach(seed => {
|
|
13
|
+
SEED_REGISTRY[seed.slug] = seed;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Ritorna il Seed per lo slug dato, o null se non esiste.
|
|
18
|
+
*/
|
|
19
|
+
export function getSeed(slug) {
|
|
20
|
+
return SEED_REGISTRY[slug] ?? null;
|
|
21
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface SiteSettings {
|
|
2
|
+
siteTitle: string;
|
|
3
|
+
defaultLanguage: string;
|
|
4
|
+
timezone: string;
|
|
5
|
+
currency: string;
|
|
6
|
+
companyName: string | null;
|
|
7
|
+
companyWebsite: string | null;
|
|
8
|
+
companyAbbreviation: string | null;
|
|
9
|
+
}
|
|
10
|
+
export interface ISiteSettingsRepository {
|
|
11
|
+
/** Returns all stored settings, applying sensible defaults for missing keys. */
|
|
12
|
+
getAll(): Promise<SiteSettings>;
|
|
13
|
+
/** Upserts the provided keys. Partial update — unspecified keys are untouched. */
|
|
14
|
+
setMany(values: Partial<SiteSettings>): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=site-settings.repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-settings.repository.d.ts","sourceRoot":"","sources":["../src/site-settings.repository.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,eAAe,EAAE,MAAM,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAA;CACnC;AAED,MAAM,WAAW,uBAAuB;IACtC,gFAAgF;IAChF,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC,CAAA;IAC/B,kFAAkF;IAClF,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACtD"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility per la gestione degli slug.
|
|
3
|
+
* Condivisa tra Dashboard e API pubbliche.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Converte una stringa in uno slug URL-safe.
|
|
7
|
+
* Limita la lunghezza a 15 caratteri come richiesto.
|
|
8
|
+
*/
|
|
9
|
+
export declare function slugify(value: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Genera uno slug da un input (es. titolo o nome) o un fallback UUID.
|
|
12
|
+
*/
|
|
13
|
+
export declare function generateEntrySlug(input: {
|
|
14
|
+
slug?: string;
|
|
15
|
+
title?: unknown;
|
|
16
|
+
name?: unknown;
|
|
17
|
+
}): string;
|
|
18
|
+
//# sourceMappingURL=slug-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slug-utils.d.ts","sourceRoot":"","sources":["../src/slug-utils.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH;;;GAGG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAU7C;AAGD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,CASnG"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
+
/**
|
|
4
|
+
* Utility per la gestione degli slug.
|
|
5
|
+
* Condivisa tra Dashboard e API pubbliche.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Converte una stringa in uno slug URL-safe.
|
|
9
|
+
* Limita la lunghezza a 15 caratteri come richiesto.
|
|
10
|
+
*/
|
|
11
|
+
export function slugify(value) {
|
|
12
|
+
return value
|
|
13
|
+
.normalize('NFKD') // Normalizza caratteri accentati
|
|
14
|
+
.replace(/[^\w\s-]/g, '') // Rimuove tutto ciò che non è alfanumerico
|
|
15
|
+
.trim()
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/[\s_-]+/g, '-') // Sostituisce spazi e underscore con trattino
|
|
18
|
+
.replace(/^-+|-+$/g, '') // Rimuove trattini all'inizio o alla fine
|
|
19
|
+
.slice(0, 15) // Limita a 15 caratteri
|
|
20
|
+
.replace(/-+$/g, ''); // Rimuove trattini rimasti alla fine dopo il taglio
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Genera uno slug da un input (es. titolo o nome) o un fallback UUID.
|
|
24
|
+
*/
|
|
25
|
+
export function generateEntrySlug(input) {
|
|
26
|
+
const candidate = (typeof input.slug === 'string' && input.slug) ||
|
|
27
|
+
(typeof input.title === 'string' && input.title) ||
|
|
28
|
+
(typeof input.name === 'string' && input.name) ||
|
|
29
|
+
Math.random().toString(36).slice(2, 10); // Fallback compatto se crypto non disponibile (o slice 8)
|
|
30
|
+
const normalized = slugify(candidate);
|
|
31
|
+
return normalized || Math.random().toString(36).slice(2, 10);
|
|
32
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export interface PutBucketOptions {
|
|
2
|
+
contentType?: string;
|
|
3
|
+
metadata?: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
export interface GetBucketResult {
|
|
6
|
+
body: ReadableStream | ArrayBuffer;
|
|
7
|
+
contentType?: string;
|
|
8
|
+
size: number;
|
|
9
|
+
metadata?: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
export interface PresignOptions {
|
|
12
|
+
/** Duration of the signed URL in seconds. */
|
|
13
|
+
expiresIn: number;
|
|
14
|
+
/** Content-Type bound in the signature (PUT). */
|
|
15
|
+
contentType?: string;
|
|
16
|
+
/** Expected Content-Length in bytes (PUT). */
|
|
17
|
+
contentLength?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface BeechBucket {
|
|
20
|
+
put(key: string, body: ArrayBuffer | Uint8Array | ReadableStream, options?: PutBucketOptions): Promise<void>;
|
|
21
|
+
get(key: string): Promise<GetBucketResult | null>;
|
|
22
|
+
delete(key: string): Promise<void>;
|
|
23
|
+
head(key: string): Promise<{
|
|
24
|
+
size: number;
|
|
25
|
+
contentType?: string;
|
|
26
|
+
metadata?: Record<string, string>;
|
|
27
|
+
} | null>;
|
|
28
|
+
getUrl(key: string): string;
|
|
29
|
+
getTotalSize(): Promise<number>;
|
|
30
|
+
list(options?: {
|
|
31
|
+
prefix?: string;
|
|
32
|
+
limit?: number;
|
|
33
|
+
cursor?: string;
|
|
34
|
+
}): Promise<{
|
|
35
|
+
objects: Array<{
|
|
36
|
+
key: string;
|
|
37
|
+
size: number;
|
|
38
|
+
}>;
|
|
39
|
+
cursor?: string;
|
|
40
|
+
}>;
|
|
41
|
+
/** Generates a signed URL for direct upload (PUT). */
|
|
42
|
+
presignPut(key: string, options: PresignOptions): Promise<string>;
|
|
43
|
+
/** Generates a signed URL for direct read (GET). */
|
|
44
|
+
presignGet(key: string, options: PresignOptions): Promise<string>;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,gBAAgB;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,cAAc,GAAG,WAAW,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,cAAc;IAC7B,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,GAAG,cAAc,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7G,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IAClD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IAC7G,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,KAAK,CAAC;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAElJ,sDAAsD;IACtD,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAElE,oDAAoD;IACpD,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACnE"}
|
package/dist/storage.js
ADDED