@beechcms/core 0.6.0-preview.3 → 0.6.0

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 (243) hide show
  1. package/dist/automations/automations-grammar-words.d.ts.map +1 -0
  2. package/dist/automations/automations.repository.interface.d.ts.map +1 -0
  3. package/dist/automations/automations.runner.interface.d.ts.map +1 -0
  4. package/dist/automations/automations.types.d.ts.map +1 -0
  5. package/dist/common/clock.d.ts.map +1 -0
  6. package/dist/common/email.provider.d.ts +32 -0
  7. package/dist/common/email.provider.d.ts.map +1 -0
  8. package/dist/common/email.provider.js +4 -0
  9. package/dist/{hooks.d.ts → common/hooks.d.ts} +2 -2
  10. package/dist/common/hooks.d.ts.map +1 -0
  11. package/dist/common/id-generator.d.ts.map +1 -0
  12. package/dist/common/storage.d.ts.map +1 -0
  13. package/dist/{content-scan.repository.d.ts → content/content-scan.repository.d.ts} +1 -1
  14. package/dist/content/content-scan.repository.d.ts.map +1 -0
  15. package/dist/{content.repository.d.ts → content/content.repository.d.ts} +13 -4
  16. package/dist/content/content.repository.d.ts.map +1 -0
  17. package/dist/content/demo-data.repository.d.ts.map +1 -0
  18. package/dist/content/idempotency.repository.d.ts.map +1 -0
  19. package/dist/content/richtext/richtext-render.d.ts +12 -0
  20. package/dist/content/richtext/richtext-render.d.ts.map +1 -0
  21. package/dist/{richtext-render.js → content/richtext/richtext-render.js} +7 -14
  22. package/dist/content/richtext/richtext.d.ts.map +1 -0
  23. package/dist/{seed.repository.d.ts → content/seed.repository.d.ts} +1 -1
  24. package/dist/content/seed.repository.d.ts.map +1 -0
  25. package/dist/content/slug-utils.d.ts.map +1 -0
  26. package/dist/{dashboard-layout.d.ts → dashboard-layout/dashboard-layout.d.ts} +1 -1
  27. package/dist/dashboard-layout/dashboard-layout.d.ts.map +1 -0
  28. package/dist/dashboard-layout/dashboard-layout.repository.d.ts.map +1 -0
  29. package/dist/dashboard-layout/dashboard-permissions.d.ts.map +1 -0
  30. package/dist/dashboard-layout/dashboard-scopes.d.ts.map +1 -0
  31. package/dist/dashboard-layout/kanban/kanban-position.repository.d.ts +19 -0
  32. package/dist/dashboard-layout/kanban/kanban-position.repository.d.ts.map +1 -0
  33. package/dist/dashboard-layout/kanban/kanban.d.ts +75 -0
  34. package/dist/dashboard-layout/kanban/kanban.d.ts.map +1 -0
  35. package/dist/dashboard-layout/kanban/kanban.js +82 -0
  36. package/dist/dashboard-layout/layout-permissions.d.ts.map +1 -0
  37. package/dist/{seed-layout.d.ts → dashboard-layout/seed-layout.d.ts} +77 -3
  38. package/dist/dashboard-layout/seed-layout.d.ts.map +1 -0
  39. package/dist/{seed-layout.js → dashboard-layout/seed-layout.js} +101 -8
  40. package/dist/{seed-layout.repository.d.ts → dashboard-layout/seed-layout.repository.d.ts} +5 -1
  41. package/dist/dashboard-layout/seed-layout.repository.d.ts.map +1 -0
  42. package/dist/dashboard-layout/view-authorization.d.ts +18 -0
  43. package/dist/dashboard-layout/view-authorization.d.ts.map +1 -0
  44. package/dist/dashboard-layout/view-authorization.js +28 -0
  45. package/dist/engine/ddl.d.ts +173 -0
  46. package/dist/engine/ddl.d.ts.map +1 -0
  47. package/dist/engine/ddl.js +454 -0
  48. package/dist/engine/define-seed.d.ts.map +1 -0
  49. package/dist/engine/engine.d.ts +4 -0
  50. package/dist/engine/engine.d.ts.map +1 -0
  51. package/dist/engine/engine.js +4 -0
  52. package/dist/engine/policies.d.ts.map +1 -0
  53. package/dist/engine/query.d.ts +23 -0
  54. package/dist/engine/query.d.ts.map +1 -0
  55. package/dist/engine/query.js +208 -0
  56. package/dist/engine/relations.d.ts.map +1 -0
  57. package/dist/{schema-mutator.d.ts → engine/schema-mutator.d.ts} +4 -0
  58. package/dist/engine/schema-mutator.d.ts.map +1 -0
  59. package/dist/engine/seed-ddl-destructive.d.ts.map +1 -0
  60. package/dist/engine/seed-ddl.d.ts.map +1 -0
  61. package/dist/engine/seed-registry.d.ts.map +1 -0
  62. package/dist/{seed-registry.js → engine/seed-registry.js} +1 -1
  63. package/dist/engine/seed-types-generator.d.ts +10 -0
  64. package/dist/engine/seed-types-generator.d.ts.map +1 -0
  65. package/dist/engine/seed-types-generator.js +86 -0
  66. package/dist/engine/seed-validation.d.ts.map +1 -0
  67. package/dist/{seed-validation.js → engine/seed-validation.js} +14 -1
  68. package/dist/engine/seeds.d.ts.map +1 -0
  69. package/dist/engine/serialize.d.ts +20 -0
  70. package/dist/engine/serialize.d.ts.map +1 -0
  71. package/dist/engine/serialize.js +160 -0
  72. package/dist/{types.d.ts → engine/types.d.ts} +102 -63
  73. package/dist/engine/types.d.ts.map +1 -0
  74. package/dist/engine/validation/cache.d.ts +12 -0
  75. package/dist/engine/validation/cache.d.ts.map +1 -0
  76. package/dist/engine/validation/cache.js +115 -0
  77. package/dist/engine/validation/file-branch.d.ts +48 -0
  78. package/dist/engine/validation/file-branch.d.ts.map +1 -0
  79. package/dist/engine/validation/file-branch.js +127 -0
  80. package/dist/engine/validation/index.d.ts +115 -0
  81. package/dist/engine/validation/index.d.ts.map +1 -0
  82. package/dist/engine/validation/index.js +333 -0
  83. package/dist/engine/validation/primitives.d.ts +33 -0
  84. package/dist/engine/validation/primitives.d.ts.map +1 -0
  85. package/dist/engine/validation/primitives.js +43 -0
  86. package/dist/engine/validation/richtext-sanitizer.d.ts +30 -0
  87. package/dist/engine/validation/richtext-sanitizer.d.ts.map +1 -0
  88. package/dist/engine/validation/richtext-sanitizer.js +193 -0
  89. package/dist/engine/validation/schema-builders.d.ts +12 -0
  90. package/dist/engine/validation/schema-builders.d.ts.map +1 -0
  91. package/dist/engine/validation/schema-builders.js +322 -0
  92. package/dist/index.d.ts +48 -40
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js +48 -40
  95. package/dist/media/file-types.d.ts +13 -0
  96. package/dist/media/file-types.d.ts.map +1 -0
  97. package/dist/media/media.repository.d.ts.map +1 -0
  98. package/dist/queue/queue.interface.d.ts +39 -0
  99. package/dist/queue/queue.interface.d.ts.map +1 -0
  100. package/dist/queue/queue.interface.js +3 -0
  101. package/dist/queue/queue.stub.d.ts +6 -0
  102. package/dist/queue/queue.stub.d.ts.map +1 -0
  103. package/dist/queue/queue.stub.js +8 -0
  104. package/dist/queue/scheduler.interface.d.ts.map +1 -0
  105. package/dist/queue/scheduler.interface.js +3 -0
  106. package/dist/queue/scheduler.stub.d.ts.map +1 -0
  107. package/dist/queue.interface.d.ts +39 -0
  108. package/dist/queue.interface.d.ts.map +1 -0
  109. package/dist/queue.stub.d.ts +6 -0
  110. package/dist/queue.stub.d.ts.map +1 -0
  111. package/dist/queue.stub.js +8 -0
  112. package/dist/rate-limit/rate-limiter.d.ts +5 -0
  113. package/dist/rate-limit/rate-limiter.d.ts.map +1 -1
  114. package/dist/search/search.repository.d.ts +1 -1
  115. package/dist/search/search.repository.d.ts.map +1 -1
  116. package/dist/seed-types-generator.d.ts +10 -0
  117. package/dist/seed-types-generator.d.ts.map +1 -0
  118. package/dist/seed-types-generator.js +86 -0
  119. package/dist/settings/site-settings.repository.d.ts.map +1 -0
  120. package/dist/settings/site-settings.repository.js +3 -0
  121. package/dist/webhook-crypto.d.ts +8 -0
  122. package/dist/webhook-crypto.d.ts.map +1 -0
  123. package/dist/webhook-crypto.js +37 -0
  124. package/dist/webhooks/webhook-crypto.d.ts +8 -0
  125. package/dist/webhooks/webhook-crypto.d.ts.map +1 -0
  126. package/dist/webhooks/webhook-crypto.js +37 -0
  127. package/dist/webhooks/webhook-validation.d.ts.map +1 -0
  128. package/dist/widget/widget.repository.d.ts +1 -1
  129. package/dist/widget/widget.repository.d.ts.map +1 -1
  130. package/package.json +6 -1
  131. package/dist/automations-grammar-words.d.ts.map +0 -1
  132. package/dist/automations.repository.interface.d.ts.map +0 -1
  133. package/dist/automations.runner.interface.d.ts.map +0 -1
  134. package/dist/automations.runner.stub.d.ts +0 -5
  135. package/dist/automations.runner.stub.d.ts.map +0 -1
  136. package/dist/automations.runner.stub.js +0 -7
  137. package/dist/automations.types.d.ts.map +0 -1
  138. package/dist/clock.d.ts.map +0 -1
  139. package/dist/content-scan.repository.d.ts.map +0 -1
  140. package/dist/content.repository.d.ts.map +0 -1
  141. package/dist/dashboard-layout.d.ts.map +0 -1
  142. package/dist/dashboard-layout.repository.d.ts.map +0 -1
  143. package/dist/dashboard-permissions.d.ts.map +0 -1
  144. package/dist/dashboard-scopes.d.ts.map +0 -1
  145. package/dist/define-seed.d.ts.map +0 -1
  146. package/dist/demo-data.repository.d.ts.map +0 -1
  147. package/dist/engine.d.ts +0 -144
  148. package/dist/engine.d.ts.map +0 -1
  149. package/dist/engine.js +0 -723
  150. package/dist/file-types.d.ts +0 -13
  151. package/dist/file-types.d.ts.map +0 -1
  152. package/dist/hooks.d.ts.map +0 -1
  153. package/dist/id-generator.d.ts.map +0 -1
  154. package/dist/idempotency.repository.d.ts.map +0 -1
  155. package/dist/layout-permissions.d.ts.map +0 -1
  156. package/dist/media.repository.d.ts.map +0 -1
  157. package/dist/policies.d.ts.map +0 -1
  158. package/dist/relations.d.ts.map +0 -1
  159. package/dist/richtext-render.d.ts +0 -11
  160. package/dist/richtext-render.d.ts.map +0 -1
  161. package/dist/richtext.d.ts.map +0 -1
  162. package/dist/scheduler.interface.d.ts.map +0 -1
  163. package/dist/scheduler.stub.d.ts.map +0 -1
  164. package/dist/schema-mutator.d.ts.map +0 -1
  165. package/dist/seed-ddl-destructive.d.ts.map +0 -1
  166. package/dist/seed-ddl.d.ts.map +0 -1
  167. package/dist/seed-layout.d.ts.map +0 -1
  168. package/dist/seed-layout.repository.d.ts.map +0 -1
  169. package/dist/seed-registry.d.ts.map +0 -1
  170. package/dist/seed-validation.d.ts.map +0 -1
  171. package/dist/seed.repository.d.ts.map +0 -1
  172. package/dist/seeds.d.ts.map +0 -1
  173. package/dist/site-settings.repository.d.ts.map +0 -1
  174. package/dist/slug-utils.d.ts.map +0 -1
  175. package/dist/storage.d.ts.map +0 -1
  176. package/dist/types.d.ts.map +0 -1
  177. package/dist/validation.d.ts +0 -50
  178. package/dist/validation.d.ts.map +0 -1
  179. package/dist/validation.js +0 -659
  180. package/dist/webhook-validation.d.ts.map +0 -1
  181. /package/dist/{automations-grammar-words.d.ts → automations/automations-grammar-words.d.ts} +0 -0
  182. /package/dist/{automations-grammar-words.js → automations/automations-grammar-words.js} +0 -0
  183. /package/dist/{automations.repository.interface.d.ts → automations/automations.repository.interface.d.ts} +0 -0
  184. /package/dist/{automations.repository.interface.js → automations/automations.repository.interface.js} +0 -0
  185. /package/dist/{automations.runner.interface.d.ts → automations/automations.runner.interface.d.ts} +0 -0
  186. /package/dist/{automations.runner.interface.js → automations/automations.runner.interface.js} +0 -0
  187. /package/dist/{automations.types.d.ts → automations/automations.types.d.ts} +0 -0
  188. /package/dist/{automations.types.js → automations/automations.types.js} +0 -0
  189. /package/dist/{clock.d.ts → common/clock.d.ts} +0 -0
  190. /package/dist/{clock.js → common/clock.js} +0 -0
  191. /package/dist/{hooks.js → common/hooks.js} +0 -0
  192. /package/dist/{id-generator.d.ts → common/id-generator.d.ts} +0 -0
  193. /package/dist/{id-generator.js → common/id-generator.js} +0 -0
  194. /package/dist/{storage.d.ts → common/storage.d.ts} +0 -0
  195. /package/dist/{storage.js → common/storage.js} +0 -0
  196. /package/dist/{content-scan.repository.js → content/content-scan.repository.js} +0 -0
  197. /package/dist/{content.repository.js → content/content.repository.js} +0 -0
  198. /package/dist/{demo-data.repository.d.ts → content/demo-data.repository.d.ts} +0 -0
  199. /package/dist/{demo-data.repository.js → content/demo-data.repository.js} +0 -0
  200. /package/dist/{idempotency.repository.d.ts → content/idempotency.repository.d.ts} +0 -0
  201. /package/dist/{idempotency.repository.js → content/idempotency.repository.js} +0 -0
  202. /package/dist/{richtext.d.ts → content/richtext/richtext.d.ts} +0 -0
  203. /package/dist/{richtext.js → content/richtext/richtext.js} +0 -0
  204. /package/dist/{seed.repository.js → content/seed.repository.js} +0 -0
  205. /package/dist/{slug-utils.d.ts → content/slug-utils.d.ts} +0 -0
  206. /package/dist/{slug-utils.js → content/slug-utils.js} +0 -0
  207. /package/dist/{dashboard-layout.js → dashboard-layout/dashboard-layout.js} +0 -0
  208. /package/dist/{dashboard-layout.repository.d.ts → dashboard-layout/dashboard-layout.repository.d.ts} +0 -0
  209. /package/dist/{dashboard-layout.repository.js → dashboard-layout/dashboard-layout.repository.js} +0 -0
  210. /package/dist/{dashboard-permissions.d.ts → dashboard-layout/dashboard-permissions.d.ts} +0 -0
  211. /package/dist/{dashboard-permissions.js → dashboard-layout/dashboard-permissions.js} +0 -0
  212. /package/dist/{dashboard-scopes.d.ts → dashboard-layout/dashboard-scopes.d.ts} +0 -0
  213. /package/dist/{dashboard-scopes.js → dashboard-layout/dashboard-scopes.js} +0 -0
  214. /package/dist/{media.repository.js → dashboard-layout/kanban/kanban-position.repository.js} +0 -0
  215. /package/dist/{layout-permissions.d.ts → dashboard-layout/layout-permissions.d.ts} +0 -0
  216. /package/dist/{layout-permissions.js → dashboard-layout/layout-permissions.js} +0 -0
  217. /package/dist/{seed-layout.repository.js → dashboard-layout/seed-layout.repository.js} +0 -0
  218. /package/dist/{define-seed.d.ts → engine/define-seed.d.ts} +0 -0
  219. /package/dist/{define-seed.js → engine/define-seed.js} +0 -0
  220. /package/dist/{policies.d.ts → engine/policies.d.ts} +0 -0
  221. /package/dist/{policies.js → engine/policies.js} +0 -0
  222. /package/dist/{relations.d.ts → engine/relations.d.ts} +0 -0
  223. /package/dist/{relations.js → engine/relations.js} +0 -0
  224. /package/dist/{schema-mutator.js → engine/schema-mutator.js} +0 -0
  225. /package/dist/{seed-ddl-destructive.d.ts → engine/seed-ddl-destructive.d.ts} +0 -0
  226. /package/dist/{seed-ddl-destructive.js → engine/seed-ddl-destructive.js} +0 -0
  227. /package/dist/{seed-ddl.d.ts → engine/seed-ddl.d.ts} +0 -0
  228. /package/dist/{seed-ddl.js → engine/seed-ddl.js} +0 -0
  229. /package/dist/{seed-registry.d.ts → engine/seed-registry.d.ts} +0 -0
  230. /package/dist/{seed-validation.d.ts → engine/seed-validation.d.ts} +0 -0
  231. /package/dist/{seeds.d.ts → engine/seeds.d.ts} +0 -0
  232. /package/dist/{seeds.js → engine/seeds.js} +0 -0
  233. /package/dist/{types.js → engine/types.js} +0 -0
  234. /package/dist/{file-types.js → media/file-types.js} +0 -0
  235. /package/dist/{media.repository.d.ts → media/media.repository.d.ts} +0 -0
  236. /package/dist/{scheduler.interface.js → media/media.repository.js} +0 -0
  237. /package/dist/{scheduler.interface.d.ts → queue/scheduler.interface.d.ts} +0 -0
  238. /package/dist/{scheduler.stub.d.ts → queue/scheduler.stub.d.ts} +0 -0
  239. /package/dist/{scheduler.stub.js → queue/scheduler.stub.js} +0 -0
  240. /package/dist/{site-settings.repository.js → queue.interface.js} +0 -0
  241. /package/dist/{site-settings.repository.d.ts → settings/site-settings.repository.d.ts} +0 -0
  242. /package/dist/{webhook-validation.d.ts → webhooks/webhook-validation.d.ts} +0 -0
  243. /package/dist/{webhook-validation.js → webhooks/webhook-validation.js} +0 -0
