@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.
Files changed (141) hide show
  1. package/dist/auth/hash-provider.d.ts.map +1 -1
  2. package/dist/auth/hash-provider.js +2 -0
  3. package/dist/auth/password-reset-token.repository.d.ts.map +1 -1
  4. package/dist/auth/password-reset-token.repository.js +2 -0
  5. package/dist/auth/session.repository.d.ts.map +1 -1
  6. package/dist/auth/session.repository.js +2 -0
  7. package/dist/auth/token-service.d.ts +2 -0
  8. package/dist/auth/token-service.d.ts.map +1 -1
  9. package/dist/auth/token-service.js +2 -0
  10. package/dist/auth/user.repository.d.ts +4 -1
  11. package/dist/auth/user.repository.d.ts.map +1 -1
  12. package/dist/auth/user.repository.js +2 -0
  13. package/dist/automations-grammar-words.d.ts.map +1 -1
  14. package/dist/automations-grammar-words.js +2 -0
  15. package/dist/automations.repository.interface.d.ts.map +1 -1
  16. package/dist/automations.repository.interface.js +2 -0
  17. package/dist/automations.runner.interface.d.ts.map +1 -1
  18. package/dist/automations.runner.interface.js +2 -0
  19. package/dist/automations.runner.stub.d.ts.map +1 -1
  20. package/dist/automations.runner.stub.js +2 -0
  21. package/dist/automations.types.d.ts +2 -0
  22. package/dist/automations.types.d.ts.map +1 -1
  23. package/dist/automations.types.js +2 -0
  24. package/dist/clock.d.ts.map +1 -1
  25. package/dist/clock.js +2 -0
  26. package/dist/content-scan.repository.d.ts.map +1 -1
  27. package/dist/content-scan.repository.js +2 -0
  28. package/dist/content.repository.d.ts +62 -0
  29. package/dist/content.repository.d.ts.map +1 -1
  30. package/dist/content.repository.js +18 -0
  31. package/dist/dashboard-layout.d.ts +154 -0
  32. package/dist/dashboard-layout.d.ts.map +1 -0
  33. package/dist/dashboard-layout.js +186 -0
  34. package/dist/dashboard-layout.repository.d.ts +18 -0
  35. package/dist/dashboard-layout.repository.d.ts.map +1 -0
  36. package/dist/dashboard-layout.repository.js +3 -0
  37. package/dist/dashboard-permissions.d.ts +3 -0
  38. package/dist/dashboard-permissions.d.ts.map +1 -0
  39. package/dist/dashboard-permissions.js +11 -0
  40. package/dist/dashboard-scopes.d.ts +12 -0
  41. package/dist/dashboard-scopes.d.ts.map +1 -0
  42. package/dist/dashboard-scopes.js +30 -0
  43. package/dist/define-seed.d.ts.map +1 -1
  44. package/dist/define-seed.js +2 -0
  45. package/dist/demo-data.repository.d.ts +5 -0
  46. package/dist/demo-data.repository.d.ts.map +1 -0
  47. package/dist/demo-data.repository.js +3 -0
  48. package/dist/engine.d.ts +76 -0
  49. package/dist/engine.d.ts.map +1 -1
  50. package/dist/engine.js +259 -5
  51. package/dist/file-types.d.ts +13 -0
  52. package/dist/file-types.d.ts.map +1 -0
  53. package/dist/file-types.js +85 -0
  54. package/dist/id-generator.d.ts +7 -0
  55. package/dist/id-generator.d.ts.map +1 -1
  56. package/dist/id-generator.js +5 -0
  57. package/dist/idempotency.repository.d.ts.map +1 -1
  58. package/dist/idempotency.repository.js +2 -0
  59. package/dist/index.d.ts +16 -1
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +18 -1
  62. package/dist/layout-permissions.d.ts +3 -0
  63. package/dist/layout-permissions.d.ts.map +1 -0
  64. package/dist/layout-permissions.js +11 -0
  65. package/dist/media.repository.d.ts.map +1 -1
  66. package/dist/media.repository.js +2 -0
  67. package/dist/notifications/notification-service.d.ts.map +1 -1
  68. package/dist/notifications/notification-service.js +2 -0
  69. package/dist/notifications/notification.repository.d.ts.map +1 -1
  70. package/dist/notifications/notification.repository.js +2 -9
  71. package/dist/observability/activity-log.repository.d.ts.map +1 -1
  72. package/dist/observability/activity-log.repository.js +2 -0
  73. package/dist/observability/activity-logger.d.ts +2 -2
  74. package/dist/observability/activity-logger.d.ts.map +1 -1
  75. package/dist/observability/activity-logger.js +2 -11
  76. package/dist/observability/analytics.repository.d.ts.map +1 -1
  77. package/dist/observability/analytics.repository.js +2 -0
  78. package/dist/policies.d.ts.map +1 -1
  79. package/dist/policies.js +8 -3
  80. package/dist/rate-limit/rate-limiter.d.ts.map +1 -1
  81. package/dist/rate-limit/rate-limiter.js +2 -0
  82. package/dist/relations.d.ts +28 -0
  83. package/dist/relations.d.ts.map +1 -0
  84. package/dist/relations.js +25 -0
  85. package/dist/richtext-render.d.ts.map +1 -1
  86. package/dist/richtext-render.js +2 -0
  87. package/dist/richtext.d.ts.map +1 -1
  88. package/dist/richtext.js +2 -0
  89. package/dist/scheduler.interface.d.ts.map +1 -1
  90. package/dist/scheduler.interface.js +2 -0
  91. package/dist/scheduler.stub.d.ts.map +1 -1
  92. package/dist/scheduler.stub.js +2 -0
  93. package/dist/schema-mutator.d.ts +22 -0
  94. package/dist/schema-mutator.d.ts.map +1 -0
  95. package/dist/schema-mutator.js +3 -0
  96. package/dist/search/search.repository.d.ts.map +1 -1
  97. package/dist/search/search.repository.js +2 -0
  98. package/dist/seed-ddl-destructive.d.ts +40 -0
  99. package/dist/seed-ddl-destructive.d.ts.map +1 -0
  100. package/dist/seed-ddl-destructive.js +149 -0
  101. package/dist/seed-ddl.d.ts +40 -0
  102. package/dist/seed-ddl.d.ts.map +1 -0
  103. package/dist/seed-ddl.js +100 -0
  104. package/dist/seed-layout.d.ts +117 -0
  105. package/dist/seed-layout.d.ts.map +1 -0
  106. package/dist/seed-layout.js +169 -0
  107. package/dist/seed-layout.repository.d.ts +18 -0
  108. package/dist/seed-layout.repository.d.ts.map +1 -0
  109. package/dist/seed-layout.repository.js +3 -0
  110. package/dist/seed-registry.d.ts +23 -1
  111. package/dist/seed-registry.d.ts.map +1 -1
  112. package/dist/seed-registry.js +105 -0
  113. package/dist/seed-validation.d.ts +23 -0
  114. package/dist/seed-validation.d.ts.map +1 -0
  115. package/dist/seed-validation.js +224 -0
  116. package/dist/seed.repository.d.ts +37 -0
  117. package/dist/seed.repository.d.ts.map +1 -0
  118. package/dist/seed.repository.js +3 -0
  119. package/dist/seeds.d.ts.map +1 -1
  120. package/dist/seeds.js +2 -0
  121. package/dist/site-settings.repository.d.ts +16 -0
  122. package/dist/site-settings.repository.d.ts.map +1 -0
  123. package/dist/site-settings.repository.js +3 -0
  124. package/dist/slug-utils.d.ts.map +1 -1
  125. package/dist/slug-utils.js +2 -0
  126. package/dist/storage.d.ts +12 -38
  127. package/dist/storage.d.ts.map +1 -1
  128. package/dist/storage.js +2 -0
  129. package/dist/types.d.ts +81 -1
  130. package/dist/types.d.ts.map +1 -1
  131. package/dist/types.js +2 -0
  132. package/dist/validation.d.ts +23 -17
  133. package/dist/validation.d.ts.map +1 -1
  134. package/dist/validation.js +487 -381
  135. package/dist/webhook-validation.d.ts +3 -0
  136. package/dist/webhook-validation.d.ts.map +1 -0
  137. package/dist/webhook-validation.js +6 -0
  138. package/dist/widget/widget.repository.d.ts +32 -3
  139. package/dist/widget/widget.repository.d.ts.map +1 -1
  140. package/dist/widget/widget.repository.js +6 -1
  141. package/package.json +5 -4