@@ -0,0 +1,208 @@
1
+ import { tableName, ftsTableName, isValidColumn, indexableSearchBranches, SYSTEM_COLUMNS } from './ddl.js';
2
+ /**
3
+ * Builds a parameterized SQL SELECT query and bindings for a given Seed based on search, filtering, status, and ordering options.
4
+ *
5
+ * If `options.isCount` is set to `true`, it generates a counting query (`COUNT(*) as total`) rather than returning database rows.
6
+ * In count mode, column projections (`fields`), ordering (`orderBy` / `kanbanOrder`), and pagination (`LIMIT` / `OFFSET`) clauses and
7
+ * bindings are omitted, while join and filtering clauses are preserved.
8
+ *
9
+ * @param seed The seed definition.
10
+ * @param options Query configuration options.
11
+ * @returns The SQL query string and bindings.
12
+ * @example
13
+ * ```ts
14
+ * const { sql, bindings } = buildSelectQuery(postSeed, {
15
+ * status: 'published',
16
+ * filters: [{ column: 'title', type: 'text', conditions: [{ op: 'contains', value: 'cms' }] }],
17
+ * orderBy: { column: 'created_at', dir: 'DESC' },
18
+ * pagination: { limit: 20, offset: 0 },
19
+ * })
20
+ * ```
21
+ */
22
+ export function buildSelectQuery(seed, options = {}) {
23
+ const table = tableName(seed);
24
+ const { filters = [], orderBy, pagination, status, search, fields } = options;
25
+ const bindings = [];
26
+ const whereClauses = [];
27
+ let joinClause = '';
28
+ let kanbanOrderClause = '';
29
+ if (options.kanbanOrder) {
30
+ joinClause = `LEFT JOIN kanban_positions kp` +
31
+ ` ON kp.seed_slug = ? AND kp.entry_id = ${table}.id AND kp.axis_branch_id = ?`;
32
+ bindings.push(options.kanbanOrder.seedSlug, options.kanbanOrder.axisBranchId);
33
+ kanbanOrderClause = ` ORDER BY (kp.position IS NULL) ASC, kp.position ASC`;
34
+ }
35
+ const rtBranches = indexableSearchBranches(seed);
36
+ if (search && rtBranches.length > 0) {
37
+ const ftsTable = ftsTableName(seed);
38
+ joinClause += (joinClause ? ' ' : '') + `INNER JOIN ${ftsTable} ON ${ftsTable}.entry_id = ${table}.id`;
39
+ whereClauses.push(`${ftsTable} MATCH ?`);
40
+ bindings.push(`"${search.replace(/"/g, '""')}"*`);
41
+ }
42
+ if (status !== undefined && status !== null) {
43
+ whereClauses.push(`${table}.status = ?`);
44
+ bindings.push(status);
45
+ }
46
+ for (const group of filters) {
47
+ if (!isValidColumn(seed, group.column))
48
+ continue;
49
+ const col = SYSTEM_COLUMNS.has(group.column)
50
+ ? `${table}.${group.column}`
51
+ : group.column;
52
+ for (const cond of group.conditions) {
53
+ const clause = buildFilterCondition(col, group.type, cond, bindings);
54
+ if (clause)
55
+ whereClauses.push(clause);
56
+ }
57
+ }
58
+ let selectCols = options.isCount ? 'COUNT(*) as total' : `${table}.*`;
59
+ if (!options.isCount && fields && fields.length > 0) {
60
+ const valid = fields.filter(f => isValidColumn(seed, f));
61
+ if (valid.length > 0) {
62
+ selectCols = valid
63
+ .map(f => (SYSTEM_COLUMNS.has(f) ? `${table}.${f}` : f))
64
+ .join(', ');
65
+ }
66
+ }
67
+ if (!options.isCount && options.kanbanOrder)
68
+ selectCols += ', kp.position';
69
+ let sql = `SELECT ${selectCols} FROM ${table}`;
70
+ if (joinClause)
71
+ sql += ` ${joinClause}`;
72
+ if (whereClauses.length > 0)
73
+ sql += ` WHERE ${whereClauses.join(' AND ')}`;
74
+ if (!options.isCount) {
75
+ if (kanbanOrderClause) {
76
+ sql += kanbanOrderClause;
77
+ }
78
+ else if (orderBy && isValidColumn(seed, orderBy.column)) {
79
+ const dir = orderBy.dir === 'DESC' ? 'DESC' : 'ASC';
80
+ const col = SYSTEM_COLUMNS.has(orderBy.column)
81
+ ? `${table}.${orderBy.column}`
82
+ : orderBy.column;
83
+ sql += ` ORDER BY ${col} ${dir}`;
84
+ }
85
+ else {
86
+ sql += ` ORDER BY ${table}.created_at DESC`;
87
+ }
88
+ if (pagination) {
89
+ sql += ` LIMIT ? OFFSET ?`;
90
+ bindings.push(pagination.limit, pagination.offset);
91
+ }
92
+ }
93
+ return { sql, bindings };
94
+ }
95
+ /**
96
+ * Normalizes user-provided filter values to a format appropriate for SQL execution.
97
+ *
98
+ * @param type The type of the filter.
99
+ * @param value The value to normalize.
100
+ * @returns The normalized value, or null.
101
+ */
102
+ function normalizeFilterValue(type, value) {
103
+ if (value === null || value === undefined)
104
+ return null;
105
+ if (type === 'date') {
106
+ if (typeof value === 'number')
107
+ return value;
108
+ if (typeof value === 'string' && value.trim() !== '') {
109
+ const d = new Date(value);
110
+ return Number.isNaN(d.getTime()) ? null : Math.floor(d.getTime() / 1000);
111
+ }
112
+ return null;
113
+ }
114
+ if (type === 'boolean') {
115
+ return value ? 1 : 0;
116
+ }
117
+ if (type === 'number') {
118
+ if (typeof value === 'number')
119
+ return value;
120
+ if (typeof value === 'string' && value.trim() !== '') {
121
+ const n = Number(value);
122
+ return Number.isNaN(n) ? null : n;
123
+ }
124
+ return null;
125
+ }
126
+ return value;
127
+ }
128
+ /**
129
+ * Translates a filter condition into its SQL clause and adds parameters to the bindings list.
130
+ *
131
+ * @param col The column name in the table.
132
+ * @param type The type of the filter.
133
+ * @param cond The condition containing the operator and value.
134
+ * @param bindings The array of bindings where parameterized values are appended.
135
+ * @returns The SQL condition fragment, or null if invalid.
136
+ */
137
+ function buildFilterCondition(col, type, cond, bindings) {
138
+ const { op, value } = cond;
139
+ if (op === 'is_empty' || op === 'is_not_empty') {
140
+ const isEmpty = op === 'is_empty';
141
+ if (type === 'text') {
142
+ return isEmpty ? `(${col} IS NULL OR ${col} = '')` : `(${col} IS NOT NULL AND ${col} != '')`;
143
+ }
144
+ if (type === 'tags' || type === 'json') {
145
+ return isEmpty
146
+ ? `(${col} IS NULL OR ${col} = '[]' OR ${col} = '{}')`
147
+ : `(${col} IS NOT NULL AND ${col} != '[]' AND ${col} != '{}')`;
148
+ }
149
+ return isEmpty ? `${col} IS NULL` : `${col} IS NOT NULL`;
150
+ }
151
+ if (op === 'in' || op === 'not_in') {
152
+ if (!Array.isArray(value) || value.length === 0)
153
+ return null;
154
+ const normalizedValues = value
155
+ .map((v) => normalizeFilterValue(type, v))
156
+ .filter((v) => v !== null);
157
+ if (normalizedValues.length === 0)
158
+ return null;
159
+ const placeholders = normalizedValues.map(() => '?').join(', ');
160
+ bindings.push(...normalizedValues);
161
+ return `${col} ${op === 'in' ? 'IN' : 'NOT IN'} (${placeholders})`;
162
+ }
163
+ if (op === 'has_tag' || op === 'has_any_tag' || op === 'has_all_tags') {
164
+ if (type !== 'tags' && type !== 'json')
165
+ return null;
166
+ const tags = (op === 'has_tag' ? [value] : Array.isArray(value) ? value : [value])
167
+ .map(t => String(t));
168
+ if (tags.length === 0)
169
+ return null;
170
+ // json_each on array → value is the element; on object → key is the property name.
171
+ // Tags stored as ["photo"] (array) or {"photo":"#3b82f6"} (object) must both match.
172
+ const tagRef = `CASE json_type(${col}) WHEN 'array' THEN value ELSE key END`;
173
+ if (op === 'has_tag' || op === 'has_any_tag') {
174
+ const placeholders = tags.map(() => '?').join(', ');
175
+ bindings.push(...tags);
176
+ return `EXISTS (SELECT 1 FROM json_each(${col}) WHERE ${tagRef} IN (${placeholders}))`;
177
+ }
178
+ const clauses = tags.map(() => `EXISTS (SELECT 1 FROM json_each(${col}) WHERE ${tagRef} = ?)`);
179
+ bindings.push(...tags);
180
+ return `(${clauses.join(' AND ')})`;
181
+ }
182
+ const normalized = normalizeFilterValue(type, value);
183
+ if (normalized === null)
184
+ return null;
185
+ if (op === 'eq' || op === 'neq') {
186
+ const sqlOp = op === 'eq' ? '=' : '!=';
187
+ bindings.push(normalized);
188
+ return `${col} ${sqlOp} ?`;
189
+ }
190
+ if (op === 'contains' || op === 'not_contains' || op === 'starts_with' || op === 'ends_with') {
191
+ const sqlOp = op === 'not_contains' ? 'NOT LIKE' : 'LIKE';
192
+ let pattern = String(value);
193
+ if (op === 'contains' || op === 'not_contains')
194
+ pattern = `%${pattern}%`;
195
+ else if (op === 'starts_with')
196
+ pattern = `${pattern}%`;
197
+ else if (op === 'ends_with')
198
+ pattern = `%${pattern}`;
199
+ bindings.push(pattern);
200
+ return `${col} ${sqlOp} ?`;
201
+ }
202
+ const mathOps = { gt: '>', gte: '>=', lt: '<', lte: '<=' };
203
+ if (mathOps[op]) {
204
+ bindings.push(normalized);
205
+ return `${col} ${mathOps[op]} ?`;
206
+ }
207
+ return null;
208
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relations.d.ts","sourceRoot":"","sources":["../../src/engine/relations.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAEtC,MAAM,WAAW,aAAa;IAC5B,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAA;IAClB,qFAAqF;IACrF,WAAW,EAAE,MAAM,CAAA;IACnB,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAA;IACnB,kEAAkE;IAClE,YAAY,EAAE,QAAQ,GAAG,OAAO,CAAA;IAChC,+EAA+E;IAC/E,SAAS,EAAE,OAAO,CAAA;CACnB;AAED,uDAAuD;AACvD,MAAM,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,CAAA;AAErD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,UAAU,CAoBzD"}
@@ -18,5 +18,9 @@ export interface ISchemaMutator {
18
18
  * type-change column rebuild, full type drop). The caller assembles the
19
19
  * statements; the impl validates identifiers and runs them as one D1 batch. */
20
20
  execDestructive(statements: string[]): Promise<void>;
21
+ /** Reads all rows from a table for the specified columns only.
22
+ * Used before DROP TABLE to collect R2 keys for media cleanup.
23
+ * All identifiers are validated against `^[A-Za-z0-9_]+$`. */
24
+ fetchRows(table: string, columns: string[]): Promise<Record<string, unknown>[]>;
21
25
  }
22
26
  //# sourceMappingURL=schema-mutator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-mutator.d.ts","sourceRoot":"","sources":["../../src/engine/schema-mutator.ts"],"names":[],"mappings":"AAGA;;yFAEyF;AACzF,MAAM,WAAW,cAAc;IAC7B,8EAA8E;IAC9E,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAA;IACtD;;8EAE0E;IAC1E,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAS5C,sCAAsC;IACtC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACvC,kDAAkD;IAClD,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxD,0DAA0D;IAC1D,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpE;;oFAEgF;IAChF,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEpD;;mEAE+D;IAC/D,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAA;CAChF"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed-ddl-destructive.d.ts","sourceRoot":"","sources":["../../src/engine/seed-ddl-destructive.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAU,UAAU,EAAE,MAAM,YAAY,CAAA;AAkC1D;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CAgBtD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAgBtE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAQ7F;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CAiBnD;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,UAAU,EACnB,WAAW,EAAE,IAAI,GAChB,gBAAgB,CAyClB"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed-ddl.d.ts","sourceRoot":"","sources":["../../src/engine/seed-ddl.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AActC;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CAanD;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,UAAU,CAqBnF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CA0BnD"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed-registry.d.ts","sourceRoot":"","sources":["../../src/engine/seed-registry.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAG9C,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,GAAG,IAAI,IAAI,EAAE,CAAA;IAEb;;;;OAIG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAA;IAE9B;;;;;OAKG;IACH,kBAAkB,IAAI,IAAI,EAAE,CAAA;IAE5B;;;OAGG;IACH,cAAc,IAAI,IAAI,EAAE,CAAA;IAExB;;;OAGG;IACH,YAAY,IAAI,IAAI,EAAE,CAAA;CACvB;AAID,qBAAa,YAAa,YAAW,aAAa;IAChD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;IAErC,YAAY,KAAK,EAAE,IAAI,EAAE,EA0BxB;IAED,GAAG,IAAI,IAAI,EAAE,CAEZ;IAED,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAE7B;IAED,kBAAkB,IAAI,IAAI,EAAE,CAE3B;IAED,cAAc,IAAI,IAAI,EAAE,CAEvB;IAED,YAAY,IAAI,IAAI,EAAE,CAErB;CACF;AAED;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,YAAY;CAAG;AAEzD,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEpE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,GAAG,MAAM,CAQjE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,CAsE1E"}
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // Copyright (c) 2024–2026 Flavio De Musso
3
- import { AUTOMATION_RESERVED_WORDS } from './automations-grammar-words.js';
3
+ import { AUTOMATION_RESERVED_WORDS } from '../automations/automations-grammar-words.js';
4
4
  const BRANCH_ID_RE = /^br_[A-Za-z0-9]+$/;