package/dist/engine.js CHANGED
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
1
3
  const BRANCH_TYPE_SQL = {
2
4
  text: { sqlType: 'TEXT' },
3
5
  number: { sqlType: 'REAL' },
@@ -7,6 +9,8 @@ const BRANCH_TYPE_SQL = {
7
9
  richtext: { sqlType: 'TEXT' },
8
10
  file: { sqlType: 'TEXT' }, // URL singolo o JSON array di URL
9
11
  tags: { sqlType: 'TEXT' }, // JSON array di stringhe
12
+ relation: { sqlType: 'TEXT' }, // FK reference stored as TEXT (id of the target row)
13
+ repeater: { sqlType: 'TEXT' }, // JSON array di record (sub-branch alias -> value)
10
14
  };
11
15
  const SYSTEM_COLUMNS = new Set(['id', 'slug', 'status', 'created_at', 'updated_at']);
12
16
  // ---- Private helpers ----
@@ -21,7 +25,7 @@ function isValidColumn(seed, col) {
21
25
  return true;
22
26
  return seed.branches.some(b => b.alias === col);
23
27
  }
24
- function indexableSearchBranches(seed) {
28
+ export function indexableSearchBranches(seed) {
25
29
  return seed.branches.filter(b => (b.type === 'text' || b.type === 'richtext') && b.policies?.search !== false);
26
30
  }
27
31
  function isAssetListBranch(branch) {
@@ -69,6 +73,29 @@ function normalizeAssetListValue(rawValue) {
69
73
  }
70
74
  return [...new Set(normalized)];
71
75
  }
76
+ // ---- FK helpers ----
77
+ const DEFAULT_ON_DELETE_RULE = 'SET NULL';
78
+ /**
79
+ * Returns the trailing FK fragment for a single-value relation branch, or ''
80
+ * if the branch is not a relation. Throws when a single-value relation branch
81
+ * is missing `targetSeed` — that is a programmer error that must surface at
82
+ * DDL time rather than producing silently invalid SQL.
83
+ *
84
+ * Multi-relation branches (multiple: true) produce NO column on the parent
85
+ * table; they live in a dedicated junction table. Returns '' for them.
86
+ */
87
+ function buildForeignKeyClause(branch) {
88
+ if (branch.type !== 'relation')
89
+ return '';
90
+ // Many-to-many branches: no column on parent table — see generateJunctionTable()
91
+ if (branch.multiple === true)
92
+ return '';
93
+ if (!branch.targetSeed) {
94
+ throw new Error(`Branch "${branch.alias}" is of type 'relation' but has no targetSeed`);
95
+ }
96
+ const onDeleteRule = branch.onDelete ?? DEFAULT_ON_DELETE_RULE;
97
+ return ` REFERENCES content_${branch.targetSeed}(id) ON DELETE ${onDeleteRule}`;
98
+ }
72
99
  // ---- DDL Generators ----
73
100
  /**
74
101
  * Generates `CREATE TABLE IF NOT EXISTS content_{slug}` with system columns
@@ -83,12 +110,17 @@ export function generateCreateTable(seed) {
83
110
  ` status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'review', 'published', 'archived')),`,
84
111
  ];
85
112
  for (const branch of seed.branches) {
113
+ // Many-to-many relation branches produce no column on the parent table;
114
+ // they are represented by a dedicated junction table. See generateJunctionTable().
115
+ if (branch.type === 'relation' && branch.multiple === true)
116
+ continue;
86
117
  const { sqlType } = BRANCH_TYPE_SQL[branch.type];
87
118
  let col = ` ${branch.alias} ${sqlType}`;
88
119
  if (branch.requiredOnCreate)
89
120
  col += ' NOT NULL';
90
121
  if (branch.type === 'boolean')
91
122
  col += ` CHECK (${branch.alias} IN (0, 1))`;
123
+ col += buildForeignKeyClause(branch);
92
124
  lines.push(col + ',');
93
125
  }
94
126
  lines.push(` created_at INTEGER NOT NULL DEFAULT (unixepoch()),`);
@@ -112,6 +144,9 @@ export function generateDraftTable(seed) {
112
144
  ` REFERENCES ${mainTable}(id) ON DELETE CASCADE,`,
113
145
  ];
114
146
  for (const branch of seed.branches) {
147
+ // Many-to-many relation branches mirror in their own junction drafts table.
148
+ if (branch.type === 'relation' && branch.multiple === true)
149
+ continue;
115
150
  const { sqlType } = BRANCH_TYPE_SQL[branch.type];
116
151
  let col = ` ${branch.alias} ${sqlType}`;
117
152
  // boolean CHECK: in SQLite, NULL IN (0,1) → NULL, which passes the CHECK (only FALSE fails it)
@@ -129,7 +164,7 @@ export function generateDraftTable(seed) {
129
164
  */
130
165
  export function generateAddColumn(seed, branch) {
131
166
  const { sqlType } = BRANCH_TYPE_SQL[branch.type];
132
- return `ALTER TABLE ${tableName(seed)} ADD COLUMN ${branch.alias} ${sqlType};`;
167
+ return `ALTER TABLE ${tableName(seed)} ADD COLUMN ${branch.alias} ${sqlType}${buildForeignKeyClause(branch)};`;
133
168
  }
134
169
  /**
135
170
  * Generates B-tree indexes for status, created_at and every filterable Branch
@@ -143,9 +178,15 @@ export function generateIndexes(seed) {
143
178
  `CREATE INDEX IF NOT EXISTS idx_${slug}_created_at ON ${table}(created_at);`,
144
179
  ];
145
180
  for (const branch of seed.branches) {
146
- if (branch.policies?.filter === false)
181
+ // Multi-relation branches have no column on the parent table; their indexes
182
+ // live on the junction table. See generateJunctionIndexes().
183
+ if (branch.type === 'relation' && branch.multiple === true)
147
184
  continue;
148
- if (['text', 'number', 'date', 'boolean'].includes(branch.type)) {
185
+ const isRelation = branch.type === 'relation';
186
+ // Relation indexes are a system concern (JOIN performance) — bypass editorial filter policy
187
+ if (!isRelation && branch.policies?.filter === false)
188
+ continue;
189
+ if (['text', 'number', 'date', 'boolean', 'relation'].includes(branch.type)) {
149
190
  indexes.push(`CREATE INDEX IF NOT EXISTS idx_${slug}_${branch.alias} ON ${table}(${branch.alias});`);
150
191
  }
151
192
  }
@@ -380,7 +421,10 @@ export function getExpectedColumns(seed) {
380
421
  { name: 'id', sqlType: 'TEXT', notNull: true, isPk: true },
381
422
  { name: 'slug', sqlType: 'TEXT', notNull: true, isPk: false },
382
423
  { name: 'status', sqlType: 'TEXT', notNull: true, isPk: false },
383
- ...seed.branches.map(b => ({
424
+ // Multi-relation branches produce no column on the parent table.
425
+ ...seed.branches
426
+ .filter(b => !(b.type === 'relation' && b.multiple === true))
427
+ .map(b => ({
384
428
  name: b.alias,
385
429
  sqlType: BRANCH_TYPE_SQL[b.type].sqlType,
386
430
  notNull: b.requiredOnCreate ?? false,
@@ -426,15 +470,225 @@ export function serializeForDb(branch, value) {
426
470
  return Array.isArray(value) ? JSON.stringify(value) : typeof value === 'string' ? value : null;
427
471
  }
428
472
  return typeof value === 'string' ? value : null;
473
+ case 'repeater':
474
+ // Non-array input is rejected by serializing as an empty list — validation.ts
475
+ // is responsible for ever letting a non-array repeater value reach here.
476
+ return JSON.stringify(Array.isArray(value) ? value : []);
429
477
  default:
430
478
  return typeof value === 'string' ? value : typeof value === 'number' ? value : null;
431
479
  }
432
480
  }
481
+ // ---- Junction table DDL (many-to-many relations, Sprint 5) ----
482
+ /**
483
+ * Returns the junction table name for a many-to-many relation branch.
484
+ * Format: `rel_<seedSlug>_<branchAlias>`.
485
+ */
486
+ export function junctionTableName(seedSlug, branchAlias) {
487
+ return `rel_${seedSlug}_${branchAlias}`;
488
+ }
489
+ /**
490
+ * Generates `CREATE TABLE IF NOT EXISTS rel_<seed>_<alias>` for a multi-relation
491
+ * branch. The parent FK always cascades (parent lifecycle owns the edge);
492
+ * the target FK uses `branch.onDelete` (default CASCADE).
493
+ *
494
+ * @throws {Error} when called on a non-multi-relation branch
495
+ * @throws {Error} when `branch.targetSeed` is missing
496
+ */
497
+ export function generateJunctionTable(seed, branch) {
498
+ if (branch.type !== 'relation' || branch.multiple !== true) {
499
+ throw new Error(`Branch "${branch.alias}" is not a multi-relation`);
500
+ }
501
+ if (!branch.targetSeed) {
502
+ throw new Error(`Branch "${branch.alias}" has no targetSeed`);
503
+ }
504
+ const onDeleteRule = branch.onDelete ?? 'CASCADE';
505
+ const table = junctionTableName(seed.slug, branch.alias);
506
+ return [
507
+ `CREATE TABLE IF NOT EXISTS ${table} (`,
508
+ ` parent_id TEXT NOT NULL REFERENCES content_${seed.slug}(id) ON DELETE CASCADE,`,
509
+ ` target_id TEXT NOT NULL REFERENCES content_${branch.targetSeed}(id) ON DELETE ${onDeleteRule},`,
510
+ ` position INTEGER NOT NULL DEFAULT 0,`,
511
+ ` created_at INTEGER NOT NULL DEFAULT (unixepoch()),`,
512
+ ` PRIMARY KEY (parent_id, target_id)`,
513
+ `);`,
514
+ ].join('\n');
515
+ }
516
+ /**
517
+ * Generates the two B-tree indexes for a junction table:
518
+ * one on `parent_id` (list entries by parent) and one on `target_id`
519
+ * (cascade checks from target side).
520
+ */
521
+ export function generateJunctionIndexes(seed, branch) {
522
+ const table = junctionTableName(seed.slug, branch.alias);
523
+ return [
524
+ `CREATE INDEX IF NOT EXISTS idx_${table}_parent ON ${table}(parent_id);`,
525
+ `CREATE INDEX IF NOT EXISTS idx_${table}_target ON ${table}(target_id);`,
526
+ ];
527
+ }
528
+ /**
529
+ * Generates the drafts junction table `rel_<seed>_<alias>_drafts` for a
530
+ * many-to-many relation branch. Note: no FK on `target_id` — targets may be
531
+ * deleted between draft save and publish; the FK would be wrong here.
532
+ * Returns null when `seed.allowDrafts` is falsy.
533
+ */
534
+ export function generateJunctionDraftTable(seed, branch) {
535
+ if (!seed.allowDrafts)
536
+ return null;
537
+ if (branch.type !== 'relation' || branch.multiple !== true)
538
+ return null;
539
+ const table = `${junctionTableName(seed.slug, branch.alias)}_drafts`;
540
+ const mainDraftTable = `content_${seed.slug}_drafts`;
541
+ return [
542
+ `CREATE TABLE IF NOT EXISTS ${table} (`,
543
+ ` entry_id TEXT NOT NULL REFERENCES ${mainDraftTable}(entry_id) ON DELETE CASCADE,`,
544
+ ` target_id TEXT NOT NULL,`,
545
+ ` position INTEGER NOT NULL DEFAULT 0,`,
546
+ ` PRIMARY KEY (entry_id, target_id)`,
547
+ `);`,
548
+ ].join('\n');
549
+ }
550
+ // ---- Destructive DDL Generators (Sprint 06 — Danger Zone) ----
551
+ //
552
+ // Every generator below emits IRREVERSIBLE, data-destroying SQL. They are pure
553
+ // string builders (no DB access); the actual execution goes through
554
+ // ISchemaMutator's dedicated destructive methods (dropTable/dropColumn/
555
+ // renameColumn/execDestructive), which re-validate every identifier before
556
+ // interpolation. These statements MUST NEVER be passed to execDdl (additive-only).
557
+ function draftTableName(seed) {
558
+ return `content_${seed.slug}_drafts`;
559
+ }
560
+ /**
561
+ * Returns every `DROP TABLE IF EXISTS` needed to fully remove a content type:
562
+ * the main `content_{slug}` table, its `fts_{slug}` virtual table, the
563
+ * `content_{slug}_drafts` mirror (when drafts are enabled), and each
564
+ * multi-relation junction table `rel_{slug}_{alias}` (+ its `_drafts` mirror).
565
+ *
566
+ * FTS triggers reference the main table; dropping the main table also drops
567
+ * its triggers in SQLite, but we drop the FTS table explicitly so no orphaned
568
+ * virtual table survives. Order: junction (children) → drafts → fts → main.
569
+ */
570
+ export function generateDropTable(seed) {
571
+ const stmts = [];
572
+ // Junction tables first (they FK-reference the main/draft tables).
573
+ for (const branch of seed.branches) {
574
+ if (branch.type !== 'relation' || branch.multiple !== true)
575
+ continue;
576
+ const jt = junctionTableName(seed.slug, branch.alias);
577
+ stmts.push(`DROP TABLE IF EXISTS ${jt}_drafts;`);
578
+ stmts.push(`DROP TABLE IF EXISTS ${jt};`);
579
+ }
580
+ if (seed.allowDrafts) {
581
+ stmts.push(`DROP TABLE IF EXISTS ${draftTableName(seed)};`);
582
+ }
583
+ // FTS triggers + virtual table.
584
+ if (indexableSearchBranches(seed).length > 0) {
585
+ const slug = seed.slug;
586
+ stmts.push(`DROP TRIGGER IF EXISTS fts_${slug}_insert;`);
587
+ stmts.push(`DROP TRIGGER IF EXISTS fts_${slug}_update;`);
588
+ stmts.push(`DROP TRIGGER IF EXISTS fts_${slug}_delete;`);
589
+ stmts.push(`DROP TABLE IF EXISTS ${ftsTableName(seed)};`);
590
+ }
591
+ stmts.push(`DROP TABLE IF EXISTS ${tableName(seed)};`);
592
+ return stmts;
593
+ }
594
+ /**
595
+ * Returns the `DROP COLUMN` statements for removing a single field.
596
+ *
597
+ * - For a multi-relation branch (no column on the parent table) it drops the
598
+ * junction table `rel_{slug}_{alias}` (+ `_drafts`) instead.
599
+ * - For a normal branch it drops the column from `content_{slug}` and, when
600
+ * drafts are enabled, the mirror column from `content_{slug}_drafts`.
601
+ * - For an orphan column (no matching branch in the definition) it falls back
602
+ * to a plain `ALTER TABLE … DROP COLUMN`, never touching drafts.
603
+ *
604
+ * Callers must rebuild FTS separately (planFtsRebuild) when the dropped column
605
+ * was searchable, since SQLite cannot ALTER an fts5 table's columns.
606
+ */
607
+ export function generateDropColumn(seed, alias) {
608
+ const branch = seed.branches.find(b => b.alias === alias);
609
+ if (branch && branch.type === 'relation' && branch.multiple === true) {
610
+ const jt = junctionTableName(seed.slug, alias);
611
+ return [
612
+ `DROP TABLE IF EXISTS ${jt}_drafts;`,
613
+ `DROP TABLE IF EXISTS ${jt};`,
614
+ ];
615
+ }
616
+ const stmts = [`ALTER TABLE ${tableName(seed)} DROP COLUMN ${alias};`];
617
+ // Drop the mirror column from the drafts table when the branch lives there.
618
+ if (branch && seed.allowDrafts) {
619
+ stmts.push(`ALTER TABLE ${draftTableName(seed)} DROP COLUMN ${alias};`);
620
+ }
621
+ return stmts;
622
+ }
623
+ /**
624
+ * Returns the `RENAME COLUMN` statements for renaming a field's alias while
625
+ * keeping the stable `branch.id`. Renames the column on `content_{slug}` and,
626
+ * when drafts are enabled, the mirror column on `content_{slug}_drafts`.
627
+ *
628
+ * For a multi-relation branch the alias is part of the junction table name, so
629
+ * the junction table (+ `_drafts`) is renamed instead of a column.
630
+ *
631
+ * Callers must rebuild FTS separately (planFtsRebuild) when the renamed column
632
+ * was searchable.
633
+ */
634
+ export function generateRenameColumn(seed, from, to) {
635
+ const branch = seed.branches.find(b => b.alias === from);
636
+ if (branch && branch.type === 'relation' && branch.multiple === true) {
637
+ const oldJt = junctionTableName(seed.slug, from);
638
+ const newJt = junctionTableName(seed.slug, to);
639
+ const stmts = [`ALTER TABLE ${oldJt} RENAME TO ${newJt};`];
640
+ if (seed.allowDrafts) {
641
+ stmts.push(`ALTER TABLE ${oldJt}_drafts RENAME TO ${newJt}_drafts;`);
642
+ }
643
+ return stmts;
644
+ }
645
+ const stmts = [`ALTER TABLE ${tableName(seed)} RENAME COLUMN ${from} TO ${to};`];
646
+ if (branch && seed.allowDrafts) {
647
+ stmts.push(`ALTER TABLE ${draftTableName(seed)} RENAME COLUMN ${from} TO ${to};`);
648
+ }
649
+ return stmts;
650
+ }
651
+ /**
652
+ * Returns the add/copy/drop/rename statements that change a column's SQL type
653
+ * in place, preserving data via `CAST`. This is the documented simpler
654
+ * alternative to SQLite's full 12-step table rebuild.
655
+ *
656
+ * The `branch` passed in is the *target* definition (already carrying the new
657
+ * `type`); its SQL type is read from BRANCH_TYPE_SQL. Multi-relation branches
658
+ * have no column to retype and are rejected. Mirrors the change onto the drafts
659
+ * table when drafts are enabled. Callers rebuild FTS separately when needed.
660
+ */
661
+ export function generateRetypeColumn(seed, branch) {
662
+ if (branch.type === 'relation' && branch.multiple === true) {
663
+ throw new Error(`Branch "${branch.alias}" is a multi-relation and has no column to retype`);
664
+ }
665
+ const { sqlType } = BRANCH_TYPE_SQL[branch.type];
666
+ const alias = branch.alias;
667
+ const tmp = `__retype_${alias}`;
668
+ const rebuild = (table) => [
669
+ `ALTER TABLE ${table} ADD COLUMN ${tmp} ${sqlType};`,
670
+ `UPDATE ${table} SET ${tmp} = CAST(${alias} AS ${sqlType});`,
671
+ `ALTER TABLE ${table} DROP COLUMN ${alias};`,
672
+ `ALTER TABLE ${table} RENAME COLUMN ${tmp} TO ${alias};`,
673
+ ];
674
+ const stmts = rebuild(tableName(seed));
675
+ if (seed.allowDrafts)
676
+ stmts.push(...rebuild(draftTableName(seed)));
677
+ return stmts;
678
+ }
433
679
  /**
434
680
  * Deserializes a value read from the DB for the API response.
435
681
  * 0/1 → boolean | Unix timestamp → ISO 8601 | JSON string → object
436
682
  */
437
683
  export function deserializeFromDb(branch, value) {
684
+ // Repeater columns deserialize to [] (never null) — drafts and rows that
685
+ // predate the column being added carry NULL, which is an empty list of items.
686
+ if (branch.type === 'repeater') {
687
+ if (typeof value !== 'string' || value.length === 0)
688
+ return [];
689
+ const parsed = parseJsonSafe(value);
690
+ return Array.isArray(parsed) ? parsed : [];
691
+ }
438
692
  if (value === null || value === undefined)
439
693
  return null;
440
694
  switch (branch.type) {
@@ -0,0 +1,13 @@
1
+ export type FileAccept = 'image' | 'document' | 'any';
2
+ export declare const IMAGE_EXTENSIONS: Set<string>;
3
+ export declare const BLOCKED_IMAGE_EXTENSIONS: Set<string>;
4
+ export declare const DOCUMENT_EXTENSIONS: Set<string>;
5
+ export declare const ARCHIVE_EXTENSIONS: Set<string>;
6
+ export declare const IMAGE_MIME_TYPES: readonly ["image/jpeg", "image/png", "image/gif", "image/webp", "image/avif", "image/bmp", "image/x-icon", "image/vnd.microsoft.icon"];
7
+ export declare const BLOCKED_IMAGE_MIME_TYPES: readonly ["image/svg+xml", "image/svg"];
8
+ export declare const DOCUMENT_MIME_TYPES: readonly ["application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation", "text/plain", "text/csv", "text/markdown"];
9
+ export declare const ARCHIVE_MIME_TYPES: readonly ["application/zip", "application/x-zip-compressed", "application/x-7z-compressed", "application/x-tar", "application/gzip", "application/json"];
10
+ export declare function extensionFromUrl(url: string): string | null;
11
+ export declare function isExtensionAccepted(ext: string | null, accept: FileAccept): boolean;
12
+ export declare function isMimeAccepted(mime: string, accept: FileAccept): boolean;
13
+ //# sourceMappingURL=file-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-types.d.ts","sourceRoot":"","sources":["../src/file-types.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,KAAK,CAAA;AAKrD,eAAO,MAAM,gBAAgB,aAAuE,CAAA;AACpG,eAAO,MAAM,wBAAwB,aAA2B,CAAA;AAChE,eAAO,MAAM,mBAAmB,aAAoF,CAAA;AACpH,eAAO,MAAM,kBAAkB,aAA8C,CAAA;AAI7E,eAAO,MAAM,gBAAgB,wIASnB,CAAA;AAGV,eAAO,MAAM,wBAAwB,yCAA0C,CAAA;AAC/E,eAAO,MAAM,mBAAmB,yXAWtB,CAAA;AACV,eAAO,MAAM,kBAAkB,0JAOrB,CAAA;AAEV,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS3D;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAMnF;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAcxE"}
@@ -0,0 +1,85 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ // SVG is intentionally excluded: SVG can embed <script> tags and is rendered
4
+ // inline by browsers when served as image/svg+xml on a same-origin route,
5
+ // creating a stored XSS vector (see security audit finding #4).
6
+ export const IMAGE_EXTENSIONS = new Set(['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'bmp', 'ico']);
7
+ export const BLOCKED_IMAGE_EXTENSIONS = new Set(['svg', 'svgz']);
8
+ export const DOCUMENT_EXTENSIONS = new Set(['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'csv', 'md']);
9
+ export const ARCHIVE_EXTENSIONS = new Set(['zip', '7z', 'tar', 'gz', 'json']);
10
+ // Explicitly enumerate safe image MIME types instead of using the wildcard
11
+ // `image/*` prefix, which would permit `image/svg+xml` and other XSS vectors.
12
+ export const IMAGE_MIME_TYPES = [
13
+ 'image/jpeg',
14
+ 'image/png',
15
+ 'image/gif',
16
+ 'image/webp',
17
+ 'image/avif',
18
+ 'image/bmp',
19
+ 'image/x-icon',
20
+ 'image/vnd.microsoft.icon',
21
+ ];
22
+ // Blocked image MIME types — listed explicitly so upload code can reject them
23
+ // with a clear error rather than a generic "file type not allowed".
24
+ export const BLOCKED_IMAGE_MIME_TYPES = ['image/svg+xml', 'image/svg'];
25
+ export const DOCUMENT_MIME_TYPES = [
26
+ 'application/pdf',
27
+ 'application/msword',
28
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
29
+ 'application/vnd.ms-excel',
30
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
31
+ 'application/vnd.ms-powerpoint',
32
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
33
+ 'text/plain',
34
+ 'text/csv',
35
+ 'text/markdown',
36
+ ];
37
+ export const ARCHIVE_MIME_TYPES = [
38
+ 'application/zip',
39
+ 'application/x-zip-compressed',
40
+ 'application/x-7z-compressed',
41
+ 'application/x-tar',
42
+ 'application/gzip',
43
+ 'application/json',
44
+ ];
45
+ export function extensionFromUrl(url) {
46
+ try {
47
+ const path = new URL(url).pathname;
48
+ const dot = path.lastIndexOf('.');
49
+ if (dot < 0 || dot === path.length - 1)
50
+ return null;
51
+ return path.slice(dot + 1).toLowerCase();
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ }
57
+ export function isExtensionAccepted(ext, accept) {
58
+ if (accept === 'any')
59
+ return true;
60
+ if (ext == null)
61
+ return false;
62
+ if (accept === 'image')
63
+ return IMAGE_EXTENSIONS.has(ext);
64
+ if (accept === 'document')
65
+ return DOCUMENT_EXTENSIONS.has(ext);
66
+ return false;
67
+ }
68
+ export function isMimeAccepted(mime, accept) {
69
+ // Normalise: strip parameters (e.g. "image/jpeg; charset=utf-8" → "image/jpeg")
70
+ const normalised = mime.split(';')[0].trim().toLowerCase();
71
+ // Unconditionally reject SVG regardless of accept mode — SVG can carry
72
+ // embedded scripts and is rendered by browsers when served inline.
73
+ if (BLOCKED_IMAGE_MIME_TYPES.includes(normalised))
74
+ return false;
75
+ if (accept === 'any') {
76
+ return IMAGE_MIME_TYPES.includes(normalised)
77
+ || DOCUMENT_MIME_TYPES.includes(normalised)
78
+ || ARCHIVE_MIME_TYPES.includes(normalised);
79
+ }
80
+ if (accept === 'image')
81
+ return IMAGE_MIME_TYPES.includes(normalised);
82
+ if (accept === 'document')
83
+ return DOCUMENT_MIME_TYPES.includes(normalised);
84
+ return false;
85
+ }
@@ -10,6 +10,13 @@ export interface IIdGenerator {
10
10
  * Test implementations return deterministic values for snapshot assertions.
11
11
  */
12
12
  uuid(): string;
13
+ /**
14
+ * Returns true when `value` has the exact shape produced by `uuid()`.
15
+ * The ONLY place in the codebase that knows the id format. Never inline
16
+ * a regex; always go through this method when validating a relation id,
17
+ * a route param, or any user-supplied id.
18
+ */
19
+ isValid(value: unknown): value is string;
13
20
  }
14
21
  /**
15
22
  * Production singleton. Delegates to the host runtime CSPRNG-backed UUID
@@ -1 +1 @@
1
- {"version":3,"file":"id-generator.d.ts","sourceRoot":"","sources":["../src/id-generator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,IAAI,IAAI,MAAM,CAAA;CACf;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,YAE/B,CAAA"}
1
+ {"version":3,"file":"id-generator.d.ts","sourceRoot":"","sources":["../src/id-generator.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,IAAI,IAAI,MAAM,CAAA;IAEd;;;;;OAKG;IACH,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAA;CACzC;AAMD;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,YAI/B,CAAA"}
@@ -1,7 +1,12 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ // Internal — never exported. Callers depend on the interface, not the shape.
4
+ const SYSTEM_ID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1
5
  /**
2
6
  * Production singleton. Delegates to the host runtime CSPRNG-backed UUID
3
7
  * generator. Stateless and therefore safe to share across requests.
4
8
  */
5
9
  export const SystemIdGenerator = {
6
10
  uuid: () => crypto.randomUUID(),
11
+ isValid: (value) => typeof value === 'string' && SYSTEM_ID_REGEX.test(value),
7
12
  };
@@ -1 +1 @@
1
- {"version":3,"file":"idempotency.repository.d.ts","sourceRoot":"","sources":["../src/idempotency.repository.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,EAAE,MAAM,CAAA;IACtB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAA;IACtD,KAAK,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/C,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACpC"}
1
+ {"version":3,"file":"idempotency.repository.d.ts","sourceRoot":"","sources":["../src/idempotency.repository.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,EAAE,MAAM,CAAA;IACtB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAA;IACtD,KAAK,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/C,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACpC"}
@@ -1 +1,3 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
1
3
  export {};
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  * @module @beechcms/core
9
9
  */
10
10
  export * from './types.js';
11
+ export * from './file-types.js';
11
12
  export * from './define-seed.js';
12
13
  export * from './seeds.js';
13
14
  export * from './engine.js';
@@ -41,7 +42,21 @@ export * from './automations.types.js';
41
42
  export * from './automations-grammar-words.js';
42
43
  export * from './automations.runner.interface.js';
43
44
  export * from './automations.repository.interface.js';
44
- export * from './automations.runner.stub.js';
45
45
  export * from './scheduler.interface.js';
46
46
  export * from './scheduler.stub.js';
47
+ export * from './webhook-validation.js';
48
+ export * from './relations.js';
49
+ export * from './site-settings.repository.js';
50
+ export * from './demo-data.repository.js';
51
+ export * from './seed-layout.js';
52
+ export * from './seed-layout.repository.js';
53
+ export * from './layout-permissions.js';
54
+ export * from './dashboard-layout.js';
55
+ export * from './dashboard-scopes.js';
56
+ export * from './dashboard-permissions.js';
57
+ export * from './dashboard-layout.repository.js';
58
+ export * from './seed.repository.js';
59
+ export * from './seed-validation.js';
60
+ export * from './seed-ddl.js';
61
+ export * from './schema-mutator.js';
47
62
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,cAAc,YAAY,CAAA;AAC1B,cAAc,kBAAkB,CAAA;AAChC,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,yBAAyB,CAAA;AACvC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,uBAAuB,CAAA;AACrC,cAAc,cAAc,CAAA;AAC5B,cAAc,eAAe,CAAA;AAC7B,cAAc,yBAAyB,CAAA;AACvC,cAAc,yBAAyB,CAAA;AACvC,cAAc,2BAA2B,CAAA;AACzC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,2CAA2C,CAAA;AACzD,cAAc,8BAA8B,CAAA;AAC5C,cAAc,oCAAoC,CAAA;AAClD,cAAc,4CAA4C,CAAA;AAC1D,cAAc,yCAAyC,CAAA;AACvD,cAAc,4CAA4C,CAAA;AAC1D,cAAc,yCAAyC,CAAA;AACvD,cAAc,+BAA+B,CAAA;AAC7C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,YAAY,CAAA;AAC1B,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,cAAc,wBAAwB,CAAA;AACtC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,mCAAmC,CAAA;AACjD,cAAc,uCAAuC,CAAA;AACrD,cAAc,8BAA8B,CAAA;AAC5C,cAAc,0BAA0B,CAAA;AACxC,cAAc,qBAAqB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,kBAAkB,CAAA;AAChC,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,yBAAyB,CAAA;AACvC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,uBAAuB,CAAA;AACrC,cAAc,cAAc,CAAA;AAC5B,cAAc,eAAe,CAAA;AAC7B,cAAc,yBAAyB,CAAA;AACvC,cAAc,yBAAyB,CAAA;AACvC,cAAc,2BAA2B,CAAA;AACzC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,2CAA2C,CAAA;AACzD,cAAc,8BAA8B,CAAA;AAC5C,cAAc,oCAAoC,CAAA;AAClD,cAAc,4CAA4C,CAAA;AAC1D,cAAc,yCAAyC,CAAA;AACvD,cAAc,4CAA4C,CAAA;AAC1D,cAAc,yCAAyC,CAAA;AACvD,cAAc,+BAA+B,CAAA;AAC7C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,YAAY,CAAA;AAC1B,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,cAAc,wBAAwB,CAAA;AACtC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,mCAAmC,CAAA;AACjD,cAAc,uCAAuC,CAAA;AACrD,cAAc,0BAA0B,CAAA;AACxC,cAAc,qBAAqB,CAAA;AACnC,cAAc,yBAAyB,CAAA;AACvC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,+BAA+B,CAAA;AAC7C,cAAc,2BAA2B,CAAA;AACzC,cAAc,kBAAkB,CAAA;AAChC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,kCAAkC,CAAA;AAChD,cAAc,sBAAsB,CAAA;AACpC,cAAc,sBAAsB,CAAA;AACpC,cAAc,eAAe,CAAA;AAC7B,cAAc,qBAAqB,CAAA"}
package/dist/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
1
3
  /**
2
4
  * @beechcms/core - Botanical Engine
3
5
  *
@@ -8,6 +10,7 @@
8
10
  * @module @beechcms/core
9
11
  */
10
12
  export * from './types.js';
13
+ export * from './file-types.js';
11
14
  export * from './define-seed.js';
12
15
  export * from './seeds.js';
13
16
  export * from './engine.js';
@@ -41,6 +44,20 @@ export * from './automations.types.js';
41
44
  export * from './automations-grammar-words.js';
42
45
  export * from './automations.runner.interface.js';
43
46
  export * from './automations.repository.interface.js';
44
- export * from './automations.runner.stub.js';
45
47
  export * from './scheduler.interface.js';
46
48
  export * from './scheduler.stub.js';
49
+ export * from './webhook-validation.js';
50
+ export * from './relations.js';
51
+ export * from './site-settings.repository.js';
52
+ export * from './demo-data.repository.js';
53
+ export * from './seed-layout.js';
54
+ export * from './seed-layout.repository.js';
55
+ export * from './layout-permissions.js';
56
+ export * from './dashboard-layout.js';
57
+ export * from './dashboard-scopes.js';
58
+ export * from './dashboard-permissions.js';
59
+ export * from './dashboard-layout.repository.js';
60
+ export * from './seed.repository.js';
61
+ export * from './seed-validation.js';
62
+ export * from './seed-ddl.js';
63
+ export * from './schema-mutator.js';
@@ -0,0 +1,3 @@
1
+ export declare const ROLES_ALLOWED_TO_EDIT_LAYOUT: ReadonlyArray<string>;
2
+ export declare function canEditLayout(role: string | undefined | null): boolean;
3
+ //# sourceMappingURL=layout-permissions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout-permissions.d.ts","sourceRoot":"","sources":["../src/layout-permissions.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,4BAA4B,EAAE,aAAa,CAAC,MAAM,CAAa,CAAA;AAE5E,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAGtE"}
@@ -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 'layout:edit' permission).
5
+ // Single source of truth — used by both API guards and dashboard buttons.
6
+ export const ROLES_ALLOWED_TO_EDIT_LAYOUT = ['admin'];
7
+ export function canEditLayout(role) {
8
+ if (!role)
9
+ return false;
10
+ return ROLES_ALLOWED_TO_EDIT_LAYOUT.includes(role);
11
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"media.repository.d.ts","sourceRoot":"","sources":["../src/media.repository.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnE;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAEnD;;OAEG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpC;;OAEG;IACH,IAAI,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,WAAW,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEnG;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/C;;OAEG;IACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/C;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzC;;OAEG;IACH,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CACpC"}
1
+ {"version":3,"file":"media.repository.d.ts","sourceRoot":"","sources":["../src/media.repository.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnE;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAEnD;;OAEG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpC;;OAEG;IACH,IAAI,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,WAAW,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEnG;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/C;;OAEG;IACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/C;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzC;;OAEG;IACH,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CACpC"}
@@ -1 +1,3 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
1
3
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"notification-service.d.ts","sourceRoot":"","sources":["../../src/notifications/notification-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAEpE,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,gBAAgB,CAAA;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC;;;;;;;;OAQG;IACH,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAC7D"}
1
+ {"version":3,"file":"notification-service.d.ts","sourceRoot":"","sources":["../../src/notifications/notification-service.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAEpE,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,gBAAgB,CAAA;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC;;;;;;;;OAQG;IACH,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAC7D"}
@@ -1 +1,3 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
1
3
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"notification.repository.d.ts","sourceRoot":"","sources":["../../src/notifications/notification.repository.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;AAEvE,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,gBAAgB,CAAA;IACtB,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAA;IAElD;;;;;;OAMG;IACH,KAAK,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAEnC;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,kBAAkB,EAAE,IAAI,GAAG,WAAW,GAAG,QAAQ,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAExF,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/C,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjD,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,MAAM,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9C"}
1
+ {"version":3,"file":"notification.repository.d.ts","sourceRoot":"","sources":["../../src/notifications/notification.repository.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;AAEvE,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,gBAAgB,CAAA;IACtB,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAA;IAElD;;;;;;OAMG;IACH,KAAK,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAEnC;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,kBAAkB,EAAE,IAAI,GAAG,WAAW,GAAG,QAAQ,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAExF,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/C,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjD,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,MAAM,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9C"}
@@ -1,10 +1,3 @@
1
- /**
2
- * Notification repository contract.
3
- *
4
- * Persists in-app notifications (admin inbox) and exposes the aggregate stats
5
- * the GET /notifications handler needs to build a strong ETag without reading
6
- * every row.
7
- *
8
- * @module @beechcms/core/notifications/notification.repository
9
- */
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
10
3
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"activity-log.repository.d.ts","sourceRoot":"","sources":["../../src/observability/activity-log.repository.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAEtE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,MAAM,EAAE,cAAc,CAAA;IACtB,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACvC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,cAAc,CAAA;IACtB,UAAU,EAAE,UAAU,CAAA;IACtB,sEAAsE;IACtE,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,sBAAsB;IACrC;;;;;;OAMG;IACH,IAAI,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAA;IAEnE;;;;;;;OAOG;IACH,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CACxD"}
1
+ {"version":3,"file":"activity-log.repository.d.ts","sourceRoot":"","sources":["../../src/observability/activity-log.repository.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAEtE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,MAAM,EAAE,cAAc,CAAA;IACtB,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACvC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,cAAc,CAAA;IACtB,UAAU,EAAE,UAAU,CAAA;IACtB,sEAAsE;IACtE,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,sBAAsB;IACrC;;;;;;OAMG;IACH,IAAI,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAA;IAEnE;;;;;;;OAOG;IACH,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CACxD"}