5
5
  export class SeedRegistry {
6
6
  seedMap;
@@ -0,0 +1,10 @@
1
+ import type { Seed, Branch } from './types.js';
2
+ /** slug/alias → PascalCase identifier. 'blog-posts' → 'BlogPosts'. */
3
+ export declare function pascalCase(input: string): string;
4
+ /** Maps a single Branch to its TypeScript type expression (no optional marker). */
5
+ export declare function tsTypeForBranch(branch: Branch): string;
6
+ /** Emits one exported interface for a single Seed. */
7
+ export declare function interfaceForSeed(seed: Seed): string;
8
+ /** Pure entry point. Deterministic: sorts seeds by slug for stable diffs. */
9
+ export declare function generateSeedTypes(seeds: Seed[]): string;
10
+ //# sourceMappingURL=seed-types-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed-types-generator.d.ts","sourceRoot":"","sources":["../../src/engine/seed-types-generator.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAe9C,sEAAsE;AACtE,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMhD;AAMD,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAoCtD;AAWD,sDAAsD;AACtD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAYnD;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,CASvD"}
@@ -0,0 +1,86 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ const HEADER = '// Questo file è generato automaticamente da BeechCMS CLI. Non modificarlo direttamente.\n';
4
+ /** System columns emitted by generateCreateTable — always present, never optional. */
5
+ const SYSTEM_FIELDS = ` id: string\n` +
6
+ ` slug: string\n` +
7
+ ` status: 'draft' | 'review' | 'published' | 'archived'\n`;
8
+ const SYSTEM_TIMESTAMPS = ` created_at: number\n` +
9
+ ` updated_at: number\n`;
10
+ /** slug/alias → PascalCase identifier. 'blog-posts' → 'BlogPosts'. */
11
+ export function pascalCase(input) {
12
+ return input
13
+ .split(/[^A-Za-z0-9]+/)
14
+ .filter(Boolean)
15
+ .map(w => w.charAt(0).toUpperCase() + w.slice(1))
16
+ .join('');
17
+ }
18
+ function literalUnion(options) {
19
+ return options.map(o => `'${o.replace(/'/g, "\\'")}'`).join(' | ');
20
+ }
21
+ /** Maps a single Branch to its TypeScript type expression (no optional marker). */
22
+ export function tsTypeForBranch(branch) {
23
+ switch (branch.type) {
24
+ case 'number':
25
+ case 'date':
26
+ return 'number';
27
+ case 'boolean':
28
+ return 'boolean';
29
+ case 'json':
30
+ return 'unknown';
31
+ case 'text':
32
+ case 'richtext':
33
+ case 'file': {
34
+ const base = branch.type === 'text' && branch.options?.length
35
+ ? literalUnion(branch.options)
36
+ : 'string';
37
+ return branch.type === 'file' && branch.multiple ? `${base}[]` : base;
38
+ }
39
+ case 'tags': {
40
+ const base = branch.options?.length ? `(${literalUnion(branch.options)})` : 'string';
41
+ return `${base}[]`;
42
+ }
43
+ case 'relation':
44
+ return branch.multiple ? 'string[]' : 'string';
45
+ case 'repeater': {
46
+ const inner = (branch.fields ?? [])
47
+ .map(f => `${propName(f)}${optional(f)}: ${tsTypeForBranch(f)}`)
48
+ .join('; ');
49
+ return `Array<{ ${inner} }>`;
50
+ }
51
+ default: {
52
+ // Exhaustiveness guard — fails the build if BranchType gains a member.
53
+ const _never = branch.type;
54
+ return _never;
55
+ }
56
+ }
57
+ }
58
+ function optional(branch) {
59
+ return branch.requiredOnCreate ? '' : '?';
60
+ }
61
+ function propName(branch) {
62
+ // alias is a valid SQL column; quote only if not a plain identifier.
63
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(branch.alias) ? branch.alias : `'${branch.alias}'`;
64
+ }
65
+ /** Emits one exported interface for a single Seed. */
66
+ export function interfaceForSeed(seed) {
67
+ const name = pascalCase(seed.slug);
68
+ const props = seed.branches
69
+ .map(b => ` ${propName(b)}${optional(b)}: ${tsTypeForBranch(b)}`)
70
+ .join('\n');
71
+ return (`export interface ${name} {\n` +
72
+ SYSTEM_FIELDS +
73
+ (props ? props + '\n' : '') +
74
+ SYSTEM_TIMESTAMPS +
75
+ `}\n`);
76
+ }
77
+ /** Pure entry point. Deterministic: sorts seeds by slug for stable diffs. */
78
+ export function generateSeedTypes(seeds) {
79
+ const sorted = [...seeds].sort((a, b) => a.slug.localeCompare(b.slug));
80
+ const interfaces = sorted.map(interfaceForSeed).join('\n');
81
+ const registry = `export interface SeedRegistryTypes {\n` +
82
+ sorted.map(s => ` ${propName({ alias: s.slug })}: ${pascalCase(s.slug)}`).join('\n') +
83
+ (sorted.length ? '\n' : '') +
84
+ `}\n`;
85
+ return `${HEADER}\n${interfaces}\n${registry}`;
86
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed-validation.d.ts","sourceRoot":"","sources":["../../src/engine/seed-validation.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAMtC;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,QAAyB,CAAA;AAErD,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,KAAK,EAAE,OAAO,CAAA;CACf;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,mBAAmB,EAAE,CAwO5E;AAED,6CAA6C;AAC7C,wBAAgB,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAErD"}
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // Copyright (c) 2024–2026 Flavio De Musso
3
- import { AUTOMATION_RESERVED_WORDS } from './automations-grammar-words.js';
3
+ import { AUTOMATION_RESERVED_WORDS } from '../automations/automations-grammar-words.js';
4
4
  import { sortSeedsByDependencies } from './seed-registry.js';
5
5
  const BRANCH_ID_RE = /^br_[A-Za-z0-9]+$/;
6
6
  /**
@@ -216,6 +216,19 @@ export function validateSeedDefinitions(seeds) {
216
216
  result.push({ slug: seed.slug, messages: warnings, fatal: false });
217
217
  }
218
218
  }
219
+ // ── Fatal 12: displayNameAlias format validation ───────────────────────────
220
+ for (const seed of seeds) {
221
+ if (typeof seed.displayNameAlias !== 'string' || !BRANCH_ALIAS_RE.test(seed.displayNameAlias)) {
222
+ result.push({
223
+ slug: seed.slug,
224
+ messages: [
225
+ `displayNameAlias '${seed.displayNameAlias}' is invalid. Expected format ${BRANCH_ALIAS_RE.source} ` +
226
+ `(lowercase letter followed by alphanumeric characters or underscores).`,
227
+ ],
228
+ fatal: true,
229
+ });
230
+ }
231
+ }
219
232
  return result;
220
233
  }
221
234
  /** Convenience: true iff no fatal issues. */
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seeds.d.ts","sourceRoot":"","sources":["../../src/engine/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"}
@@ -0,0 +1,20 @@
1
+ import type { Branch } from './types.js';
2
+ /**
3
+ * Serializes a value for writing to the DB.
4
+ * boolean → 0/1 | date → Unix timestamp | json/tags/richtext/repeater → JSON string
5
+ *
6
+ * @param branch The branch definition.
7
+ * @param value The value to serialize.
8
+ * @returns The serialized DB value (string, number, or null).
9
+ */
10
+ export declare function serializeForDb(branch: Branch, value: unknown): string | number | null;
11
+ /**
12
+ * Deserializes a value read from the DB to its API/JS representation.
13
+ * 0/1 → boolean | Unix timestamp → ISO 8601 | JSON string → object/array
14
+ *
15
+ * @param branch The branch definition.
16
+ * @param value The raw database value.
17
+ * @returns The deserialized value.
18
+ */
19
+ export declare function deserializeFromDb(branch: Branch, value: unknown): unknown;
20
+ //# sourceMappingURL=serialize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../../src/engine/serialize.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,MAAM,EAOP,MAAM,YAAY,CAAC;AAmEpB;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAsCrF;AAGD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAmCzE"}
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Checks if a branch represents a multiple asset list field.
3
+ *
4
+ * @param branch The branch definition.
5
+ * @returns True if it's an asset list, false otherwise.
6
+ */
7
+ function isAssetListBranch(branch) {
8
+ return branch.type === 'file' && (branch.multiple === true || branch.format === 'asset-list');
9
+ }
10
+ /**
11
+ * Normalizes an unknown value to a valid HTTP URL string, or null.
12
+ *
13
+ * @param value The value to normalize.
14
+ * @returns The normalized URL string, or null if invalid.
15
+ */
16
+ function normalizeHttpUrl(value) {
17
+ if (typeof value !== 'string')
18
+ return null;
19
+ const cleaned = value.trim();
20
+ if (!cleaned)
21
+ return null;
22
+ try {
23
+ const parsed = new URL(cleaned);
24
+ return parsed.protocol.startsWith('http') ? cleaned : null;
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ /**
31
+ * Safely parses a JSON string, falling back to the raw string on error.
32
+ *
33
+ * @param value The string to parse.
34
+ * @returns The parsed object, or the original string.
35
+ */
36
+ function parseJsonSafe(value) {
37
+ try {
38
+ return JSON.parse(value);
39
+ }
40
+ catch {
41
+ return value;
42
+ }
43
+ }
44
+ /**
45
+ * Normalizes raw asset list values to a flat list of valid URL strings.
46
+ *
47
+ * @param rawValue The raw value (string, array, or object) to normalize.
48
+ * @returns An array of unique normalized URL strings.
49
+ */
50
+ function normalizeAssetListValue(rawValue) {
51
+ const input = typeof rawValue === 'string' ? parseJsonSafe(rawValue) : rawValue;
52
+ const values = Array.isArray(input) ? input : [input];
53
+ const normalized = [];
54
+ for (const item of values) {
55
+ if (item == null)
56
+ continue;
57
+ const direct = normalizeHttpUrl(item);
58
+ if (direct) {
59
+ normalized.push(direct);
60
+ continue;
61
+ }
62
+ if (typeof item === 'object' && !Array.isArray(item)) {
63
+ const fromObj = normalizeHttpUrl(item.url);
64
+ if (fromObj)
65
+ normalized.push(fromObj);
66
+ }
67
+ }
68
+ return [...new Set(normalized)];
69
+ }
70
+ /**
71
+ * Serializes a value for writing to the DB.
72
+ * boolean → 0/1 | date → Unix timestamp | json/tags/richtext/repeater → JSON string
73
+ *
74
+ * @param branch The branch definition.
75
+ * @param value The value to serialize.
76
+ * @returns The serialized DB value (string, number, or null).
77
+ */
78
+ export function serializeForDb(branch, value) {
79
+ if (value === null || value === undefined)
80
+ return null;
81
+ switch (branch.type) {
82
+ case 'boolean':
83
+ return value ? 1 : 0;
84
+ case 'json':
85
+ case 'tags':
86
+ case 'richtext':
87
+ return typeof value === 'string' ? value : JSON.stringify(value);
88
+ case 'date': {
89
+ if (typeof value === 'number')
90
+ return value;
91
+ if (typeof value === 'string') {
92
+ const d = new Date(value);
93
+ if (Number.isNaN(d.getTime()))
94
+ return null;
95
+ if (branch.format === 'date') {
96
+ const midnight = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
97
+ return Math.floor(midnight / 1000);
98
+ }
99
+ return Math.floor(d.getTime() / 1000);
100
+ }
101
+ return null;
102
+ }
103
+ case 'file':
104
+ if (isAssetListBranch(branch)) {
105
+ return Array.isArray(value) ? JSON.stringify(value) : typeof value === 'string' ? value : null;
106
+ }
107
+ return typeof value === 'string' ? value : null;
108
+ case 'repeater':
109
+ return JSON.stringify(Array.isArray(value) ? value : []);
110
+ default:
111
+ return typeof value === 'string' ? value : typeof value === 'number' ? value : null;
112
+ }
113
+ }
114
+ /**
115
+ * Deserializes a value read from the DB to its API/JS representation.
116
+ * 0/1 → boolean | Unix timestamp → ISO 8601 | JSON string → object/array
117
+ *
118
+ * @param branch The branch definition.
119
+ * @param value The raw database value.
120
+ * @returns The deserialized value.
121
+ */
122
+ export function deserializeFromDb(branch, value) {
123
+ if (branch.type === 'repeater') {
124
+ if (typeof value !== 'string' || value.length === 0)
125
+ return [];
126
+ const parsed = parseJsonSafe(value);
127
+ return Array.isArray(parsed) ? parsed : [];
128
+ }
129
+ if (value === null || value === undefined)
130
+ return null;
131
+ switch (branch.type) {
132
+ case 'boolean':
133
+ return value === 1 || value === true;
134
+ case 'json':
135
+ case 'tags':
136
+ case 'richtext': {
137
+ if (typeof value === 'string') {
138
+ try {
139
+ return JSON.parse(value);
140
+ }
141
+ catch {
142
+ return value;
143
+ }
144
+ }
145
+ return value;
146
+ }
147
+ case 'date': {
148
+ if (typeof value !== 'number')
149
+ return null;
150
+ const d = new Date(value * 1000);
151
+ return branch.format === 'date' ? d.toISOString().slice(0, 10) : d.toISOString();
152
+ }
153
+ case 'file':
154
+ if (isAssetListBranch(branch))
155
+ return normalizeAssetListValue(value);
156
+ return typeof value === 'string' ? value : null;
157
+ default:
158
+ return value;
159
+ }
160
+ }